@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,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StateMachine - Flexible state management for game objects
|
|
3
|
+
*
|
|
4
|
+
* Manages states with enter/update/exit lifecycle callbacks.
|
|
5
|
+
* Supports timed transitions, sequential phases, and conditional triggers.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Basic usage
|
|
9
|
+
* const fsm = new StateMachine({
|
|
10
|
+
* initial: 'idle',
|
|
11
|
+
* states: {
|
|
12
|
+
* idle: {
|
|
13
|
+
* enter: () => console.log('Now idle'),
|
|
14
|
+
* update: (dt) => { /* per-frame logic *\/ },
|
|
15
|
+
* exit: () => console.log('Leaving idle')
|
|
16
|
+
* },
|
|
17
|
+
* walking: {
|
|
18
|
+
* enter: () => player.startWalkAnimation(),
|
|
19
|
+
* update: (dt) => player.move(dt)
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Change state
|
|
25
|
+
* fsm.setState('walking');
|
|
26
|
+
*
|
|
27
|
+
* // Update each frame
|
|
28
|
+
* fsm.update(dt);
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Timed phase sequence (like laser charging)
|
|
32
|
+
* const laser = new StateMachine({
|
|
33
|
+
* initial: 'warning',
|
|
34
|
+
* states: {
|
|
35
|
+
* warning: { duration: 0.3, next: 'charging' },
|
|
36
|
+
* charging: { duration: 0.2, next: 'active' },
|
|
37
|
+
* active: {
|
|
38
|
+
* duration: 0.4,
|
|
39
|
+
* next: 'fade',
|
|
40
|
+
* enter: () => { laser.canDamage = true; }
|
|
41
|
+
* },
|
|
42
|
+
* fade: {
|
|
43
|
+
* duration: 0.2,
|
|
44
|
+
* exit: () => { laser.destroy(); }
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* });
|
|
48
|
+
*/
|
|
49
|
+
export class StateMachine {
|
|
50
|
+
/**
|
|
51
|
+
* Create a new state machine
|
|
52
|
+
*
|
|
53
|
+
* @param {Object} config - Configuration object
|
|
54
|
+
* @param {string} config.initial - Initial state name
|
|
55
|
+
* @param {Object} config.states - State definitions
|
|
56
|
+
* @param {Object} [config.context] - Context object passed to callbacks (usually `this`)
|
|
57
|
+
*/
|
|
58
|
+
constructor(config = {}) {
|
|
59
|
+
/** @type {Object} State definitions */
|
|
60
|
+
this.states = config.states || {};
|
|
61
|
+
|
|
62
|
+
/** @type {string|null} Current state name */
|
|
63
|
+
this.currentState = null;
|
|
64
|
+
|
|
65
|
+
/** @type {string|null} Previous state name */
|
|
66
|
+
this.previousState = null;
|
|
67
|
+
|
|
68
|
+
/** @type {number} Time spent in current state */
|
|
69
|
+
this.stateTime = 0;
|
|
70
|
+
|
|
71
|
+
/** @type {Object} Context passed to state callbacks */
|
|
72
|
+
this.context = config.context || null;
|
|
73
|
+
|
|
74
|
+
/** @type {boolean} Whether the state machine is paused */
|
|
75
|
+
this.paused = false;
|
|
76
|
+
|
|
77
|
+
/** @type {Function|null} Global state change callback */
|
|
78
|
+
this.onStateChange = null;
|
|
79
|
+
|
|
80
|
+
// Enter initial state
|
|
81
|
+
if (config.initial) {
|
|
82
|
+
this.setState(config.initial);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the current state name
|
|
88
|
+
* @returns {string|null}
|
|
89
|
+
*/
|
|
90
|
+
get state() {
|
|
91
|
+
return this.currentState;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the current state definition
|
|
96
|
+
* @returns {Object|null}
|
|
97
|
+
*/
|
|
98
|
+
get currentStateConfig() {
|
|
99
|
+
return this.currentState ? this.states[this.currentState] : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if currently in a specific state
|
|
104
|
+
*
|
|
105
|
+
* @param {string} stateName - State to check
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
is(stateName) {
|
|
109
|
+
return this.currentState === stateName;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if currently in any of the given states
|
|
114
|
+
*
|
|
115
|
+
* @param {...string} stateNames - States to check
|
|
116
|
+
* @returns {boolean}
|
|
117
|
+
*/
|
|
118
|
+
isAny(...stateNames) {
|
|
119
|
+
return stateNames.includes(this.currentState);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Add or update a state definition
|
|
124
|
+
*
|
|
125
|
+
* @param {string} name - State name
|
|
126
|
+
* @param {Object} config - State configuration
|
|
127
|
+
* @returns {StateMachine} this for chaining
|
|
128
|
+
*/
|
|
129
|
+
addState(name, config) {
|
|
130
|
+
this.states[name] = config;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Remove a state definition
|
|
136
|
+
*
|
|
137
|
+
* @param {string} name - State name to remove
|
|
138
|
+
* @returns {StateMachine} this for chaining
|
|
139
|
+
*/
|
|
140
|
+
removeState(name) {
|
|
141
|
+
delete this.states[name];
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Transition to a new state
|
|
147
|
+
*
|
|
148
|
+
* @param {string} newState - State to transition to
|
|
149
|
+
* @param {Object} [data] - Optional data passed to enter callback
|
|
150
|
+
* @returns {boolean} True if transition occurred
|
|
151
|
+
*/
|
|
152
|
+
setState(newState, data) {
|
|
153
|
+
if (!this.states[newState]) {
|
|
154
|
+
console.warn(`StateMachine: Unknown state '${newState}'`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Exit current state
|
|
159
|
+
if (this.currentState) {
|
|
160
|
+
const currentConfig = this.states[this.currentState];
|
|
161
|
+
if (currentConfig?.exit) {
|
|
162
|
+
this._call(currentConfig.exit, data);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Update state tracking
|
|
167
|
+
this.previousState = this.currentState;
|
|
168
|
+
this.currentState = newState;
|
|
169
|
+
this.stateTime = 0;
|
|
170
|
+
|
|
171
|
+
// Enter new state
|
|
172
|
+
const newConfig = this.states[newState];
|
|
173
|
+
if (newConfig?.enter) {
|
|
174
|
+
this._call(newConfig.enter, data);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Global callback
|
|
178
|
+
if (this.onStateChange) {
|
|
179
|
+
this.onStateChange(newState, this.previousState, data);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Attempt to transition based on a trigger/event
|
|
187
|
+
*
|
|
188
|
+
* @param {string} trigger - Trigger name to check against state transitions
|
|
189
|
+
* @param {Object} [data] - Optional data passed to callbacks
|
|
190
|
+
* @returns {boolean} True if a transition occurred
|
|
191
|
+
*/
|
|
192
|
+
trigger(trigger, data) {
|
|
193
|
+
const config = this.currentStateConfig;
|
|
194
|
+
if (!config?.on) return false;
|
|
195
|
+
|
|
196
|
+
const transition = config.on[trigger];
|
|
197
|
+
if (!transition) return false;
|
|
198
|
+
|
|
199
|
+
// Transition can be a string (state name) or object { target, guard, action }
|
|
200
|
+
if (typeof transition === "string") {
|
|
201
|
+
return this.setState(transition, data);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check guard condition
|
|
205
|
+
if (transition.guard && !this._call(transition.guard, data)) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Run action before transition
|
|
210
|
+
if (transition.action) {
|
|
211
|
+
this._call(transition.action, data);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Transition to target state
|
|
215
|
+
if (transition.target) {
|
|
216
|
+
return this.setState(transition.target, data);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Update the state machine (call each frame)
|
|
224
|
+
*
|
|
225
|
+
* @param {number} dt - Delta time in seconds
|
|
226
|
+
*/
|
|
227
|
+
update(dt) {
|
|
228
|
+
if (this.paused || !this.currentState) return;
|
|
229
|
+
|
|
230
|
+
this.stateTime += dt;
|
|
231
|
+
|
|
232
|
+
const config = this.states[this.currentState];
|
|
233
|
+
if (!config) return;
|
|
234
|
+
|
|
235
|
+
// Run update callback
|
|
236
|
+
if (config.update) {
|
|
237
|
+
this._call(config.update, dt);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for timed auto-transition
|
|
241
|
+
if (config.duration !== undefined && this.stateTime >= config.duration) {
|
|
242
|
+
if (config.next) {
|
|
243
|
+
this.setState(config.next);
|
|
244
|
+
} else if (config.onComplete) {
|
|
245
|
+
this._call(config.onComplete);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Get normalized progress through current state (0-1)
|
|
252
|
+
* Only meaningful for states with a duration
|
|
253
|
+
*
|
|
254
|
+
* @returns {number} Progress from 0 to 1
|
|
255
|
+
*/
|
|
256
|
+
get progress() {
|
|
257
|
+
const config = this.currentStateConfig;
|
|
258
|
+
if (!config?.duration) return 0;
|
|
259
|
+
return Math.min(1, this.stateTime / config.duration);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get remaining time in current state
|
|
264
|
+
* Only meaningful for states with a duration
|
|
265
|
+
*
|
|
266
|
+
* @returns {number} Remaining time in seconds
|
|
267
|
+
*/
|
|
268
|
+
get remaining() {
|
|
269
|
+
const config = this.currentStateConfig;
|
|
270
|
+
if (!config?.duration) return Infinity;
|
|
271
|
+
return Math.max(0, config.duration - this.stateTime);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if current state is a timed state
|
|
276
|
+
*
|
|
277
|
+
* @returns {boolean}
|
|
278
|
+
*/
|
|
279
|
+
get isTimed() {
|
|
280
|
+
return this.currentStateConfig?.duration !== undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Pause the state machine (stops update processing)
|
|
285
|
+
*/
|
|
286
|
+
pause() {
|
|
287
|
+
this.paused = true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Resume the state machine
|
|
292
|
+
*/
|
|
293
|
+
resume() {
|
|
294
|
+
this.paused = false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Reset to initial state or specific state
|
|
299
|
+
*
|
|
300
|
+
* @param {string} [state] - State to reset to (defaults to first defined state)
|
|
301
|
+
*/
|
|
302
|
+
reset(state) {
|
|
303
|
+
this.stateTime = 0;
|
|
304
|
+
this.previousState = null;
|
|
305
|
+
|
|
306
|
+
if (state) {
|
|
307
|
+
this.setState(state);
|
|
308
|
+
} else {
|
|
309
|
+
// Find first state key
|
|
310
|
+
const firstState = Object.keys(this.states)[0];
|
|
311
|
+
if (firstState) {
|
|
312
|
+
this.setState(firstState);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Call a callback with context binding
|
|
319
|
+
* @private
|
|
320
|
+
*/
|
|
321
|
+
_call(fn, ...args) {
|
|
322
|
+
if (typeof fn === "function") {
|
|
323
|
+
return this.context ? fn.call(this.context, ...args) : fn(...args);
|
|
324
|
+
}
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Create a state machine from a simple phase sequence
|
|
330
|
+
* Convenience method for common "phase 1 → phase 2 → phase 3" patterns
|
|
331
|
+
*
|
|
332
|
+
* @param {Array<Object>} phases - Array of { name, duration, enter?, update?, exit? }
|
|
333
|
+
* @param {Object} [options] - Additional options
|
|
334
|
+
* @param {boolean} [options.loop=false] - Whether to loop back to first phase
|
|
335
|
+
* @param {Function} [options.onComplete] - Called when sequence completes (if not looping)
|
|
336
|
+
* @returns {StateMachine}
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* const lightning = StateMachine.fromSequence([
|
|
340
|
+
* { name: 'tracing', duration: 0.4, enter: () => startTrace() },
|
|
341
|
+
* { name: 'active', duration: 0.3, enter: () => enableDamage() },
|
|
342
|
+
* { name: 'fade', duration: 0.2, exit: () => destroy() }
|
|
343
|
+
* ]);
|
|
344
|
+
*/
|
|
345
|
+
static fromSequence(phases, options = {}) {
|
|
346
|
+
const states = {};
|
|
347
|
+
|
|
348
|
+
for (let i = 0; i < phases.length; i++) {
|
|
349
|
+
const phase = phases[i];
|
|
350
|
+
const isLast = i === phases.length - 1;
|
|
351
|
+
const nextPhase = isLast
|
|
352
|
+
? (options.loop ? phases[0].name : null)
|
|
353
|
+
: phases[i + 1].name;
|
|
354
|
+
|
|
355
|
+
states[phase.name] = {
|
|
356
|
+
duration: phase.duration,
|
|
357
|
+
next: nextPhase,
|
|
358
|
+
enter: phase.enter,
|
|
359
|
+
update: phase.update,
|
|
360
|
+
exit: phase.exit,
|
|
361
|
+
onComplete: isLast && !options.loop ? options.onComplete : undefined,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return new StateMachine({
|
|
366
|
+
initial: phases[0]?.name,
|
|
367
|
+
states,
|
|
368
|
+
context: options.context,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|