@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,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module FluentGame
|
|
3
|
+
* @description Root builder class for the fluent API, wrapping the Game class
|
|
4
|
+
*
|
|
5
|
+
* This provides a declarative, chainable API for creating GCanvas games
|
|
6
|
+
* with less boilerplate than the traditional class-based approach.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { gcanvas } from 'gcanvas';
|
|
10
|
+
*
|
|
11
|
+
* gcanvas({ bg: 'black' })
|
|
12
|
+
* .scene('game')
|
|
13
|
+
* .go({ x: 400, y: 300, name: 'player' })
|
|
14
|
+
* .circle({ radius: 30, fill: 'lime' })
|
|
15
|
+
* .start();
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Game } from "../game/game.js";
|
|
19
|
+
import { Scene } from "../game/objects/scene.js";
|
|
20
|
+
import { FluentScene } from "./fluent-scene.js";
|
|
21
|
+
import { FluentGO } from "./fluent-go.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Main entry point for the fluent API
|
|
25
|
+
* @param {Object} options - Game configuration
|
|
26
|
+
* @returns {FluentGame}
|
|
27
|
+
*/
|
|
28
|
+
export function gcanvas(options = {}) {
|
|
29
|
+
return new FluentGame(options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* FluentGame - Root builder class wrapping Game
|
|
34
|
+
*/
|
|
35
|
+
export class FluentGame {
|
|
36
|
+
/** @type {Game} */
|
|
37
|
+
#game;
|
|
38
|
+
/** @type {Map<string, Scene>} */
|
|
39
|
+
#scenes = new Map();
|
|
40
|
+
/** @type {Scene|null} */
|
|
41
|
+
#currentScene = null;
|
|
42
|
+
/** @type {Object} */
|
|
43
|
+
#refs = {};
|
|
44
|
+
/** @type {Object} */
|
|
45
|
+
#state = {};
|
|
46
|
+
/** @type {Array} */
|
|
47
|
+
#plugins = [];
|
|
48
|
+
/** @type {HTMLCanvasElement|null} */
|
|
49
|
+
#canvas = null;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Object} options - Game configuration
|
|
53
|
+
* @param {HTMLCanvasElement} [options.canvas] - Canvas element to use
|
|
54
|
+
* @param {number} [options.width=800] - Canvas width (if auto-creating)
|
|
55
|
+
* @param {number} [options.height=600] - Canvas height (if auto-creating)
|
|
56
|
+
* @param {string} [options.bg] - Background color
|
|
57
|
+
* @param {boolean} [options.fluid=true] - Enable fluid/responsive sizing
|
|
58
|
+
* @param {HTMLElement} [options.container=document.body] - Container for auto-created canvas
|
|
59
|
+
* @param {number} [options.fps=60] - Target FPS
|
|
60
|
+
* @param {number} [options.pixelRatio=window.devicePixelRatio] - Pixel ratio for HiDPI
|
|
61
|
+
*/
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
const {
|
|
64
|
+
canvas,
|
|
65
|
+
width = 800,
|
|
66
|
+
height = 600,
|
|
67
|
+
bg,
|
|
68
|
+
fluid, // Default determined below based on canvas presence
|
|
69
|
+
container,
|
|
70
|
+
fps = 60,
|
|
71
|
+
pixelRatio = typeof window !== 'undefined' ? window.devicePixelRatio : 1
|
|
72
|
+
} = options;
|
|
73
|
+
|
|
74
|
+
// Create or use provided canvas
|
|
75
|
+
this.#canvas = canvas || this.#createCanvas(width, height, container);
|
|
76
|
+
this.#game = new Game(this.#canvas);
|
|
77
|
+
|
|
78
|
+
// Initialize the game
|
|
79
|
+
this.#game.init();
|
|
80
|
+
|
|
81
|
+
// Apply options
|
|
82
|
+
if (bg) this.#game.backgroundColor = bg;
|
|
83
|
+
// Only enable fluid sizing if explicitly requested, or if we auto-created the canvas
|
|
84
|
+
// When user provides their own canvas, default to respecting its dimensions
|
|
85
|
+
const shouldBeFluid = fluid !== undefined ? fluid : !canvas;
|
|
86
|
+
if (shouldBeFluid) this.#game.enableFluidSize();
|
|
87
|
+
if (fps !== 60) this.#game.targetFPS = fps;
|
|
88
|
+
|
|
89
|
+
// Store pixel ratio for shapes that need it
|
|
90
|
+
this.#game._pixelRatio = pixelRatio;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create a canvas element
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
#createCanvas(width, height, container) {
|
|
98
|
+
const canvas = document.createElement('canvas');
|
|
99
|
+
canvas.width = width;
|
|
100
|
+
canvas.height = height;
|
|
101
|
+
canvas.style.display = 'block';
|
|
102
|
+
|
|
103
|
+
const target = container || document.body;
|
|
104
|
+
target.appendChild(canvas);
|
|
105
|
+
|
|
106
|
+
return canvas;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─────────────────────────────────────────────────────────
|
|
110
|
+
// SCENE MANAGEMENT
|
|
111
|
+
// ─────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create or switch to a scene
|
|
115
|
+
*
|
|
116
|
+
* Supports multiple signatures:
|
|
117
|
+
* - scene('name') - Create/switch to named scene
|
|
118
|
+
* - scene('name', options) - Create with options
|
|
119
|
+
* - scene('name', CustomSceneClass) - Create using custom class
|
|
120
|
+
* - scene('name', CustomSceneClass, options) - Custom class with options
|
|
121
|
+
* - scene(CustomSceneClass) - Custom class, name derived from class
|
|
122
|
+
* - scene(CustomSceneClass, options) - Custom class with options
|
|
123
|
+
*
|
|
124
|
+
* @param {string|Function} nameOrClass - Scene identifier or custom Scene class
|
|
125
|
+
* @param {Object|Function} [optionsOrClass] - Scene options or custom Scene class
|
|
126
|
+
* @param {Object} [options] - Scene options (when class is second arg)
|
|
127
|
+
* @param {number} [options.zIndex=0] - Scene z-index
|
|
128
|
+
* @param {boolean} [options.active=true] - Whether scene is visible
|
|
129
|
+
* @param {Function} [options.onEnter] - Scene enter callback
|
|
130
|
+
* @param {Function} [options.onExit] - Scene exit callback
|
|
131
|
+
* @returns {FluentScene}
|
|
132
|
+
*/
|
|
133
|
+
scene(nameOrClass, optionsOrClass, options) {
|
|
134
|
+
// Parse flexible arguments
|
|
135
|
+
let name, SceneClass, opts;
|
|
136
|
+
|
|
137
|
+
if (typeof nameOrClass === 'function') {
|
|
138
|
+
// scene(CustomClass) or scene(CustomClass, options)
|
|
139
|
+
SceneClass = nameOrClass;
|
|
140
|
+
name = SceneClass.name || 'custom_scene';
|
|
141
|
+
opts = optionsOrClass || {};
|
|
142
|
+
} else if (typeof optionsOrClass === 'function') {
|
|
143
|
+
// scene('name', CustomClass) or scene('name', CustomClass, options)
|
|
144
|
+
name = nameOrClass;
|
|
145
|
+
SceneClass = optionsOrClass;
|
|
146
|
+
opts = options || {};
|
|
147
|
+
} else {
|
|
148
|
+
// scene('name') or scene('name', options)
|
|
149
|
+
name = nameOrClass;
|
|
150
|
+
SceneClass = Scene;
|
|
151
|
+
opts = optionsOrClass || {};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const {
|
|
155
|
+
zIndex = 0,
|
|
156
|
+
active = true,
|
|
157
|
+
onEnter,
|
|
158
|
+
onExit,
|
|
159
|
+
...customOpts
|
|
160
|
+
} = opts;
|
|
161
|
+
|
|
162
|
+
let scene = this.#scenes.get(name);
|
|
163
|
+
|
|
164
|
+
if (!scene) {
|
|
165
|
+
// Instantiate the scene class (custom or default)
|
|
166
|
+
scene = new SceneClass(this.#game, customOpts);
|
|
167
|
+
scene.name = name;
|
|
168
|
+
scene.zIndex = zIndex;
|
|
169
|
+
scene.visible = active;
|
|
170
|
+
scene._onEnter = onEnter;
|
|
171
|
+
scene._onExit = onExit;
|
|
172
|
+
this.#scenes.set(name, scene);
|
|
173
|
+
this.#game.pipeline.add(scene);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this.#currentScene = scene;
|
|
177
|
+
return new FluentScene(this, scene, this.#refs, this.#state);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Switch context to an existing scene (does not create)
|
|
182
|
+
* @param {string} name - Scene name
|
|
183
|
+
* @returns {FluentScene}
|
|
184
|
+
*/
|
|
185
|
+
inScene(name) {
|
|
186
|
+
const scene = this.#scenes.get(name);
|
|
187
|
+
if (!scene) {
|
|
188
|
+
throw new Error(`Scene '${name}' does not exist. Use .scene('${name}') to create it.`);
|
|
189
|
+
}
|
|
190
|
+
this.#currentScene = scene;
|
|
191
|
+
return new FluentScene(this, scene, this.#refs, this.#state);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Shortcut: create GO in current/default scene
|
|
196
|
+
*
|
|
197
|
+
* Supports same signatures as FluentScene.go():
|
|
198
|
+
* - go(options) - Plain GameObject with options
|
|
199
|
+
* - go(CustomClass) - Custom GameObject class
|
|
200
|
+
* - go(CustomClass, options) - Custom class with options
|
|
201
|
+
*
|
|
202
|
+
* @param {Object|Function} [optionsOrClass] - GameObject options or custom class
|
|
203
|
+
* @param {Object} [options] - Options when class is first arg
|
|
204
|
+
* @returns {FluentGO}
|
|
205
|
+
*/
|
|
206
|
+
go(optionsOrClass, options) {
|
|
207
|
+
if (!this.#currentScene) {
|
|
208
|
+
this.scene('default');
|
|
209
|
+
}
|
|
210
|
+
return new FluentScene(this, this.#currentScene, this.#refs, this.#state).go(optionsOrClass, options);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─────────────────────────────────────────────────────────
|
|
214
|
+
// SCENE VISIBILITY & TRANSITIONS
|
|
215
|
+
// ─────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Show a scene
|
|
219
|
+
* @param {string} name - Scene name
|
|
220
|
+
* @returns {FluentGame}
|
|
221
|
+
*/
|
|
222
|
+
showScene(name) {
|
|
223
|
+
const scene = this.#scenes.get(name);
|
|
224
|
+
if (scene) {
|
|
225
|
+
scene.visible = true;
|
|
226
|
+
scene._onEnter?.(this.#createContext());
|
|
227
|
+
}
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Hide a scene
|
|
233
|
+
* @param {string} name - Scene name
|
|
234
|
+
* @returns {FluentGame}
|
|
235
|
+
*/
|
|
236
|
+
hideScene(name) {
|
|
237
|
+
const scene = this.#scenes.get(name);
|
|
238
|
+
if (scene) {
|
|
239
|
+
scene._onExit?.(this.#createContext());
|
|
240
|
+
scene.visible = false;
|
|
241
|
+
}
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Transition between scenes
|
|
247
|
+
* @param {string} from - Source scene name
|
|
248
|
+
* @param {string} to - Target scene name
|
|
249
|
+
* @param {Object} [options] - Transition options
|
|
250
|
+
* @param {number} [options.fade=0] - Fade duration in seconds
|
|
251
|
+
* @param {Function} [options.onComplete] - Completion callback
|
|
252
|
+
* @returns {FluentGame}
|
|
253
|
+
*/
|
|
254
|
+
transition(from, to, options = {}) {
|
|
255
|
+
const { fade = 0, onComplete } = options;
|
|
256
|
+
|
|
257
|
+
// TODO: Implement fade transition using Tweenetik
|
|
258
|
+
this.hideScene(from);
|
|
259
|
+
this.showScene(to);
|
|
260
|
+
onComplete?.();
|
|
261
|
+
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─────────────────────────────────────────────────────────
|
|
266
|
+
// STATE MANAGEMENT
|
|
267
|
+
// ─────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Set initial state
|
|
271
|
+
* @param {Object} initialState
|
|
272
|
+
* @returns {FluentGame}
|
|
273
|
+
*/
|
|
274
|
+
state(initialState) {
|
|
275
|
+
Object.assign(this.#state, initialState);
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get a state value
|
|
281
|
+
* @param {string} key - State key
|
|
282
|
+
* @returns {*}
|
|
283
|
+
*/
|
|
284
|
+
getState(key) {
|
|
285
|
+
return this.#state[key];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Set a state value
|
|
290
|
+
* @param {string} key - State key
|
|
291
|
+
* @param {*} value - State value
|
|
292
|
+
* @returns {FluentGame}
|
|
293
|
+
*/
|
|
294
|
+
setState(key, value) {
|
|
295
|
+
this.#state[key] = value;
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ─────────────────────────────────────────────────────────
|
|
300
|
+
// EVENTS & LIFECYCLE
|
|
301
|
+
// ─────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Register event handler
|
|
305
|
+
* @param {string} event - Event name (update, keydown:escape, click, etc.)
|
|
306
|
+
* @param {Function} handler - Handler function receiving context
|
|
307
|
+
* @returns {FluentGame}
|
|
308
|
+
*/
|
|
309
|
+
on(event, handler) {
|
|
310
|
+
const ctx = this.#createContext();
|
|
311
|
+
|
|
312
|
+
if (event === 'update') {
|
|
313
|
+
// Hook into the game's update loop
|
|
314
|
+
const originalUpdate = this.#game.update.bind(this.#game);
|
|
315
|
+
this.#game.update = (dt) => {
|
|
316
|
+
originalUpdate(dt);
|
|
317
|
+
handler(dt, ctx);
|
|
318
|
+
};
|
|
319
|
+
} else if (event.startsWith('keydown:')) {
|
|
320
|
+
const key = event.split(':')[1];
|
|
321
|
+
this.#game.events.on('keydown', (e) => {
|
|
322
|
+
if (e.key?.toLowerCase() === key.toLowerCase() ||
|
|
323
|
+
e.code?.toLowerCase() === key.toLowerCase()) {
|
|
324
|
+
handler(ctx, e);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
} else if (event.startsWith('keyup:')) {
|
|
328
|
+
const key = event.split(':')[1];
|
|
329
|
+
this.#game.events.on('keyup', (e) => {
|
|
330
|
+
if (e.key?.toLowerCase() === key.toLowerCase()) {
|
|
331
|
+
handler(ctx, e);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
} else {
|
|
335
|
+
this.#game.events.on(event, (e) => handler(ctx, e));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return this;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ─────────────────────────────────────────────────────────
|
|
342
|
+
// PLUGINS & EXTENSIONS
|
|
343
|
+
// ─────────────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Use a plugin or scene builder function
|
|
347
|
+
* @param {Function} plugin - Plugin function receiving FluentGame
|
|
348
|
+
* @returns {FluentGame}
|
|
349
|
+
*/
|
|
350
|
+
use(plugin) {
|
|
351
|
+
plugin(this);
|
|
352
|
+
this.#plugins.push(plugin);
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ─────────────────────────────────────────────────────────
|
|
357
|
+
// LIFECYCLE
|
|
358
|
+
// ─────────────────────────────────────────────────────────
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Start the game loop
|
|
362
|
+
* @returns {FluentGame}
|
|
363
|
+
*/
|
|
364
|
+
start() {
|
|
365
|
+
this.#game.start();
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Stop the game loop
|
|
371
|
+
* @returns {FluentGame}
|
|
372
|
+
*/
|
|
373
|
+
stop() {
|
|
374
|
+
this.#game.stop();
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Restart the game
|
|
380
|
+
* @returns {FluentGame}
|
|
381
|
+
*/
|
|
382
|
+
restart() {
|
|
383
|
+
this.#game.restart();
|
|
384
|
+
return this;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ─────────────────────────────────────────────────────────
|
|
388
|
+
// ACCESSORS
|
|
389
|
+
// ─────────────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
/** @returns {Game} Underlying Game instance */
|
|
392
|
+
get game() { return this.#game; }
|
|
393
|
+
|
|
394
|
+
/** @returns {Object} Named object references */
|
|
395
|
+
get refs() { return this.#refs; }
|
|
396
|
+
|
|
397
|
+
/** @returns {Map<string, Scene>} All scenes */
|
|
398
|
+
get scenes() { return this.#scenes; }
|
|
399
|
+
|
|
400
|
+
/** @returns {HTMLCanvasElement} Canvas element */
|
|
401
|
+
get canvas() { return this.#canvas; }
|
|
402
|
+
|
|
403
|
+
/** @returns {number} Canvas width */
|
|
404
|
+
get width() { return this.#canvas.width; }
|
|
405
|
+
|
|
406
|
+
/** @returns {number} Canvas height */
|
|
407
|
+
get height() { return this.#canvas.height; }
|
|
408
|
+
|
|
409
|
+
// ─────────────────────────────────────────────────────────
|
|
410
|
+
// INTERNAL
|
|
411
|
+
// ─────────────────────────────────────────────────────────
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Create a context object for handlers
|
|
415
|
+
* @private
|
|
416
|
+
*/
|
|
417
|
+
#createContext() {
|
|
418
|
+
return {
|
|
419
|
+
refs: this.#refs,
|
|
420
|
+
state: this.#state,
|
|
421
|
+
scenes: Object.fromEntries(this.#scenes),
|
|
422
|
+
game: this.#game,
|
|
423
|
+
width: this.#canvas.width,
|
|
424
|
+
height: this.#canvas.height,
|
|
425
|
+
showScene: (name) => this.showScene(name),
|
|
426
|
+
hideScene: (name) => this.hideScene(name),
|
|
427
|
+
transition: (from, to, opts) => this.transition(from, to, opts)
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|