@guinetik/gcanvas 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
# State Module
|
|
2
|
+
|
|
3
|
+
> Flexible state machine for managing game and entity states with lifecycle callbacks.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The state module provides a `StateMachine` class for managing states with enter/update/exit lifecycle callbacks. It supports timed auto-transitions, event triggers, and sequential phase patterns - perfect for both high-level game states and entity-level behaviors.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { StateMachine } from 'gcanvas';
|
|
13
|
+
|
|
14
|
+
// Basic state machine
|
|
15
|
+
const fsm = new StateMachine({
|
|
16
|
+
initial: 'idle',
|
|
17
|
+
states: {
|
|
18
|
+
idle: {
|
|
19
|
+
enter: () => console.log('Now idle'),
|
|
20
|
+
update: (dt) => { /* per-frame logic */ },
|
|
21
|
+
exit: () => console.log('Leaving idle')
|
|
22
|
+
},
|
|
23
|
+
walking: {
|
|
24
|
+
enter: () => player.startWalkAnimation(),
|
|
25
|
+
update: (dt) => player.move(dt)
|
|
26
|
+
},
|
|
27
|
+
jumping: {
|
|
28
|
+
enter: () => player.jump(),
|
|
29
|
+
duration: 0.5,
|
|
30
|
+
next: 'falling'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Change state
|
|
36
|
+
fsm.setState('walking');
|
|
37
|
+
|
|
38
|
+
// Update each frame
|
|
39
|
+
fsm.update(dt);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Use Cases
|
|
43
|
+
|
|
44
|
+
| Pattern | Example |
|
|
45
|
+
|---------|---------|
|
|
46
|
+
| **Game States** | menu → playing → paused → game_over |
|
|
47
|
+
| **Entity Lifecycle** | spawn → active → dying → dead |
|
|
48
|
+
| **Attack Phases** | warning → charging → active → cooldown |
|
|
49
|
+
| **UI States** | hidden → entering → visible → exiting |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## StateMachine Class
|
|
54
|
+
|
|
55
|
+
### Constructor
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const fsm = new StateMachine({
|
|
59
|
+
initial: 'stateName', // Starting state
|
|
60
|
+
states: { ... }, // State definitions
|
|
61
|
+
context: this // Optional: bound to callbacks
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### State Definition
|
|
66
|
+
|
|
67
|
+
Each state can have these properties:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
states: {
|
|
71
|
+
stateName: {
|
|
72
|
+
// Lifecycle callbacks
|
|
73
|
+
enter: (data) => { }, // Called when entering state
|
|
74
|
+
update: (dt) => { }, // Called every frame
|
|
75
|
+
exit: (data) => { }, // Called when leaving state
|
|
76
|
+
|
|
77
|
+
// Timed auto-transition
|
|
78
|
+
duration: 1.5, // Seconds before auto-transition
|
|
79
|
+
next: 'nextState', // State to transition to
|
|
80
|
+
onComplete: () => { }, // Called if no 'next' defined
|
|
81
|
+
|
|
82
|
+
// Event-based transitions
|
|
83
|
+
on: {
|
|
84
|
+
'eventName': 'targetState',
|
|
85
|
+
'anotherEvent': {
|
|
86
|
+
target: 'targetState',
|
|
87
|
+
guard: () => canTransition,
|
|
88
|
+
action: () => doSomething()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Core Patterns
|
|
98
|
+
|
|
99
|
+
### 1. Game State Management
|
|
100
|
+
|
|
101
|
+
Manage high-level game flow with event-driven transitions.
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
class MyGame extends Game {
|
|
105
|
+
init() {
|
|
106
|
+
super.init();
|
|
107
|
+
|
|
108
|
+
this.fsm = new StateMachine({
|
|
109
|
+
initial: 'menu',
|
|
110
|
+
context: this,
|
|
111
|
+
states: {
|
|
112
|
+
menu: {
|
|
113
|
+
enter: () => this.showMenu(),
|
|
114
|
+
on: {
|
|
115
|
+
start: 'playing',
|
|
116
|
+
options: 'settings'
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
playing: {
|
|
120
|
+
enter: () => this.startGame(),
|
|
121
|
+
update: (dt) => this.updateGameplay(dt),
|
|
122
|
+
on: {
|
|
123
|
+
pause: 'paused',
|
|
124
|
+
death: 'game_over',
|
|
125
|
+
bossSpawn: 'boss_fight'
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
paused: {
|
|
129
|
+
enter: () => this.showPauseMenu(),
|
|
130
|
+
on: {
|
|
131
|
+
resume: 'playing',
|
|
132
|
+
quit: 'menu'
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
boss_fight: {
|
|
136
|
+
enter: () => this.startBossFight(),
|
|
137
|
+
update: (dt) => this.updateBossFight(dt),
|
|
138
|
+
on: {
|
|
139
|
+
bossDefeated: 'playing',
|
|
140
|
+
death: 'game_over'
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
game_over: {
|
|
144
|
+
enter: () => this.showGameOver(),
|
|
145
|
+
on: {
|
|
146
|
+
restart: 'playing',
|
|
147
|
+
menu: 'menu'
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
update(dt) {
|
|
155
|
+
super.update(dt);
|
|
156
|
+
this.fsm.update(dt);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Trigger transitions from game logic
|
|
160
|
+
onPlayerDeath() {
|
|
161
|
+
this.fsm.trigger('death');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
onBossDefeated() {
|
|
165
|
+
this.fsm.trigger('bossDefeated');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 2. Timed Phase Sequences
|
|
171
|
+
|
|
172
|
+
Perfect for attacks, effects, and entity lifecycles.
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
// Laser beam with phases: warning → charging → active → fade
|
|
176
|
+
class Laser extends GameObject {
|
|
177
|
+
constructor(game) {
|
|
178
|
+
super(game);
|
|
179
|
+
|
|
180
|
+
this.fsm = new StateMachine({
|
|
181
|
+
initial: 'warning',
|
|
182
|
+
context: this,
|
|
183
|
+
states: {
|
|
184
|
+
warning: {
|
|
185
|
+
duration: 0.3,
|
|
186
|
+
next: 'charging',
|
|
187
|
+
enter: () => { this.alpha = 0.3; }
|
|
188
|
+
},
|
|
189
|
+
charging: {
|
|
190
|
+
duration: 0.2,
|
|
191
|
+
next: 'active',
|
|
192
|
+
enter: () => { this.playChargeSound(); }
|
|
193
|
+
},
|
|
194
|
+
active: {
|
|
195
|
+
duration: 0.4,
|
|
196
|
+
next: 'fade',
|
|
197
|
+
enter: () => {
|
|
198
|
+
this.canDamage = true;
|
|
199
|
+
this.alpha = 1;
|
|
200
|
+
},
|
|
201
|
+
exit: () => { this.canDamage = false; }
|
|
202
|
+
},
|
|
203
|
+
fade: {
|
|
204
|
+
duration: 0.2,
|
|
205
|
+
enter: () => { /* Start fade animation */ },
|
|
206
|
+
exit: () => { this.destroy(); }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
update(dt) {
|
|
213
|
+
this.fsm.update(dt);
|
|
214
|
+
|
|
215
|
+
// Use progress for animations
|
|
216
|
+
if (this.fsm.is('fade')) {
|
|
217
|
+
this.alpha = 1 - this.fsm.progress;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 3. Using fromSequence() Factory
|
|
224
|
+
|
|
225
|
+
Shorthand for simple phase sequences.
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
// Lightning strike phases
|
|
229
|
+
this.fsm = StateMachine.fromSequence([
|
|
230
|
+
{ name: 'tracing', duration: 0.4, update: (dt) => this.trace(dt) },
|
|
231
|
+
{ name: 'active', duration: 0.3, enter: () => { this.canDamage = true; } },
|
|
232
|
+
{ name: 'fade', duration: 0.2, exit: () => this.destroy() }
|
|
233
|
+
], { context: this });
|
|
234
|
+
|
|
235
|
+
// With looping
|
|
236
|
+
this.fsm = StateMachine.fromSequence([
|
|
237
|
+
{ name: 'visible', duration: 0.5 },
|
|
238
|
+
{ name: 'hidden', duration: 0.5 }
|
|
239
|
+
], { loop: true });
|
|
240
|
+
|
|
241
|
+
// With completion callback
|
|
242
|
+
this.fsm = StateMachine.fromSequence([
|
|
243
|
+
{ name: 'phase1', duration: 1 },
|
|
244
|
+
{ name: 'phase2', duration: 1 },
|
|
245
|
+
{ name: 'phase3', duration: 1 }
|
|
246
|
+
], {
|
|
247
|
+
onComplete: () => console.log('Sequence finished!')
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 4. Entity AI States
|
|
252
|
+
|
|
253
|
+
```js
|
|
254
|
+
class Enemy extends GameObject {
|
|
255
|
+
constructor(game) {
|
|
256
|
+
super(game);
|
|
257
|
+
|
|
258
|
+
this.fsm = new StateMachine({
|
|
259
|
+
initial: 'patrol',
|
|
260
|
+
context: this,
|
|
261
|
+
states: {
|
|
262
|
+
patrol: {
|
|
263
|
+
update: (dt) => this.moveAlongPath(dt),
|
|
264
|
+
on: {
|
|
265
|
+
playerSpotted: {
|
|
266
|
+
target: 'chase',
|
|
267
|
+
guard: () => this.canSeePlayer()
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
chase: {
|
|
272
|
+
enter: () => this.playAlertSound(),
|
|
273
|
+
update: (dt) => this.moveTowardPlayer(dt),
|
|
274
|
+
on: {
|
|
275
|
+
playerLost: 'patrol',
|
|
276
|
+
inRange: 'attack'
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
attack: {
|
|
280
|
+
enter: () => this.startAttackAnimation(),
|
|
281
|
+
duration: 0.5,
|
|
282
|
+
next: 'cooldown',
|
|
283
|
+
update: () => this.performAttack()
|
|
284
|
+
},
|
|
285
|
+
cooldown: {
|
|
286
|
+
duration: 1,
|
|
287
|
+
next: 'chase'
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## API Reference
|
|
298
|
+
|
|
299
|
+
### State Checking
|
|
300
|
+
|
|
301
|
+
```js
|
|
302
|
+
// Check current state
|
|
303
|
+
if (fsm.is('playing')) { }
|
|
304
|
+
|
|
305
|
+
// Check multiple states
|
|
306
|
+
if (fsm.isAny('playing', 'boss_fight')) { }
|
|
307
|
+
|
|
308
|
+
// Get current state name
|
|
309
|
+
const stateName = fsm.state;
|
|
310
|
+
|
|
311
|
+
// Get previous state
|
|
312
|
+
const prevState = fsm.previousState;
|
|
313
|
+
|
|
314
|
+
// Get current state config
|
|
315
|
+
const config = fsm.currentStateConfig;
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Transitions
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
// Direct state change
|
|
322
|
+
fsm.setState('newState');
|
|
323
|
+
|
|
324
|
+
// With data passed to enter/exit callbacks
|
|
325
|
+
fsm.setState('newState', { reason: 'player_action' });
|
|
326
|
+
|
|
327
|
+
// Event-based transition (checks 'on' config)
|
|
328
|
+
fsm.trigger('eventName');
|
|
329
|
+
fsm.trigger('eventName', { data: 'value' });
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Timed State Info
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
// Time spent in current state
|
|
336
|
+
const time = fsm.stateTime;
|
|
337
|
+
|
|
338
|
+
// Progress through timed state (0-1)
|
|
339
|
+
const progress = fsm.progress;
|
|
340
|
+
|
|
341
|
+
// Remaining time in timed state
|
|
342
|
+
const remaining = fsm.remaining;
|
|
343
|
+
|
|
344
|
+
// Is current state timed?
|
|
345
|
+
if (fsm.isTimed) { }
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Control
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
// Pause/resume state machine
|
|
352
|
+
fsm.pause();
|
|
353
|
+
fsm.resume();
|
|
354
|
+
|
|
355
|
+
// Reset to initial or specific state
|
|
356
|
+
fsm.reset();
|
|
357
|
+
fsm.reset('specificState');
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Dynamic States
|
|
361
|
+
|
|
362
|
+
```js
|
|
363
|
+
// Add state at runtime
|
|
364
|
+
fsm.addState('newState', {
|
|
365
|
+
enter: () => { },
|
|
366
|
+
duration: 1,
|
|
367
|
+
next: 'idle'
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Remove state
|
|
371
|
+
fsm.removeState('unusedState');
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Global Callbacks
|
|
375
|
+
|
|
376
|
+
```js
|
|
377
|
+
// Listen to all state changes
|
|
378
|
+
fsm.onStateChange = (newState, oldState, data) => {
|
|
379
|
+
console.log(`${oldState} → ${newState}`);
|
|
380
|
+
};
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Method Reference
|
|
384
|
+
|
|
385
|
+
| Method | Returns | Description |
|
|
386
|
+
|--------|---------|-------------|
|
|
387
|
+
| `is(state)` | `boolean` | Check if in specific state |
|
|
388
|
+
| `isAny(...states)` | `boolean` | Check if in any of states |
|
|
389
|
+
| `setState(state, data?)` | `boolean` | Transition to state |
|
|
390
|
+
| `trigger(event, data?)` | `boolean` | Trigger event-based transition |
|
|
391
|
+
| `update(dt)` | `void` | Update state machine |
|
|
392
|
+
| `pause()` | `void` | Pause updates |
|
|
393
|
+
| `resume()` | `void` | Resume updates |
|
|
394
|
+
| `reset(state?)` | `void` | Reset to initial/specific state |
|
|
395
|
+
| `addState(name, config)` | `this` | Add state dynamically |
|
|
396
|
+
| `removeState(name)` | `this` | Remove state |
|
|
397
|
+
|
|
398
|
+
### Properties
|
|
399
|
+
|
|
400
|
+
| Property | Type | Description |
|
|
401
|
+
|----------|------|-------------|
|
|
402
|
+
| `state` | `string` | Current state name |
|
|
403
|
+
| `previousState` | `string` | Previous state name |
|
|
404
|
+
| `stateTime` | `number` | Time in current state |
|
|
405
|
+
| `progress` | `number` | Progress through timed state (0-1) |
|
|
406
|
+
| `remaining` | `number` | Time remaining in timed state |
|
|
407
|
+
| `isTimed` | `boolean` | Does current state have duration? |
|
|
408
|
+
| `paused` | `boolean` | Is state machine paused? |
|
|
409
|
+
| `context` | `Object` | Context for callbacks |
|
|
410
|
+
|
|
411
|
+
### Static Methods
|
|
412
|
+
|
|
413
|
+
| Method | Returns | Description |
|
|
414
|
+
|--------|---------|-------------|
|
|
415
|
+
| `StateMachine.fromSequence(phases, options?)` | `StateMachine` | Create from phase array |
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Complete Example
|
|
420
|
+
|
|
421
|
+
A complete entity with states, phases, and game integration:
|
|
422
|
+
|
|
423
|
+
```js
|
|
424
|
+
import { GameObject, StateMachine } from 'gcanvas';
|
|
425
|
+
|
|
426
|
+
class Boss extends GameObject {
|
|
427
|
+
constructor(game) {
|
|
428
|
+
super(game);
|
|
429
|
+
|
|
430
|
+
this.health = 100;
|
|
431
|
+
this.phase = 1;
|
|
432
|
+
|
|
433
|
+
// Boss behavior state machine
|
|
434
|
+
this.fsm = new StateMachine({
|
|
435
|
+
initial: 'entering',
|
|
436
|
+
context: this,
|
|
437
|
+
states: {
|
|
438
|
+
entering: {
|
|
439
|
+
enter: () => this.playEntranceAnimation(),
|
|
440
|
+
duration: 2,
|
|
441
|
+
next: 'idle'
|
|
442
|
+
},
|
|
443
|
+
idle: {
|
|
444
|
+
duration: 1,
|
|
445
|
+
update: (dt) => this.trackPlayer(dt),
|
|
446
|
+
on: {
|
|
447
|
+
attack: 'attacking'
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
attacking: {
|
|
451
|
+
enter: () => this.chooseAttack(),
|
|
452
|
+
duration: 1.5,
|
|
453
|
+
next: 'cooldown'
|
|
454
|
+
},
|
|
455
|
+
cooldown: {
|
|
456
|
+
duration: 0.5,
|
|
457
|
+
next: 'idle'
|
|
458
|
+
},
|
|
459
|
+
enraged: {
|
|
460
|
+
enter: () => {
|
|
461
|
+
this.speed *= 1.5;
|
|
462
|
+
this.playEnrageAnimation();
|
|
463
|
+
},
|
|
464
|
+
update: (dt) => this.aggressiveAttack(dt)
|
|
465
|
+
},
|
|
466
|
+
dying: {
|
|
467
|
+
enter: () => this.playDeathAnimation(),
|
|
468
|
+
duration: 3,
|
|
469
|
+
exit: () => this.game.events.emit('boss-defeated')
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Attack phase state machine (separate concern)
|
|
475
|
+
this.attackFsm = null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
update(dt) {
|
|
479
|
+
this.fsm.update(dt);
|
|
480
|
+
this.attackFsm?.update(dt);
|
|
481
|
+
|
|
482
|
+
// Trigger attack periodically when idle
|
|
483
|
+
if (this.fsm.is('idle') && this.fsm.stateTime > 0.5) {
|
|
484
|
+
this.fsm.trigger('attack');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Check for phase transition
|
|
488
|
+
if (this.health < 50 && this.phase === 1) {
|
|
489
|
+
this.phase = 2;
|
|
490
|
+
this.fsm.setState('enraged');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
takeDamage(amount) {
|
|
495
|
+
this.health -= amount;
|
|
496
|
+
|
|
497
|
+
if (this.health <= 0) {
|
|
498
|
+
this.fsm.setState('dying');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
chooseAttack() {
|
|
503
|
+
// Create timed attack sequence
|
|
504
|
+
this.attackFsm = StateMachine.fromSequence([
|
|
505
|
+
{ name: 'windup', duration: 0.3, enter: () => this.showWarning() },
|
|
506
|
+
{ name: 'strike', duration: 0.1, enter: () => this.dealDamage() },
|
|
507
|
+
{ name: 'recovery', duration: 0.4 }
|
|
508
|
+
], { context: this });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Architecture
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
519
|
+
│ StateMachine │
|
|
520
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
521
|
+
│ │ State Definitions │ │
|
|
522
|
+
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
|
|
523
|
+
│ │ │ idle │ │ moving │ │ attack │ │ dying │ │ │
|
|
524
|
+
│ │ │ enter │ │ enter │ │ enter │ │ enter │ │ │
|
|
525
|
+
│ │ │ update │ │ update │ │ update │ │ update │ │ │
|
|
526
|
+
│ │ │ exit │ │ exit │ │ exit │ │ exit │ │ │
|
|
527
|
+
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │
|
|
528
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
529
|
+
│ │ │
|
|
530
|
+
│ ▼ │
|
|
531
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
532
|
+
│ │ Current State Tracking │ │
|
|
533
|
+
│ │ state: 'idle' │ stateTime: 1.5 │ progress: 0.75 │ │
|
|
534
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
535
|
+
│ │ │
|
|
536
|
+
│ ▼ │
|
|
537
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
538
|
+
│ │ Transitions │ │
|
|
539
|
+
│ │ setState('moving') │ trigger('damage') │ │
|
|
540
|
+
│ │ Auto: duration → next │ │
|
|
541
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
542
|
+
└─────────────────────────────────────────────────────────────┘
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Tips & Best Practices
|
|
548
|
+
|
|
549
|
+
1. **Separate concerns**: Use multiple state machines for different aspects (movement, attack, animation)
|
|
550
|
+
|
|
551
|
+
2. **Use context**: Pass `context: this` to access entity properties in callbacks
|
|
552
|
+
|
|
553
|
+
3. **Guard transitions**: Use guards to prevent invalid state changes
|
|
554
|
+
```js
|
|
555
|
+
on: {
|
|
556
|
+
attack: {
|
|
557
|
+
target: 'attacking',
|
|
558
|
+
guard: () => this.stamina > 0
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
4. **Progress for animations**: Use `fsm.progress` for smooth timed effects
|
|
564
|
+
|
|
565
|
+
5. **fromSequence for phases**: Use the factory for simple linear sequences
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## Related
|
|
570
|
+
|
|
571
|
+
- [Collision Module](../collision/README.md) - Collision detection
|
|
572
|
+
- [Game Module](../game/README.md) - Game loop and GameObjects
|
|
573
|
+
|
|
574
|
+
## See Also
|
|
575
|
+
|
|
576
|
+
- [Motion Module](../motion/README.md) - Animation patterns
|
|
577
|
+
- [Game Lifecycle](../../concepts/lifecycle.md) - Update/render cycle
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Util Module
|
|
2
|
+
|
|
3
|
+
> Utilities for 3D projection, layout, positioning, and more.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The util module provides helper classes and functions for common tasks:
|
|
8
|
+
|
|
9
|
+
| Utility | Description |
|
|
10
|
+
|---------|-------------|
|
|
11
|
+
| [**Camera3D**](./camera3d.md) | Pseudo-3D projection with mouse controls |
|
|
12
|
+
| [**Scene3D**](./scene3d.md) | Scene with automatic 3D projection |
|
|
13
|
+
| **Layout** | Vertical, horizontal, and grid layouts |
|
|
14
|
+
| **Position** | Anchor constants and positioning |
|
|
15
|
+
|
|
16
|
+
## Quick Navigation
|
|
17
|
+
|
|
18
|
+
- [Camera3D](./camera3d.md) - 3D to 2D projection, rotation, mouse control
|
|
19
|
+
- [Scene3D](./scene3d.md) - Scene that projects children through Camera3D
|
|
20
|
+
|
|
21
|
+
## 3D System Overview
|
|
22
|
+
|
|
23
|
+
GCanvas provides a pseudo-3D system that projects 3D coordinates to 2D canvas:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
27
|
+
│ Camera3D │
|
|
28
|
+
│ • Rotation (X, Y, Z axes) │
|
|
29
|
+
│ • Perspective projection │
|
|
30
|
+
│ • Mouse/touch rotation control │
|
|
31
|
+
│ • Auto-rotation │
|
|
32
|
+
└─────────────────────────────────────────────────────────────┘
|
|
33
|
+
│
|
|
34
|
+
▼
|
|
35
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ Scene3D │
|
|
37
|
+
│ • Container for GameObjects with z-coordinates │
|
|
38
|
+
│ • Automatic projection through camera │
|
|
39
|
+
│ • Depth sorting (back-to-front) │
|
|
40
|
+
│ • Perspective scaling │
|
|
41
|
+
└─────────────────────────────────────────────────────────────┘
|
|
42
|
+
│
|
|
43
|
+
▼
|
|
44
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
45
|
+
│ ParticleSystem │
|
|
46
|
+
│ • 3D particle effects │
|
|
47
|
+
│ • Camera integration │
|
|
48
|
+
│ • Depth-sorted rendering │
|
|
49
|
+
└─────────────────────────────────────────────────────────────┘
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Basic 3D Setup
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
import { Game, Camera3D, Scene3D, GameObject, Rectangle } from 'gcanvas';
|
|
56
|
+
|
|
57
|
+
class My3DDemo extends Game {
|
|
58
|
+
init() {
|
|
59
|
+
super.init();
|
|
60
|
+
|
|
61
|
+
// Create camera
|
|
62
|
+
this.camera = new Camera3D({
|
|
63
|
+
rotationX: 0.3,
|
|
64
|
+
perspective: 800,
|
|
65
|
+
autoRotate: true,
|
|
66
|
+
});
|
|
67
|
+
this.camera.enableMouseControl(this.canvas);
|
|
68
|
+
|
|
69
|
+
// Create 3D scene centered on screen
|
|
70
|
+
this.scene3d = new Scene3D(this, {
|
|
71
|
+
x: this.width / 2,
|
|
72
|
+
y: this.height / 2,
|
|
73
|
+
camera: this.camera,
|
|
74
|
+
depthSort: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Add objects with z coordinates
|
|
78
|
+
for (let i = 0; i < 5; i++) {
|
|
79
|
+
const box = new BoxObject(this);
|
|
80
|
+
box.x = (i - 2) * 100;
|
|
81
|
+
box.z = Math.sin(i) * 100; // Vary depth
|
|
82
|
+
this.scene3d.add(box);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.pipeline.add(this.scene3d);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
update(dt) {
|
|
89
|
+
super.update(dt);
|
|
90
|
+
this.camera.update(dt);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Related
|
|
96
|
+
|
|
97
|
+
- [Camera3D](./camera3d.md) - Full camera documentation
|
|
98
|
+
- [Scene3D](./scene3d.md) - Full scene documentation
|
|
99
|
+
- [Particle Module](../particle/README.md) - 3D particle effects
|