@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,220 @@
|
|
|
1
|
+
import { Transformable } from "../../shapes/transformable.js";
|
|
2
|
+
import { EventEmitter } from "../../io/index.js";
|
|
3
|
+
import { applyAnchor } from "../../mixins/anchor.js";
|
|
4
|
+
/**
|
|
5
|
+
* GameObject
|
|
6
|
+
* ----------
|
|
7
|
+
*
|
|
8
|
+
* A dynamic canvas entity with interactivity, state, and update/render lifecycle.
|
|
9
|
+
* Builds upon `Transformable` and adds:
|
|
10
|
+
* - Event handling via `EventEmitter`
|
|
11
|
+
* - Bounding box-based hit detection
|
|
12
|
+
* - Game instance linkage
|
|
13
|
+
* - Parent reference for scene graph traversal
|
|
14
|
+
*
|
|
15
|
+
* ### Modernized Interactivity
|
|
16
|
+
*
|
|
17
|
+
* Previous versions used a `Shape` for hit testing. This version uses
|
|
18
|
+
* the object's bounding box (`getBounds()`) for collision/interactivity.
|
|
19
|
+
*
|
|
20
|
+
* ### Event Model
|
|
21
|
+
* Events are managed via the internal emitter. Use:
|
|
22
|
+
* ```js
|
|
23
|
+
* obj.on("click", cb)
|
|
24
|
+
* obj.off("mouseover", cb)
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* ### Typical Usage
|
|
28
|
+
* ```js
|
|
29
|
+
* const box = new GameObject(game, { x: 100, y: 100, width: 200, height: 80 });
|
|
30
|
+
* box.on("click", () => console.log("clicked"));
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @abstract
|
|
34
|
+
* @extends Transformable
|
|
35
|
+
*/
|
|
36
|
+
export class GameObject extends Transformable {
|
|
37
|
+
/**
|
|
38
|
+
* @param {Game} game - Game instance reference
|
|
39
|
+
* @param {Object} [options={}] - Configuration and styling
|
|
40
|
+
*/
|
|
41
|
+
constructor(game, options = {}) {
|
|
42
|
+
super(options);
|
|
43
|
+
this.game = game;
|
|
44
|
+
this.parent = null;
|
|
45
|
+
/** @type {EventEmitter} */
|
|
46
|
+
this.events = new EventEmitter();
|
|
47
|
+
this._interactive = options.interactive ?? false;
|
|
48
|
+
this._hovered = false;
|
|
49
|
+
if (options.anchor) {
|
|
50
|
+
applyAnchor(this, options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
update(dt) {
|
|
55
|
+
this.logger.groupCollapsed(
|
|
56
|
+
"GameObject.update: " +
|
|
57
|
+
(this.name == undefined ? this.constructor.name : this.name)
|
|
58
|
+
);
|
|
59
|
+
super.update(dt);
|
|
60
|
+
this.logger.groupEnd();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Enable or disable hit-testing for this GameObject.
|
|
65
|
+
* When enabled, _hitTest() will run during interaction checks.
|
|
66
|
+
* @type {boolean}
|
|
67
|
+
*/
|
|
68
|
+
get interactive() {
|
|
69
|
+
return this._interactive;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
set interactive(value) {
|
|
73
|
+
const newValue = Boolean(value);
|
|
74
|
+
|
|
75
|
+
// Only proceed if there's an actual change
|
|
76
|
+
if (this._interactive !== newValue) {
|
|
77
|
+
// Store the new state
|
|
78
|
+
this._interactive = newValue;
|
|
79
|
+
|
|
80
|
+
if (newValue === true) {
|
|
81
|
+
// Object is becoming interactive
|
|
82
|
+
this._enableEvents();
|
|
83
|
+
} else {
|
|
84
|
+
// Object is becoming non-interactive
|
|
85
|
+
this._disableEvents();
|
|
86
|
+
|
|
87
|
+
// Also reset hover state if it was previously hovered
|
|
88
|
+
if (this._hovered) {
|
|
89
|
+
this._hovered = false;
|
|
90
|
+
this.events.emit("mouseout");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Enable event handling for this GameObject.
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
_enableEvents() {
|
|
101
|
+
// No need to create event handlers here
|
|
102
|
+
// The Pipeline class already handles dispatching events to interactive objects
|
|
103
|
+
// We just need to mark the object as interactive
|
|
104
|
+
this.logger.log(`${this.constructor.name} is now interactive`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Disable event handling for this GameObject.
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
_disableEvents() {
|
|
112
|
+
// Clean up any specific event state
|
|
113
|
+
this.logger.log(`${this.constructor.name} is no longer interactive`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* True if the pointer is currently hovering over the object.
|
|
118
|
+
* @type {boolean}
|
|
119
|
+
* @readonly
|
|
120
|
+
*/
|
|
121
|
+
get hovered() {
|
|
122
|
+
return this._hovered;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
set hovered(value) {
|
|
126
|
+
this._hovered = Boolean(value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Internal use by input system */
|
|
130
|
+
_setHovered(state) {
|
|
131
|
+
this._hovered = Boolean(state);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Test whether a given point lies inside the object's bounds,
|
|
136
|
+
* taking into account the full transformation hierarchy (position, rotation, scale).
|
|
137
|
+
*
|
|
138
|
+
* @param {number} x - X screen coordinate
|
|
139
|
+
* @param {number} y - Y screen coordinate
|
|
140
|
+
* @returns {boolean} True if the point is inside this object's bounds
|
|
141
|
+
*/
|
|
142
|
+
_hitTest(x, y) {
|
|
143
|
+
if (!this._interactive) return false;
|
|
144
|
+
|
|
145
|
+
const bounds = this.getBounds?.();
|
|
146
|
+
if (!bounds) return false;
|
|
147
|
+
|
|
148
|
+
// Transform point from screen space to object's local space
|
|
149
|
+
let localX = x;
|
|
150
|
+
let localY = y;
|
|
151
|
+
|
|
152
|
+
// Build transform chain (from root to this object)
|
|
153
|
+
const transformChain = [];
|
|
154
|
+
let current = this;
|
|
155
|
+
while (current) {
|
|
156
|
+
transformChain.unshift(current);
|
|
157
|
+
current = current.parent;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Apply inverse transforms in sequence (from root to object)
|
|
161
|
+
for (const obj of transformChain) {
|
|
162
|
+
// Translation: subtract object position
|
|
163
|
+
localX -= obj.x || 0;
|
|
164
|
+
localY -= obj.y || 0;
|
|
165
|
+
|
|
166
|
+
// Rotation: apply inverse rotation if needed
|
|
167
|
+
if (obj.rotation) {
|
|
168
|
+
const cos = Math.cos(-obj.rotation);
|
|
169
|
+
const sin = Math.sin(-obj.rotation);
|
|
170
|
+
const tempX = localX;
|
|
171
|
+
localX = tempX * cos - localY * sin;
|
|
172
|
+
localY = tempX * sin + localY * cos;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Scale: apply inverse scale if needed
|
|
176
|
+
if (obj.scaleX !== undefined && obj.scaleX !== 0) {
|
|
177
|
+
localX /= obj.scaleX;
|
|
178
|
+
}
|
|
179
|
+
if (obj.scaleY !== undefined && obj.scaleY !== 0) {
|
|
180
|
+
localY /= obj.scaleY;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Now check if the point is inside our local bounds
|
|
185
|
+
// Use bounds from getBounds() if available, otherwise fall back to this.width/height
|
|
186
|
+
const halfW = (bounds.width || this.width || 0) / 2;
|
|
187
|
+
const halfH = (bounds.height || this.height || 0) / 2;
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
localX >= -halfW && localX <= halfW && localY >= -halfH && localY <= halfH
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Attach an event handler.
|
|
196
|
+
* @param {string} event - Event name
|
|
197
|
+
* @param {Function} callback - Callback function
|
|
198
|
+
*/
|
|
199
|
+
on(event, callback) {
|
|
200
|
+
this.events.on(event, callback);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Remove an event handler.
|
|
205
|
+
* @param {string} event - Event name
|
|
206
|
+
* @param {Function} callback - Callback function
|
|
207
|
+
*/
|
|
208
|
+
off(event, callback) {
|
|
209
|
+
this.events.off(event, callback);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Dispatch an event manually.
|
|
214
|
+
* @param {string} event - Event name
|
|
215
|
+
* @param {...any} args - Event arguments
|
|
216
|
+
*/
|
|
217
|
+
emit(event, ...args) {
|
|
218
|
+
this.events.emit(event, ...args);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ImageShape } from "../../shapes/image";
|
|
2
|
+
import { GameObjectShapeWrapper } from ".";
|
|
3
|
+
|
|
4
|
+
export class ImageGo extends GameObjectShapeWrapper {
|
|
5
|
+
/**
|
|
6
|
+
* Quickly drop a bitmap into the pipeline.
|
|
7
|
+
*
|
|
8
|
+
* @param {Game} game – the main Game instance
|
|
9
|
+
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|Uint8ClampedArray} bitmap
|
|
10
|
+
* @param {object} [options] – BitmapShape & GO options (x, y, scale, anchor, …)
|
|
11
|
+
*
|
|
12
|
+
* The constructor:
|
|
13
|
+
* 1. creates / re‑uses a BitmapShape,
|
|
14
|
+
* 2. hands it to GameObjectShapeWrapper,
|
|
15
|
+
* 3. propagates any extra options (anchor, padding, debug …).
|
|
16
|
+
*/
|
|
17
|
+
constructor(game, bitmap, options = {}) {
|
|
18
|
+
// If the user passed an existing BitmapShape we keep it,
|
|
19
|
+
// otherwise we fabricate one.
|
|
20
|
+
const shape =
|
|
21
|
+
bitmap instanceof ImageShape ? bitmap : new ImageShape(bitmap, options);
|
|
22
|
+
// GameObjectShapeWrapper keeps transform + render in sync with the shape
|
|
23
|
+
super(game, shape, options);
|
|
24
|
+
//console.log("imagego", this);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
reset() {
|
|
28
|
+
this.shape.reset();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module GameObjects
|
|
3
|
+
* @description Specialized GameObjects providing common functionality for game development.
|
|
4
|
+
*
|
|
5
|
+
* This module exports a collection of pre-built GameObject classes that provide
|
|
6
|
+
* specific functionality commonly needed in games:
|
|
7
|
+
* - {@link Scene}: A container for organizing hierarchies of GameObjects
|
|
8
|
+
* - {@link Text}: A GameObject for rendering text with various styling options
|
|
9
|
+
*
|
|
10
|
+
* All classes in this module extend the core {@link GameObject} class and inherit
|
|
11
|
+
* its properties and lifecycle methods while adding specialized behaviors.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // Using Scene to organize a group of related objects
|
|
15
|
+
* import { Game } from '../core/game.js';
|
|
16
|
+
* import { Scene, Text } from '../core/objects';
|
|
17
|
+
*
|
|
18
|
+
* class MyGame extends Game {
|
|
19
|
+
* init() {
|
|
20
|
+
* // Create a UI layer as a Scene
|
|
21
|
+
* const uiLayer = new Scene(this, {
|
|
22
|
+
* x: 0,
|
|
23
|
+
* y: 0
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Add text elements to the scene
|
|
27
|
+
* const scoreText = new Text(this, "Score: 0", {
|
|
28
|
+
* x: 20,
|
|
29
|
+
* y: 20,
|
|
30
|
+
* font: "24px Arial",
|
|
31
|
+
* color: "#ffffff"
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* uiLayer.add(scoreText);
|
|
35
|
+
*
|
|
36
|
+
* // Add the entire scene to the pipeline
|
|
37
|
+
* this.pipeline.add(uiLayer);
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* @see {@link GameObject} The base class for all objects in this module
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// Interactive abstracts
|
|
45
|
+
export { GameObject } from "./go.js";
|
|
46
|
+
export { GameObjectShapeWrapper, ShapeGOFactory } from "./wrapper.js";
|
|
47
|
+
// Interactive Groups
|
|
48
|
+
export { Scene } from "./scene.js";
|
|
49
|
+
export { Scene3D } from "./scene3d.js";
|
|
50
|
+
export { IsometricScene } from "./isometric-scene.js";
|
|
51
|
+
export * from "./layoutscene.js";
|
|
52
|
+
// Specialized GameObjects
|
|
53
|
+
export { Text } from "./text.js";
|
|
54
|
+
export { ImageGo } from "./imagego.js";
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Scene } from "./scene.js";
|
|
2
|
+
import { Painter } from "../../painter/painter.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* IsometricScene - A Scene that projects children using isometric projection
|
|
6
|
+
*
|
|
7
|
+
* Bridges the GameObject/Scene system with isometric rendering, allowing GameObjects
|
|
8
|
+
* to be positioned in 3D grid space (x, y, z) and automatically projected to 2D
|
|
9
|
+
* isometric view using diamond projection.
|
|
10
|
+
*
|
|
11
|
+
* Children can have an optional `z` property for height above the ground plane.
|
|
12
|
+
* The scene handles depth sorting automatically based on isometric depth (x + y).
|
|
13
|
+
*
|
|
14
|
+
* Supports optional IsometricCamera for animated view rotation.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Basic usage
|
|
18
|
+
* const isoScene = new IsometricScene(this, {
|
|
19
|
+
* x: this.width / 2,
|
|
20
|
+
* y: this.height / 2,
|
|
21
|
+
* tileWidth: 64,
|
|
22
|
+
* tileHeight: 32,
|
|
23
|
+
* depthSort: true,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // With camera for rotatable view
|
|
27
|
+
* const camera = new IsometricCamera({ rotationStep: Math.PI / 4 });
|
|
28
|
+
* isoScene.setCamera(camera);
|
|
29
|
+
* camera.rotateRight(); // Rotate view 45°
|
|
30
|
+
*
|
|
31
|
+
* const box = new IsometricBox(this, { x: 2, y: 3 });
|
|
32
|
+
* box.z = 0; // ground level
|
|
33
|
+
* isoScene.add(box);
|
|
34
|
+
*
|
|
35
|
+
* this.pipeline.add(isoScene);
|
|
36
|
+
*
|
|
37
|
+
* @extends Scene
|
|
38
|
+
*/
|
|
39
|
+
export class IsometricScene extends Scene {
|
|
40
|
+
/**
|
|
41
|
+
* @param {Game} game - Game instance
|
|
42
|
+
* @param {Object} options - Configuration
|
|
43
|
+
* @param {number} [options.tileWidth=64] - Width of a tile in the isometric grid (pixels)
|
|
44
|
+
* @param {number} [options.tileHeight] - Height of a tile (defaults to tileWidth / 2)
|
|
45
|
+
* @param {boolean} [options.depthSort=true] - Sort children by depth (back-to-front)
|
|
46
|
+
* @param {boolean} [options.scaleByDepth=false] - Scale children by perspective distance
|
|
47
|
+
* @param {number} [options.gridSize=10] - Size of the grid in tiles (used for scale calculations)
|
|
48
|
+
* @param {number} [options.elevationScale=1] - Multiplier for z-axis visual offset
|
|
49
|
+
* @param {IsometricCamera} [options.camera=null] - Optional camera for view rotation
|
|
50
|
+
*/
|
|
51
|
+
constructor(game, options = {}) {
|
|
52
|
+
super(game, options);
|
|
53
|
+
|
|
54
|
+
/** @type {number} Width of a tile in pixels */
|
|
55
|
+
this.tileWidth = options.tileWidth ?? 64;
|
|
56
|
+
|
|
57
|
+
/** @type {number} Height of a tile in pixels (typically tileWidth / 2 for standard isometric) */
|
|
58
|
+
this.tileHeight = options.tileHeight ?? this.tileWidth / 2;
|
|
59
|
+
|
|
60
|
+
/** @type {boolean} Whether to sort children by depth (back-to-front) */
|
|
61
|
+
this.depthSort = options.depthSort ?? true;
|
|
62
|
+
|
|
63
|
+
/** @type {boolean} Whether to scale children by perspective distance */
|
|
64
|
+
this.scaleByDepth = options.scaleByDepth ?? false;
|
|
65
|
+
|
|
66
|
+
/** @type {number} Size of the grid in tiles (for scale calculations) */
|
|
67
|
+
this.gridSize = options.gridSize ?? 10;
|
|
68
|
+
|
|
69
|
+
/** @type {number} Multiplier for z-axis visual offset */
|
|
70
|
+
this.elevationScale = options.elevationScale ?? 1;
|
|
71
|
+
|
|
72
|
+
/** @type {IsometricCamera|null} Camera for view rotation */
|
|
73
|
+
this.camera = options.camera ?? null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Set or update the camera reference
|
|
78
|
+
* @param {IsometricCamera} camera - Camera instance
|
|
79
|
+
* @returns {IsometricScene} this for chaining
|
|
80
|
+
*/
|
|
81
|
+
setCamera(camera) {
|
|
82
|
+
this.camera = camera;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert 3D grid coordinates (x, y, z) to 2D isometric screen position.
|
|
88
|
+
*
|
|
89
|
+
* Uses the standard "diamond" isometric projection with camera rotation.
|
|
90
|
+
* Note: For best visual results, use 90° rotation steps (Math.PI/2).
|
|
91
|
+
* 45° rotations can cause visual flattening at certain angles.
|
|
92
|
+
*
|
|
93
|
+
* @param {number} x - Grid X coordinate
|
|
94
|
+
* @param {number} y - Grid Y coordinate
|
|
95
|
+
* @param {number} [z=0] - Height above ground plane
|
|
96
|
+
* @returns {{x: number, y: number, depth: number}} Screen position and depth for sorting
|
|
97
|
+
*/
|
|
98
|
+
toIsometric(x, y, z = 0) {
|
|
99
|
+
// Apply camera rotation if present
|
|
100
|
+
let rotatedX = x;
|
|
101
|
+
let rotatedY = y;
|
|
102
|
+
|
|
103
|
+
if (this.camera) {
|
|
104
|
+
const angle = this.camera.angle;
|
|
105
|
+
const cos = Math.cos(angle);
|
|
106
|
+
const sin = Math.sin(angle);
|
|
107
|
+
rotatedX = x * cos - y * sin;
|
|
108
|
+
rotatedY = x * sin + y * cos;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const isoX = (rotatedX - rotatedY) * (this.tileWidth / 2);
|
|
112
|
+
const isoY = (rotatedX + rotatedY) * (this.tileHeight / 2) - z * this.elevationScale;
|
|
113
|
+
|
|
114
|
+
// Depth for sorting
|
|
115
|
+
const depth = rotatedX + rotatedY - z * 0.01;
|
|
116
|
+
|
|
117
|
+
return { x: isoX, y: isoY, depth };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert screen coordinates back to grid coordinates.
|
|
122
|
+
*
|
|
123
|
+
* Useful for mouse picking and tile selection.
|
|
124
|
+
* Note: This assumes z = 0 (ground plane).
|
|
125
|
+
* If a camera is attached, the inverse rotation is applied.
|
|
126
|
+
*
|
|
127
|
+
* @param {number} screenX - Screen X relative to scene center
|
|
128
|
+
* @param {number} screenY - Screen Y relative to scene center
|
|
129
|
+
* @returns {{x: number, y: number}} Grid coordinates
|
|
130
|
+
*/
|
|
131
|
+
fromIsometric(screenX, screenY) {
|
|
132
|
+
// Inverse of the isometric transform (assuming z = 0)
|
|
133
|
+
const halfTileW = this.tileWidth / 2;
|
|
134
|
+
const halfTileH = this.tileHeight / 2;
|
|
135
|
+
|
|
136
|
+
// Get rotated grid coordinates
|
|
137
|
+
let rotatedX = (screenX / halfTileW + screenY / halfTileH) / 2;
|
|
138
|
+
let rotatedY = (screenY / halfTileH - screenX / halfTileW) / 2;
|
|
139
|
+
|
|
140
|
+
// Apply inverse camera rotation if present
|
|
141
|
+
if (this.camera) {
|
|
142
|
+
const angle = -this.camera.angle; // Negative for inverse
|
|
143
|
+
const cos = Math.cos(angle);
|
|
144
|
+
const sin = Math.sin(angle);
|
|
145
|
+
const x = rotatedX * cos - rotatedY * sin;
|
|
146
|
+
const y = rotatedX * sin + rotatedY * cos;
|
|
147
|
+
return { x, y };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { x: rotatedX, y: rotatedY };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Update method - updates camera if attached
|
|
155
|
+
* @param {number} dt - Delta time in seconds
|
|
156
|
+
*/
|
|
157
|
+
update(dt) {
|
|
158
|
+
super.update(dt);
|
|
159
|
+
|
|
160
|
+
// Update camera animation
|
|
161
|
+
if (this.camera) {
|
|
162
|
+
this.camera.update(dt);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get the tile coordinates at a given screen position (floored to integers).
|
|
168
|
+
*
|
|
169
|
+
* @param {number} screenX - Screen X relative to scene center
|
|
170
|
+
* @param {number} screenY - Screen Y relative to scene center
|
|
171
|
+
* @returns {{x: number, y: number}} Tile coordinates (integers)
|
|
172
|
+
*/
|
|
173
|
+
getTileAt(screenX, screenY) {
|
|
174
|
+
const grid = this.fromIsometric(screenX, screenY);
|
|
175
|
+
return {
|
|
176
|
+
x: Math.floor(grid.x),
|
|
177
|
+
y: Math.floor(grid.y),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Calculate scale factor based on Y position (for perspective effect).
|
|
183
|
+
*
|
|
184
|
+
* Objects further "back" (higher y in grid) appear smaller.
|
|
185
|
+
*
|
|
186
|
+
* @param {number} y - Grid Y position
|
|
187
|
+
* @returns {number} Scale factor (0.7 to 1.3 range)
|
|
188
|
+
*/
|
|
189
|
+
getDepthScale(y) {
|
|
190
|
+
const distanceFromCenter = Math.abs(y);
|
|
191
|
+
const maxDistance = this.gridSize;
|
|
192
|
+
// Range from 0.7 (far) to 1.3 (near)
|
|
193
|
+
return 0.7 + (distanceFromCenter / maxDistance) * 0.6;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Override Scene's render to provide isometric coordinate system.
|
|
198
|
+
*
|
|
199
|
+
* This method:
|
|
200
|
+
* 1. Translates to the scene's position (center of projection)
|
|
201
|
+
* 2. Depth-sorts children back-to-front by their grid position
|
|
202
|
+
* 3. Renders each child (children use toIsometric() for their own projection)
|
|
203
|
+
*
|
|
204
|
+
* Note: Children are responsible for calling toIsometric() in their render()
|
|
205
|
+
* method. This gives them full control over complex rendering (shadows, etc).
|
|
206
|
+
*/
|
|
207
|
+
render() {
|
|
208
|
+
if (!this.visible) return;
|
|
209
|
+
|
|
210
|
+
Painter.save();
|
|
211
|
+
|
|
212
|
+
// Translate to the IsometricScene's position (e.g., center of screen)
|
|
213
|
+
// This defines the origin (0,0) of the isometric projection on the 2D canvas
|
|
214
|
+
Painter.translateTo(this.x, this.y);
|
|
215
|
+
|
|
216
|
+
// Build render list with depth info for sorting
|
|
217
|
+
const renderList = [];
|
|
218
|
+
|
|
219
|
+
for (const child of this._collection.getSortedChildren()) {
|
|
220
|
+
if (!child.visible) continue;
|
|
221
|
+
|
|
222
|
+
// Use custom isoDepth if available, otherwise calculate
|
|
223
|
+
let depth;
|
|
224
|
+
if (child.isoDepth !== undefined) {
|
|
225
|
+
depth = child.isoDepth;
|
|
226
|
+
} else {
|
|
227
|
+
// For moving objects, use z as height
|
|
228
|
+
const height = child.z ?? 0;
|
|
229
|
+
// Higher (x + y) = closer to viewer = higher depth = render later
|
|
230
|
+
// Higher z = on top = higher depth = render later
|
|
231
|
+
depth = (child.x + child.y) + height * 0.05;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
renderList.push({
|
|
235
|
+
child,
|
|
236
|
+
depth,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Depth sort: respect zIndex first, then isometric depth
|
|
241
|
+
// Lower depth (back) renders first, higher depth (front) renders last
|
|
242
|
+
if (this.depthSort) {
|
|
243
|
+
renderList.sort((a, b) => {
|
|
244
|
+
const za = a.child.zIndex ?? 0;
|
|
245
|
+
const zb = b.child.zIndex ?? 0;
|
|
246
|
+
if (za !== zb) return za - zb;
|
|
247
|
+
return a.depth - b.depth; // back-to-front by position
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Render each child - they handle their own projection via toIsometric()
|
|
252
|
+
for (const item of renderList) {
|
|
253
|
+
Painter.save();
|
|
254
|
+
item.child.render();
|
|
255
|
+
Painter.restore();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
Painter.restore();
|
|
259
|
+
}
|
|
260
|
+
}
|