emeraldengine 2.2.1 → 3.1.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/README.md +2198 -968
- package/dist/types/index.d.ts +75 -32
- package/dist/types/src/Animator.d.ts +50 -0
- package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
- package/dist/types/src/Camera.d.ts +122 -0
- package/dist/types/src/CameraController.d.ts +107 -0
- package/dist/types/src/CanvasText.d.ts +91 -0
- package/dist/types/src/CollisionLayers.d.ts +58 -0
- package/dist/types/{Color.d.ts → src/Color.d.ts} +6 -6
- package/dist/types/src/Coroutine.d.ts +111 -0
- package/dist/types/src/DebugOverlay.d.ts +86 -0
- package/dist/types/src/Drawable.d.ts +271 -0
- package/dist/types/src/Easing.d.ts +22 -0
- package/dist/types/src/Emerald.d.ts +420 -0
- package/dist/types/src/EmeraldDB.d.ts +159 -0
- package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
- package/dist/types/src/GLUtils.d.ts +4 -0
- package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
- package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +75 -23
- package/dist/types/src/Interpolator.d.ts +66 -0
- package/dist/types/src/Material.d.ts +87 -0
- package/dist/types/src/MathUtils.d.ts +81 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/src/Physics.d.ts +337 -0
- package/dist/types/src/Pool.d.ts +53 -0
- package/dist/types/src/PostEffects.d.ts +68 -0
- package/dist/types/src/PostProcessor.d.ts +124 -0
- package/dist/types/src/RenderTarget.d.ts +56 -0
- package/dist/types/src/Scene.d.ts +62 -0
- package/dist/types/src/ScreenEffects.d.ts +111 -0
- package/dist/types/src/Serializer.d.ts +86 -0
- package/dist/types/src/Shaders.d.ts +2 -0
- package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
- package/dist/types/src/SpatialGrid.d.ts +50 -0
- package/dist/types/src/SpriteBatch.d.ts +89 -0
- package/dist/types/src/StateMachine.d.ts +59 -0
- package/dist/types/src/Storage.d.ts +89 -0
- package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
- package/dist/types/src/TextureAtlas.d.ts +55 -0
- package/dist/types/src/Tilemap.d.ts +91 -0
- package/dist/types/src/Time.d.ts +53 -0
- package/dist/types/src/Timer.d.ts +54 -0
- package/dist/types/src/Transform.d.ts +94 -0
- package/dist/types/src/Tween.d.ts +81 -0
- package/dist/types/src/UI.d.ts +121 -0
- package/dist/types/src/components/Behaviour.d.ts +71 -0
- package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
- package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
- package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
- package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
- package/dist/types/src/components/Collider.d.ts +102 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/PolygonCollider.d.ts +33 -0
- package/dist/types/src/components/RigidBody.d.ts +456 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -0
- package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
- package/dist/types/src/importers/TiledMap.d.ts +62 -0
- package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
- package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
- package/dist/types/src/managers/AssetManager.d.ts +116 -0
- package/dist/types/src/managers/AudioManager.d.ts +259 -0
- package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
- package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
- package/dist/types/src/managers/GLManager.d.ts +84 -0
- package/dist/types/src/managers/GLState.d.ts +37 -0
- package/dist/types/src/managers/Gamepad.d.ts +102 -0
- package/dist/types/src/managers/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +381 -0
- package/dist/types/src/managers/NetworkManager.d.ts +93 -0
- package/dist/types/src/managers/RenderStats.d.ts +34 -0
- package/dist/types/src/managers/SceneManager.d.ts +44 -0
- package/dist/types/src/managers/ShaderManager.d.ts +55 -0
- package/dist/types/src/managers/TextureManager.d.ts +102 -0
- package/dist/types/src/particlesystem/Particle.d.ts +64 -0
- package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
- package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
- package/dist/types/src/physics/AABB.d.ts +92 -0
- package/dist/types/src/physics/Body.d.ts +435 -0
- package/dist/types/src/physics/BodyType.d.ts +6 -0
- package/dist/types/src/physics/BroadPhase.d.ts +210 -0
- package/dist/types/src/physics/Collision.d.ts +102 -0
- package/dist/types/src/physics/Contact.d.ts +206 -0
- package/dist/types/src/physics/ContactSolver.d.ts +108 -0
- package/dist/types/src/physics/Distance.d.ts +54 -0
- package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
- package/dist/types/src/physics/Fixture.d.ts +221 -0
- package/dist/types/src/physics/Island.d.ts +52 -0
- package/dist/types/src/physics/Joint.d.ts +59 -0
- package/dist/types/src/physics/Math2D.d.ts +371 -0
- package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
- package/dist/types/src/physics/Settings.d.ts +22 -0
- package/dist/types/src/physics/Shapes.d.ts +207 -0
- package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
- package/dist/types/src/physics/World.d.ts +274 -0
- package/dist/types/src/physics/index.d.ts +34 -0
- package/index.js +78 -0
- package/package.json +73 -60
- package/src/Animator.js +95 -0
- package/src/BitmapText.js +6 -5
- package/src/Camera.js +183 -0
- package/src/CameraController.js +192 -0
- package/src/CanvasText.js +281 -0
- package/src/CollisionLayers.js +86 -0
- package/src/Color.js +26 -18
- package/src/Coroutine.js +259 -0
- package/src/DebugOverlay.js +246 -0
- package/src/Drawable.js +842 -582
- package/src/Easing.js +57 -0
- package/src/Emerald.js +1150 -459
- package/src/EmeraldDB.js +328 -0
- package/src/FPSCounter.js +43 -43
- package/src/GLUtils.js +60 -67
- package/src/Instance.js +41 -5
- package/src/InstancedTexture.js +308 -127
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +134 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +431 -40
- package/src/Pool.js +85 -0
- package/src/PostEffects.js +296 -0
- package/src/PostProcessor.js +304 -0
- package/src/RenderTarget.js +134 -0
- package/src/Scene.js +115 -83
- package/src/ScreenEffects.js +266 -0
- package/src/Serializer.js +131 -0
- package/src/Shaders.js +150 -165
- package/src/Shapes.js +118 -129
- package/src/SpatialGrid.js +111 -0
- package/src/SpriteBatch.js +299 -0
- package/src/StateMachine.js +82 -0
- package/src/Storage.js +175 -47
- package/src/Texture.js +58 -67
- package/src/TextureAtlas.js +96 -0
- package/src/Tilemap.js +274 -0
- package/src/Time.js +51 -6
- package/src/Timer.js +99 -0
- package/src/Transform.js +100 -7
- package/src/Tween.js +160 -0
- package/src/UI.js +394 -0
- package/src/components/Behaviour.js +90 -0
- package/src/components/BoxCollider.js +29 -12
- package/src/components/BoxColliderDebug.js +3 -4
- package/src/components/CircleCollider.js +26 -12
- package/src/components/CircleColliderDebug.js +25 -24
- package/src/components/Collider.js +150 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/PolygonCollider.js +55 -0
- package/src/components/RigidBody.js +556 -8
- package/src/importers/Aseprite.js +142 -0
- package/src/importers/ForgeLevel.js +581 -0
- package/src/importers/TiledMap.js +158 -0
- package/src/lights/DirectionalLight.js +6 -15
- package/src/lights/PointLight.js +4 -4
- package/src/managers/AssetManager.js +239 -0
- package/src/managers/AudioManager.js +565 -146
- package/src/managers/EventManager.js +488 -477
- package/src/managers/GLManager.js +57 -0
- package/src/managers/GLState.js +70 -0
- package/src/managers/Gamepad.js +126 -0
- package/src/managers/IDManager.js +24 -2
- package/src/managers/InputManager.js +905 -0
- package/src/managers/NetworkManager.js +178 -0
- package/src/managers/RenderStats.js +34 -0
- package/src/managers/SceneManager.js +30 -0
- package/src/managers/ShaderManager.js +0 -2
- package/src/managers/TextureManager.js +218 -0
- package/src/particlesystem/Particle.js +82 -7
- package/src/particlesystem/ParticleSettings.js +21 -3
- package/src/particlesystem/Particles.js +80 -31
- package/src/physics/AABB.js +207 -0
- package/src/physics/Body.js +862 -0
- package/src/physics/BodyType.js +16 -0
- package/src/physics/BroadPhase.js +641 -0
- package/src/physics/Collision.js +534 -0
- package/src/physics/Contact.js +500 -0
- package/src/physics/ContactSolver.js +526 -0
- package/src/physics/Distance.js +403 -0
- package/src/physics/DistanceJoint.js +227 -0
- package/src/physics/Fixture.js +346 -0
- package/src/physics/Island.js +203 -0
- package/src/physics/Joint.js +78 -0
- package/src/physics/Math2D.js +573 -0
- package/src/physics/RevoluteJoint.js +278 -0
- package/src/physics/Settings.js +78 -0
- package/src/physics/Shapes.js +549 -0
- package/src/physics/TimeOfImpact.js +87 -0
- package/src/physics/World.js +731 -0
- package/src/physics/index.js +79 -0
- package/dist/types/Drawable.d.ts +0 -157
- package/dist/types/Emerald.d.ts +0 -73
- package/dist/types/GLUtils.d.ts +0 -6
- package/dist/types/Physics.d.ts +0 -139
- package/dist/types/Scene.d.ts +0 -39
- package/dist/types/Shaders.d.ts +0 -4
- package/dist/types/Storage.d.ts +0 -46
- package/dist/types/Time.d.ts +0 -22
- package/dist/types/Transform.d.ts +0 -41
- package/dist/types/components/BoxColliderDebug.d.ts +0 -19
- package/dist/types/components/CircleColliderDebug.d.ts +0 -19
- package/dist/types/components/Collider.d.ts +0 -53
- package/dist/types/components/GameObject.d.ts +0 -72
- package/dist/types/components/RigidBody.d.ts +0 -104
- package/dist/types/managers/AudioManager.d.ts +0 -60
- package/dist/types/managers/GLManager.d.ts +0 -47
- package/dist/types/managers/IDManager.d.ts +0 -21
- package/dist/types/managers/SceneManager.d.ts +0 -22
- package/dist/types/particlesystem/Particle.d.ts +0 -42
package/src/Tween.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import Easing from "./Easing.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class Tween
|
|
5
|
+
* @description Animates numeric properties of a target object over time with
|
|
6
|
+
* easing. Tweens created via `Tween.to(...)` are auto-registered and advanced
|
|
7
|
+
* by `Emerald.drawScene` (using time-scaled delta), so they respect pause/slow-mo.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* Tween.to(sprite.transform.position, { x: 200, y: -50 }, 0.6, {
|
|
11
|
+
* easing: Easing.outBack,
|
|
12
|
+
* onComplete: () => console.log("done"),
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
class Tween {
|
|
16
|
+
/**
|
|
17
|
+
* @param {Object} target - The object whose numeric keys will be animated
|
|
18
|
+
* @param {Object} props - Map of key -> end value
|
|
19
|
+
* @param {number} duration - Duration in seconds
|
|
20
|
+
* @param {Object} [options] - { easing, delay, loop, yoyo, onUpdate, onComplete }
|
|
21
|
+
*/
|
|
22
|
+
constructor(target, props, duration, options = {}) {
|
|
23
|
+
this.target = target;
|
|
24
|
+
this.props = props;
|
|
25
|
+
this.duration = Math.max(1e-4, duration);
|
|
26
|
+
this.easing = options.easing || Easing.linear;
|
|
27
|
+
this.delay = options.delay || 0;
|
|
28
|
+
this.loop = !!options.loop;
|
|
29
|
+
this.yoyo = !!options.yoyo;
|
|
30
|
+
this.onUpdate = options.onUpdate || null;
|
|
31
|
+
this.onComplete = options.onComplete || null;
|
|
32
|
+
|
|
33
|
+
this.elapsed = 0;
|
|
34
|
+
this.started = false;
|
|
35
|
+
this.done = false;
|
|
36
|
+
this.from = {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @private */
|
|
40
|
+
_begin() {
|
|
41
|
+
for (const key in this.props) {
|
|
42
|
+
this.from[key] = this.target[key];
|
|
43
|
+
}
|
|
44
|
+
this.started = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @method update
|
|
49
|
+
* @description Advances the tween. Returns false when finished.
|
|
50
|
+
* @param {number} dt - Seconds since the last update
|
|
51
|
+
* @returns {boolean} - Whether the tween is still active
|
|
52
|
+
*/
|
|
53
|
+
update(dt) {
|
|
54
|
+
if (this.done) return false;
|
|
55
|
+
|
|
56
|
+
if (this.delay > 0) {
|
|
57
|
+
this.delay -= dt;
|
|
58
|
+
if (this.delay > 0) return true;
|
|
59
|
+
dt = -this.delay;
|
|
60
|
+
this.delay = 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!this.started) this._begin();
|
|
64
|
+
|
|
65
|
+
this.elapsed += dt;
|
|
66
|
+
const t = Math.min(1, this.elapsed / this.duration);
|
|
67
|
+
const e = this.easing(t);
|
|
68
|
+
|
|
69
|
+
for (const key in this.props) {
|
|
70
|
+
this.target[key] =
|
|
71
|
+
this.from[key] + (this.props[key] - this.from[key]) * e;
|
|
72
|
+
}
|
|
73
|
+
if (this.onUpdate) this.onUpdate(this.target, t);
|
|
74
|
+
|
|
75
|
+
if (t >= 1) {
|
|
76
|
+
if (this.loop) {
|
|
77
|
+
this.elapsed = 0;
|
|
78
|
+
if (this.yoyo) {
|
|
79
|
+
const swap = this.from;
|
|
80
|
+
this.from = this.props;
|
|
81
|
+
this.props = swap;
|
|
82
|
+
} else {
|
|
83
|
+
for (const key in this.props) this.target[key] = this.from[key];
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
this.done = true;
|
|
87
|
+
if (this.onComplete) this.onComplete(this.target);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @method then
|
|
96
|
+
* @description Chains a callback to run when the tween completes.
|
|
97
|
+
* @param {Function} callback
|
|
98
|
+
* @returns {Tween} - this
|
|
99
|
+
*/
|
|
100
|
+
then(callback) {
|
|
101
|
+
const prev = this.onComplete;
|
|
102
|
+
this.onComplete = (target) => {
|
|
103
|
+
if (prev) prev(target);
|
|
104
|
+
callback(target);
|
|
105
|
+
};
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @method stop
|
|
111
|
+
* @description Stops the tween immediately (without firing onComplete).
|
|
112
|
+
*/
|
|
113
|
+
stop() {
|
|
114
|
+
this.done = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
Tween.active = [];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @method Tween.to
|
|
122
|
+
* @description Creates and registers a tween.
|
|
123
|
+
* @returns {Tween}
|
|
124
|
+
*/
|
|
125
|
+
Tween.to = function (target, props, duration, options) {
|
|
126
|
+
const tween = new Tween(target, props, duration, options);
|
|
127
|
+
Tween.active.push(tween);
|
|
128
|
+
return tween;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @method Tween.update
|
|
133
|
+
* @description Advances all active tweens. Driven by Emerald.drawScene.
|
|
134
|
+
* @param {number} dt - Seconds since the last frame
|
|
135
|
+
*/
|
|
136
|
+
Tween.update = function (dt) {
|
|
137
|
+
for (let i = Tween.active.length - 1; i >= 0; i--) {
|
|
138
|
+
if (!Tween.active[i].update(dt)) {
|
|
139
|
+
Tween.active.splice(i, 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @method Tween.killOf
|
|
146
|
+
* @description Removes all tweens targeting the given object.
|
|
147
|
+
*/
|
|
148
|
+
Tween.killOf = function (target) {
|
|
149
|
+
Tween.active = Tween.active.filter((tween) => tween.target !== target);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @method Tween.killAll
|
|
154
|
+
* @description Removes every active tween.
|
|
155
|
+
*/
|
|
156
|
+
Tween.killAll = function () {
|
|
157
|
+
Tween.active.length = 0;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default Tween;
|
package/src/UI.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import GameObject from "./components/GameObject.js";
|
|
2
|
+
import { Square2D } from "./Shapes.js";
|
|
3
|
+
import CanvasText from "./CanvasText.js";
|
|
4
|
+
import Camera from "./Camera.js";
|
|
5
|
+
import Color from "./Color.js";
|
|
6
|
+
import { Vector2, Vector3 } from "./Physics.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @class UI
|
|
10
|
+
* @description A small retained-mode UI toolkit rendered entirely with the
|
|
11
|
+
* engine (no DOM/HTML overlay): panels, labels, buttons and text fields, drawn
|
|
12
|
+
* as screen-space objects on a dedicated layer with their own pointer/keyboard
|
|
13
|
+
* hit-testing. Positions are in pixels from the viewport center (y up), or a
|
|
14
|
+
* `(viewW, viewH) => ({x, y})` function for responsive anchoring; call
|
|
15
|
+
* `relayout()` on resize.
|
|
16
|
+
*
|
|
17
|
+
* Pair it with a UI camera (see {@link UI.createCamera}) so the UI draws over
|
|
18
|
+
* the game and the game cameras ignore the UI layer.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const uiCam = UI.createCamera(); // onlyLayers = [UI layer]
|
|
22
|
+
* gameCamera.ignoreLayer(UI.LAYER);
|
|
23
|
+
* emerald.setCameras([gameCamera, uiCam]);
|
|
24
|
+
*
|
|
25
|
+
* const ui = new UI(scene, canvas);
|
|
26
|
+
* ui.label(() => ({ x: 0, y: 200 }), "PAUSED", { font: "700 48px sans-serif" });
|
|
27
|
+
* ui.button(() => ({ x: 0, y: 0 }), "Resume", 220, 56, { onClick: resume });
|
|
28
|
+
* // in the loop: ui.relayout() on resize; remove input via ui.destroy().
|
|
29
|
+
*/
|
|
30
|
+
class UI {
|
|
31
|
+
/**
|
|
32
|
+
* @param {Scene} scene - Scene to add UI objects to
|
|
33
|
+
* @param {HTMLCanvasElement} canvas - Canvas for pointer coordinates
|
|
34
|
+
* @param {Object} [options] - { layer = UI.LAYER, accent = [110,180,255] }
|
|
35
|
+
*/
|
|
36
|
+
constructor(scene, canvas, options = {}) {
|
|
37
|
+
this.scene = scene;
|
|
38
|
+
this.canvas = canvas;
|
|
39
|
+
this.layer = options.layer ?? UI.LAYER;
|
|
40
|
+
this.accent = options.accent || [110, 180, 255];
|
|
41
|
+
|
|
42
|
+
this.elements = [];
|
|
43
|
+
this.focused = null;
|
|
44
|
+
/** @private */
|
|
45
|
+
this._hover = null;
|
|
46
|
+
|
|
47
|
+
/** @private */
|
|
48
|
+
this._onMove = (e) => this._handleMove(e);
|
|
49
|
+
/** @private */
|
|
50
|
+
this._onDown = (e) => this._handleDown(e);
|
|
51
|
+
/** @private */
|
|
52
|
+
this._onKey = (e) => this._handleKey(e);
|
|
53
|
+
canvas.addEventListener("mousemove", this._onMove);
|
|
54
|
+
canvas.addEventListener("mousedown", this._onDown);
|
|
55
|
+
window.addEventListener("keydown", this._onKey);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @private */
|
|
59
|
+
_resolvePos(pos) {
|
|
60
|
+
const vw = this.canvas.clientWidth || this.canvas.width;
|
|
61
|
+
const vh = this.canvas.clientHeight || this.canvas.height;
|
|
62
|
+
return typeof pos === "function" ? pos(vw, vh) : pos;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @method relayout
|
|
67
|
+
* @description Recomputes positions of all elements (call after a resize).
|
|
68
|
+
*/
|
|
69
|
+
relayout() {
|
|
70
|
+
for (const el of this.elements) {
|
|
71
|
+
const p = this._resolvePos(el.pos);
|
|
72
|
+
el.x = p.x;
|
|
73
|
+
el.y = p.y;
|
|
74
|
+
el.go.transform.position.x = p.x;
|
|
75
|
+
el.go.transform.position.y = p.y;
|
|
76
|
+
if (el.label) {
|
|
77
|
+
el.label.transform.position.x = p.x;
|
|
78
|
+
el.label.transform.position.y = p.y;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @method panel
|
|
85
|
+
* @description Adds a rectangular panel.
|
|
86
|
+
* @returns {Object} - Element handle
|
|
87
|
+
*/
|
|
88
|
+
panel(pos, width, height, options = {}) {
|
|
89
|
+
const color = options.color || new Color(20, 24, 34, 235);
|
|
90
|
+
const go = this._makeQuad(pos, width, height, color, options.z ?? 0);
|
|
91
|
+
if (options.opacity != null) go.setOpacity(options.opacity);
|
|
92
|
+
const el = { type: "panel", go, pos, w: width, h: height };
|
|
93
|
+
return this._register(el, pos);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @method dim
|
|
98
|
+
* @description Adds a full-screen dimmer (modal backdrop).
|
|
99
|
+
* @returns {Object} - Element handle
|
|
100
|
+
*/
|
|
101
|
+
dim(opacity = 0.6) {
|
|
102
|
+
const go = this._makeQuad(
|
|
103
|
+
{ x: 0, y: 0 },
|
|
104
|
+
20000,
|
|
105
|
+
20000,
|
|
106
|
+
new Color(0, 0, 0, 255),
|
|
107
|
+
-1
|
|
108
|
+
);
|
|
109
|
+
go.setOpacity(opacity);
|
|
110
|
+
const el = { type: "dim", go, pos: { x: 0, y: 0 }, w: 20000, h: 20000 };
|
|
111
|
+
return this._register(el, { x: 0, y: 0 });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @method label
|
|
116
|
+
* @description Adds a text label.
|
|
117
|
+
* @returns {Object} - Element handle with setText(text)
|
|
118
|
+
*/
|
|
119
|
+
label(pos, text, options = {}) {
|
|
120
|
+
const go = CanvasText.create(text, {
|
|
121
|
+
font: options.font || "500 18px system-ui, sans-serif",
|
|
122
|
+
color: options.color || "#e8eefc",
|
|
123
|
+
screenSpace: true,
|
|
124
|
+
});
|
|
125
|
+
go.setLayer(this.layer);
|
|
126
|
+
go.alwaysVisible = true;
|
|
127
|
+
go.transform.position.z = options.z ?? 1;
|
|
128
|
+
this.scene.add(go);
|
|
129
|
+
const text2d = go.getComponent(CanvasText);
|
|
130
|
+
const el = {
|
|
131
|
+
type: "label",
|
|
132
|
+
go,
|
|
133
|
+
pos,
|
|
134
|
+
get w() {
|
|
135
|
+
return text2d.width;
|
|
136
|
+
},
|
|
137
|
+
get h() {
|
|
138
|
+
return text2d.height;
|
|
139
|
+
},
|
|
140
|
+
setText: (t) => text2d.setText(t),
|
|
141
|
+
};
|
|
142
|
+
return this._register(el, pos);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @method button
|
|
147
|
+
* @description Adds an interactive button (panel + centered label).
|
|
148
|
+
* @param {Object} [options] - { onClick, accent, font, color }
|
|
149
|
+
* @returns {Object} - Element handle with setText(text), setEnabled(bool)
|
|
150
|
+
*/
|
|
151
|
+
button(pos, text, width, height, options = {}) {
|
|
152
|
+
const accent = options.accent || this.accent;
|
|
153
|
+
const base = new Color(28, 34, 48, 245);
|
|
154
|
+
const go = this._makeQuad(pos, width, height, base, options.z ?? 0);
|
|
155
|
+
|
|
156
|
+
const label = CanvasText.create(text, {
|
|
157
|
+
font: options.font || "600 18px system-ui, sans-serif",
|
|
158
|
+
color: "#f2f6ff",
|
|
159
|
+
screenSpace: true,
|
|
160
|
+
});
|
|
161
|
+
label.setLayer(this.layer);
|
|
162
|
+
label.alwaysVisible = true;
|
|
163
|
+
label.transform.position.z = (options.z ?? 0) + 1;
|
|
164
|
+
this.scene.add(label);
|
|
165
|
+
|
|
166
|
+
const shape = go.getComponent(Square2D);
|
|
167
|
+
const el = {
|
|
168
|
+
type: "button",
|
|
169
|
+
go,
|
|
170
|
+
label,
|
|
171
|
+
pos,
|
|
172
|
+
w: width,
|
|
173
|
+
h: height,
|
|
174
|
+
enabled: true,
|
|
175
|
+
onClick: options.onClick,
|
|
176
|
+
_base: base,
|
|
177
|
+
_hover: new Color(accent[0], accent[1], accent[2], 255),
|
|
178
|
+
_shape: shape,
|
|
179
|
+
setText: (t) => label.getComponent(CanvasText).setText(t),
|
|
180
|
+
setEnabled: (v) => {
|
|
181
|
+
el.enabled = v;
|
|
182
|
+
go.setOpacity(v ? 1 : 0.45);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
return this._register(el, pos);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @method textField
|
|
190
|
+
* @description Adds an editable single-line text field.
|
|
191
|
+
* @param {Object} [options] - { value, placeholder, onChange, maxLength, font }
|
|
192
|
+
* @returns {Object} - Element handle with getValue()/setValue(text)
|
|
193
|
+
*/
|
|
194
|
+
textField(pos, width, height, options = {}) {
|
|
195
|
+
const go = this._makeQuad(
|
|
196
|
+
pos,
|
|
197
|
+
width,
|
|
198
|
+
height,
|
|
199
|
+
new Color(12, 16, 24, 250),
|
|
200
|
+
options.z ?? 0
|
|
201
|
+
);
|
|
202
|
+
const label = CanvasText.create(
|
|
203
|
+
options.value || options.placeholder || "",
|
|
204
|
+
{
|
|
205
|
+
font: options.font || "500 18px system-ui, sans-serif",
|
|
206
|
+
color: options.value ? "#e8eefc" : "#7c8aa0",
|
|
207
|
+
screenSpace: true,
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
label.setLayer(this.layer);
|
|
211
|
+
label.alwaysVisible = true;
|
|
212
|
+
label.transform.position.z = (options.z ?? 0) + 1;
|
|
213
|
+
this.scene.add(label);
|
|
214
|
+
|
|
215
|
+
const el = {
|
|
216
|
+
type: "textField",
|
|
217
|
+
go,
|
|
218
|
+
label,
|
|
219
|
+
pos,
|
|
220
|
+
w: width,
|
|
221
|
+
h: height,
|
|
222
|
+
value: options.value || "",
|
|
223
|
+
placeholder: options.placeholder || "",
|
|
224
|
+
maxLength: options.maxLength || 64,
|
|
225
|
+
onChange: options.onChange,
|
|
226
|
+
_render: () => {
|
|
227
|
+
const ct = label.getComponent(CanvasText);
|
|
228
|
+
const showing = el.value || el.placeholder;
|
|
229
|
+
ct.setColor(el.value ? "#e8eefc" : "#7c8aa0");
|
|
230
|
+
ct.setText(showing + (this.focused === el ? "|" : ""));
|
|
231
|
+
},
|
|
232
|
+
getValue: () => el.value,
|
|
233
|
+
setValue: (t) => {
|
|
234
|
+
el.value = t;
|
|
235
|
+
el._render();
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
return this._register(el, pos);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** @private */
|
|
242
|
+
_makeQuad(pos, width, height, color, z) {
|
|
243
|
+
const go = new GameObject(
|
|
244
|
+
"UIQuad",
|
|
245
|
+
new Vector3(0, 0, z),
|
|
246
|
+
0,
|
|
247
|
+
new Vector2(width / 2, height / 2)
|
|
248
|
+
);
|
|
249
|
+
const shape = new Square2D();
|
|
250
|
+
shape.setColor(color);
|
|
251
|
+
shape.setUseLighting(false);
|
|
252
|
+
go.addComponent(shape);
|
|
253
|
+
go.setScreenSpace(true);
|
|
254
|
+
go.setLayer(this.layer);
|
|
255
|
+
go.alwaysVisible = true;
|
|
256
|
+
this.scene.add(go);
|
|
257
|
+
const p = this._resolvePos(pos);
|
|
258
|
+
go.transform.position.x = p.x;
|
|
259
|
+
go.transform.position.y = p.y;
|
|
260
|
+
return go;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** @private */
|
|
264
|
+
_register(el, pos) {
|
|
265
|
+
const p = this._resolvePos(pos);
|
|
266
|
+
el.x = p.x;
|
|
267
|
+
el.y = p.y;
|
|
268
|
+
this.elements.push(el);
|
|
269
|
+
return el;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** @private */
|
|
273
|
+
_toScreen(clientX, clientY) {
|
|
274
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
275
|
+
const cssW = rect.width;
|
|
276
|
+
const cssH = rect.height;
|
|
277
|
+
return {
|
|
278
|
+
x: clientX - rect.left - cssW / 2,
|
|
279
|
+
y: -(clientY - rect.top - cssH / 2),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @private */
|
|
284
|
+
_hitTest(sx, sy) {
|
|
285
|
+
for (let i = this.elements.length - 1; i >= 0; i--) {
|
|
286
|
+
const el = this.elements[i];
|
|
287
|
+
if (el.type !== "button" && el.type !== "textField") continue;
|
|
288
|
+
if (el.enabled === false) continue;
|
|
289
|
+
const x = el.x;
|
|
290
|
+
const y = el.y;
|
|
291
|
+
if (Math.abs(sx - x) <= el.w / 2 && Math.abs(sy - y) <= el.h / 2) {
|
|
292
|
+
return el;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @method isOver
|
|
300
|
+
* @description Whether an interactive element is under the given page point,
|
|
301
|
+
* useful to suppress game clicks behind the UI.
|
|
302
|
+
*/
|
|
303
|
+
isOver(clientX, clientY) {
|
|
304
|
+
const s = this._toScreen(clientX, clientY);
|
|
305
|
+
return this._hitTest(s.x, s.y) != null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** @private */
|
|
309
|
+
_handleMove(e) {
|
|
310
|
+
const s = this._toScreen(e.clientX, e.clientY);
|
|
311
|
+
const hit = this._hitTest(s.x, s.y);
|
|
312
|
+
if (hit === this._hover) return;
|
|
313
|
+
if (this._hover && this._hover.type === "button") {
|
|
314
|
+
this._hover._shape.setColor(this._hover._base);
|
|
315
|
+
}
|
|
316
|
+
this._hover = hit;
|
|
317
|
+
if (hit && hit.type === "button") {
|
|
318
|
+
hit._shape.setColor(hit._hover);
|
|
319
|
+
this.canvas.style.cursor = "pointer";
|
|
320
|
+
} else {
|
|
321
|
+
this.canvas.style.cursor = "default";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** @private */
|
|
326
|
+
_handleDown(e) {
|
|
327
|
+
const s = this._toScreen(e.clientX, e.clientY);
|
|
328
|
+
const hit = this._hitTest(s.x, s.y);
|
|
329
|
+
|
|
330
|
+
if (this.focused && this.focused !== hit) {
|
|
331
|
+
const prev = this.focused;
|
|
332
|
+
this.focused = null;
|
|
333
|
+
if (prev._render) prev._render();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!hit) return;
|
|
337
|
+
if (hit.type === "button") {
|
|
338
|
+
if (typeof hit.onClick === "function") hit.onClick(e, hit);
|
|
339
|
+
} else if (hit.type === "textField") {
|
|
340
|
+
this.focused = hit;
|
|
341
|
+
hit._render();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** @private */
|
|
346
|
+
_handleKey(e) {
|
|
347
|
+
if (!this.focused || this.focused.type !== "textField") return;
|
|
348
|
+
const el = this.focused;
|
|
349
|
+
if (e.key === "Backspace") {
|
|
350
|
+
el.value = el.value.slice(0, -1);
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
} else if (e.key === "Enter" || e.key === "Escape") {
|
|
353
|
+
this.focused = null;
|
|
354
|
+
} else if (e.key.length === 1 && el.value.length < el.maxLength) {
|
|
355
|
+
el.value += e.key;
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
} else {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
el._render();
|
|
361
|
+
if (typeof el.onChange === "function") el.onChange(el.value);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* @method destroy
|
|
366
|
+
* @description Removes all UI objects and detaches input listeners.
|
|
367
|
+
*/
|
|
368
|
+
destroy() {
|
|
369
|
+
for (const el of this.elements) {
|
|
370
|
+
this.scene.remove(el.go);
|
|
371
|
+
if (el.label) this.scene.remove(el.label);
|
|
372
|
+
}
|
|
373
|
+
this.elements.length = 0;
|
|
374
|
+
this.canvas.removeEventListener("mousemove", this._onMove);
|
|
375
|
+
this.canvas.removeEventListener("mousedown", this._onDown);
|
|
376
|
+
window.removeEventListener("keydown", this._onKey);
|
|
377
|
+
this.canvas.style.cursor = "default";
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @method createCamera
|
|
382
|
+
* @description Returns a full-screen camera that renders only the UI layer,
|
|
383
|
+
* so UI draws over the game. Add it last in setCameras.
|
|
384
|
+
* @param {number} [layer] - The UI layer (defaults to UI.LAYER)
|
|
385
|
+
* @returns {Camera}
|
|
386
|
+
*/
|
|
387
|
+
static createCamera(layer = UI.LAYER) {
|
|
388
|
+
return new Camera({ onlyLayers: [layer] });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
UI.LAYER = 100000;
|
|
393
|
+
|
|
394
|
+
export default UI;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import IDManager from "../managers/IDManager.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class Behaviour
|
|
5
|
+
* @description Base class for custom per-object game logic. Subclass it and
|
|
6
|
+
* override `start()` (called once, the first update after being added) and
|
|
7
|
+
* `update(deltaTime)` (called every frame the owner is active). Add it to a
|
|
8
|
+
* GameObject like any other component; it is ticked by `Scene.update(dt)` via
|
|
9
|
+
* `GameObject.update(dt)`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* class Spinner extends Behaviour {
|
|
13
|
+
* start() { this.speed = 2; }
|
|
14
|
+
* update(dt) { this.gameObject.transform.rotation += this.speed * dt; }
|
|
15
|
+
* }
|
|
16
|
+
* obj.addComponent(new Spinner());
|
|
17
|
+
*/
|
|
18
|
+
class Behaviour {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.id = IDManager.generateUniqueID();
|
|
21
|
+
this.parentObject = null;
|
|
22
|
+
this.enabled = true;
|
|
23
|
+
/** @private */
|
|
24
|
+
this._started = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @member gameObject
|
|
29
|
+
* @description Convenience accessor for the owning GameObject.
|
|
30
|
+
*/
|
|
31
|
+
get gameObject() {
|
|
32
|
+
return this.parentObject;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @method setParent
|
|
37
|
+
* @description Sets the owning object. Called automatically by addComponent.
|
|
38
|
+
* @param {Object} parent - The owning GameObject (or Instance)
|
|
39
|
+
*/
|
|
40
|
+
setParent(parent) {
|
|
41
|
+
this.parentObject = parent;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @method getParent
|
|
46
|
+
* @description Returns the owning object
|
|
47
|
+
* @returns {Object} - The owning object
|
|
48
|
+
*/
|
|
49
|
+
getParent() {
|
|
50
|
+
return this.parentObject;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @method start
|
|
55
|
+
* @description Called once before the first update. Override in subclasses.
|
|
56
|
+
*/
|
|
57
|
+
start() {}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @method update
|
|
61
|
+
* @description Called every active frame. Override in subclasses.
|
|
62
|
+
* @param {number} deltaTime - Seconds since the previous frame (time-scaled)
|
|
63
|
+
*/
|
|
64
|
+
update(deltaTime) {}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @method onDestroy
|
|
68
|
+
* @description Called when the behaviour is removed. Override for cleanup.
|
|
69
|
+
*/
|
|
70
|
+
onDestroy() {}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @method onCollisionEnter
|
|
74
|
+
* @description Called when the owner's body starts touching another. Requires
|
|
75
|
+
* the Physics world to be ticked. Override in subclasses.
|
|
76
|
+
* @param {Object} other - The other GameObject/Instance (or null)
|
|
77
|
+
* @param {Object} contact - The physics contact
|
|
78
|
+
*/
|
|
79
|
+
onCollisionEnter(other, contact) {}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @method onCollisionExit
|
|
83
|
+
* @description Called when the owner's body stops touching another.
|
|
84
|
+
* @param {Object} other - The other GameObject/Instance (or null)
|
|
85
|
+
* @param {Object} contact - The physics contact
|
|
86
|
+
*/
|
|
87
|
+
onCollisionExit(other, contact) {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default Behaviour;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Box } from "../physics/index.js";
|
|
2
2
|
import BoxColliderDebug from "./BoxColliderDebug.js";
|
|
3
3
|
import SceneManager from "../managers/SceneManager.js";
|
|
4
4
|
import Collider from "./Collider.js";
|
|
@@ -22,21 +22,34 @@ class BoxCollider extends Collider {
|
|
|
22
22
|
friction,
|
|
23
23
|
restitution,
|
|
24
24
|
isSensor = false,
|
|
25
|
-
parentObject = null
|
|
25
|
+
parentObject = null,
|
|
26
|
+
filter = null
|
|
26
27
|
) {
|
|
27
28
|
super(rigidbody, isSensor, parentObject);
|
|
28
|
-
this.collider = rigidbody
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
isSensor: isSensor,
|
|
35
|
-
});
|
|
29
|
+
this.collider = rigidbody.createFixture(Box(fixtureSize.x, fixtureSize.y), {
|
|
30
|
+
density: density,
|
|
31
|
+
friction: friction,
|
|
32
|
+
restitution: restitution,
|
|
33
|
+
isSensor: isSensor,
|
|
34
|
+
});
|
|
36
35
|
this.fixtureSize = fixtureSize;
|
|
37
36
|
this.rigidbody.setCollider(this);
|
|
38
37
|
this.name = "BoxCollider" + this.id;
|
|
39
|
-
this.
|
|
38
|
+
this._applyFilterSpec(filter);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @method debugShape
|
|
43
|
+
* @description Lazily builds the debug visualization on first access, so a
|
|
44
|
+
* collider stays free of any GameObject/GL allocation until it is debugged.
|
|
45
|
+
* @returns {BoxColliderDebug} - The debug shape
|
|
46
|
+
*/
|
|
47
|
+
get debugShape() {
|
|
48
|
+
if (!this._debugShape) {
|
|
49
|
+
/** @private */
|
|
50
|
+
this._debugShape = new BoxColliderDebug(this.rigidbody);
|
|
51
|
+
}
|
|
52
|
+
return this._debugShape;
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
/**
|
|
@@ -55,7 +68,11 @@ class BoxCollider extends Collider {
|
|
|
55
68
|
* @description Hides the debug shape of the collider
|
|
56
69
|
*/
|
|
57
70
|
hideDebugShape() {
|
|
58
|
-
|
|
71
|
+
if (!this._debugShape) return;
|
|
72
|
+
var scene = SceneManager.getScene();
|
|
73
|
+
if (scene) {
|
|
74
|
+
scene.remove(this._debugShape.gameObject);
|
|
75
|
+
}
|
|
59
76
|
}
|
|
60
77
|
|
|
61
78
|
/**
|