emeraldengine 2.2.1 → 3.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/README.md +2359 -968
- package/dist/types/index.d.ts +72 -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} +5 -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} +60 -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 +80 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/{Physics.d.ts → src/Physics.d.ts} +88 -20
- 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 +96 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/RigidBody.d.ts +183 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -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/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +290 -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/index.js +72 -0
- package/package.json +74 -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 +18 -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 +251 -118
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +133 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +186 -5
- 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 +22 -3
- package/src/components/CircleCollider.js +19 -3
- package/src/components/CircleColliderDebug.js +24 -24
- package/src/components/Collider.js +140 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/RigidBody.js +123 -2
- package/src/importers/Aseprite.js +142 -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/IDManager.js +24 -2
- package/src/managers/InputManager.js +779 -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/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/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/Camera.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import Transform from "./Transform.js";
|
|
2
|
+
import { Vector2, Vector3 } from "./Physics.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @class Camera
|
|
6
|
+
* @description A view into the scene with its own transform (position, zoom,
|
|
7
|
+
* rotation) and a viewport rectangle. Multiple cameras can be added to an
|
|
8
|
+
* Emerald instance to render split-screen or picture-in-picture. The viewport
|
|
9
|
+
* is expressed in normalized [0..1] coordinates with the origin at the
|
|
10
|
+
* bottom-left of the canvas (matching WebGL).
|
|
11
|
+
*
|
|
12
|
+
* Backward compatible with the previous inline camera: position is stored
|
|
13
|
+
* negated internally, and setPosition/getPosition/setZoom/getZoom behave the
|
|
14
|
+
* same, so EventManager and CameraController are unaffected.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const left = new Camera({ viewport: { x: 0, y: 0, width: 0.5, height: 1 } });
|
|
18
|
+
* const right = new Camera({ viewport: { x: 0.5, y: 0, width: 0.5, height: 1 } });
|
|
19
|
+
* emerald.setCameras([left, right]);
|
|
20
|
+
*/
|
|
21
|
+
class Camera {
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.transform = new Transform(new Vector3(0, 0, 0), 0, new Vector2(1, 1));
|
|
24
|
+
|
|
25
|
+
const vp = options.viewport || {};
|
|
26
|
+
this.viewport = {
|
|
27
|
+
x: vp.x ?? 0,
|
|
28
|
+
y: vp.y ?? 0,
|
|
29
|
+
width: vp.width ?? 1,
|
|
30
|
+
height: vp.height ?? 1,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
this.active = options.active !== false;
|
|
34
|
+
this.clearColor = options.clearColor || null;
|
|
35
|
+
|
|
36
|
+
this.ignoreLayers = new Set(options.ignoreLayers || []);
|
|
37
|
+
|
|
38
|
+
this.onlyLayers = options.onlyLayers ? new Set(options.onlyLayers) : null;
|
|
39
|
+
|
|
40
|
+
this.excludeFromPost = options.excludeFromPost === true;
|
|
41
|
+
|
|
42
|
+
this.pixelSnap = options.pixelSnap === true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @method setPixelSnap
|
|
47
|
+
* @description When true, the camera's rendered position is rounded to whole
|
|
48
|
+
* screen pixels each frame (the stored position stays smooth). Use it for
|
|
49
|
+
* pixel-art games to eliminate sub-pixel shimmer and tile seams while a
|
|
50
|
+
* smooth-follow camera moves. Ignored while the camera is rotated.
|
|
51
|
+
* @param {boolean} snap
|
|
52
|
+
* @returns {Camera} - this
|
|
53
|
+
*/
|
|
54
|
+
setPixelSnap(snap) {
|
|
55
|
+
this.pixelSnap = snap !== false;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @method setExcludeFromPost
|
|
61
|
+
* @description When true, this camera renders straight to the screen AFTER the
|
|
62
|
+
* post-processing pass instead of into the post-processed scene buffer. Use it
|
|
63
|
+
* for HUD/UI cameras so effects like bloom don't affect the interface.
|
|
64
|
+
* @param {boolean} exclude
|
|
65
|
+
* @returns {Camera} - this
|
|
66
|
+
*/
|
|
67
|
+
setExcludeFromPost(exclude) {
|
|
68
|
+
this.excludeFromPost = exclude !== false;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @method setOnlyLayers
|
|
74
|
+
* @description Restricts the camera to rendering only the given layers (or
|
|
75
|
+
* pass null to clear the restriction).
|
|
76
|
+
* @param {number[]|null} layers
|
|
77
|
+
* @returns {Camera} - this
|
|
78
|
+
*/
|
|
79
|
+
setOnlyLayers(layers) {
|
|
80
|
+
this.onlyLayers = layers ? new Set(layers) : null;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @method setIgnoreLayers
|
|
86
|
+
* @description Replaces the set of layers this camera skips when rendering.
|
|
87
|
+
* @param {number[]} layers - Layer indices to ignore
|
|
88
|
+
* @returns {Camera} - this
|
|
89
|
+
*/
|
|
90
|
+
setIgnoreLayers(layers) {
|
|
91
|
+
this.ignoreLayers = new Set(layers);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @method ignoreLayer
|
|
97
|
+
* @description Adds a single layer to the ignore set.
|
|
98
|
+
* @param {number} layer
|
|
99
|
+
* @returns {Camera} - this
|
|
100
|
+
*/
|
|
101
|
+
ignoreLayer(layer) {
|
|
102
|
+
this.ignoreLayers.add(layer);
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @method setPosition
|
|
108
|
+
* @description Sets the world-space center of the camera.
|
|
109
|
+
*/
|
|
110
|
+
setPosition(x, y, z = 0) {
|
|
111
|
+
this.transform.position.x = -x;
|
|
112
|
+
this.transform.position.y = -y;
|
|
113
|
+
this.transform.position.z = -z;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @method getPosition
|
|
118
|
+
* @description Returns the world-space center of the camera.
|
|
119
|
+
*/
|
|
120
|
+
getPosition() {
|
|
121
|
+
return {
|
|
122
|
+
x: -this.transform.position.x,
|
|
123
|
+
y: -this.transform.position.y,
|
|
124
|
+
z: -this.transform.position.z,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @method setZoom
|
|
130
|
+
* @description Sets the zoom (1 = default, >1 zooms in).
|
|
131
|
+
*/
|
|
132
|
+
setZoom(zoom) {
|
|
133
|
+
const z = Math.max(0.0001, zoom);
|
|
134
|
+
this.transform.scale.x = z;
|
|
135
|
+
this.transform.scale.y = z;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @method getZoom
|
|
140
|
+
* @returns {number}
|
|
141
|
+
*/
|
|
142
|
+
getZoom() {
|
|
143
|
+
return this.transform.scale.x;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @method setRotation
|
|
148
|
+
* @description Sets the camera rotation in radians.
|
|
149
|
+
*/
|
|
150
|
+
setRotation(radians) {
|
|
151
|
+
this.transform.rotation = radians;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @method getRotation
|
|
156
|
+
* @returns {number}
|
|
157
|
+
*/
|
|
158
|
+
getRotation() {
|
|
159
|
+
return this.transform.rotation;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @method setViewport
|
|
164
|
+
* @description Sets the normalized viewport rectangle (origin bottom-left).
|
|
165
|
+
* @returns {Camera} - this
|
|
166
|
+
*/
|
|
167
|
+
setViewport(x, y, width, height) {
|
|
168
|
+
this.viewport = { x, y, width, height };
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @method setActive
|
|
174
|
+
* @description Enables/disables rendering of this camera.
|
|
175
|
+
* @returns {Camera} - this
|
|
176
|
+
*/
|
|
177
|
+
setActive(active) {
|
|
178
|
+
this.active = active;
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export default Camera;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import MathUtils from "./MathUtils.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @class CameraController
|
|
5
|
+
* @description Drives an Emerald camera with follow, bounds clamping, zoom and
|
|
6
|
+
* screen shake. Construct it with `emerald.camera` and call `update(dt)` once
|
|
7
|
+
* per frame.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const cam = new CameraController(emerald.camera);
|
|
11
|
+
* cam.follow(player.gameObject, 0.1).setBounds(-1000, -1000, 1000, 1000);
|
|
12
|
+
* // in the loop: cam.update(deltaTime);
|
|
13
|
+
*/
|
|
14
|
+
class CameraController {
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} camera - The Emerald camera (e.g. emerald.camera)
|
|
17
|
+
*/
|
|
18
|
+
constructor(camera) {
|
|
19
|
+
this.camera = camera;
|
|
20
|
+
this.target = null;
|
|
21
|
+
this.smoothing = 1;
|
|
22
|
+
this.bounds = null;
|
|
23
|
+
|
|
24
|
+
const start = camera.getPosition ? camera.getPosition() : { x: 0, y: 0 };
|
|
25
|
+
this.position = { x: start.x, y: start.y };
|
|
26
|
+
|
|
27
|
+
this.shakeIntensity = 0;
|
|
28
|
+
this.shakeDuration = 0;
|
|
29
|
+
this.shakeElapsed = Infinity;
|
|
30
|
+
|
|
31
|
+
this.deadzone = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @method setDeadzone
|
|
36
|
+
* @description Sets a centered follow deadzone. The camera only scrolls when
|
|
37
|
+
* the target moves outside this box, giving the player room to move without
|
|
38
|
+
* the camera reacting to every step. Pass 0/0 or null to disable.
|
|
39
|
+
* @param {number} halfWidth - Horizontal half-extent in world units
|
|
40
|
+
* @param {number} halfHeight - Vertical half-extent in world units
|
|
41
|
+
* @returns {CameraController} - this
|
|
42
|
+
*/
|
|
43
|
+
setDeadzone(halfWidth, halfHeight) {
|
|
44
|
+
this.deadzone =
|
|
45
|
+
halfWidth || halfHeight
|
|
46
|
+
? { x: Math.abs(halfWidth || 0), y: Math.abs(halfHeight || 0) }
|
|
47
|
+
: null;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @method follow
|
|
53
|
+
* @description Follows a target each frame.
|
|
54
|
+
* @param {Object} target - GameObject/Instance (uses transform.position) or a { x, y }
|
|
55
|
+
* @param {number} smoothing - 0..1, higher snaps faster (1 = instant)
|
|
56
|
+
* @returns {CameraController} - this
|
|
57
|
+
*/
|
|
58
|
+
follow(target, smoothing = 0.1) {
|
|
59
|
+
this.target = target;
|
|
60
|
+
this.smoothing = smoothing;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @method stopFollow
|
|
66
|
+
* @description Stops following the current target.
|
|
67
|
+
* @returns {CameraController} - this
|
|
68
|
+
*/
|
|
69
|
+
stopFollow() {
|
|
70
|
+
this.target = null;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @method setBounds
|
|
76
|
+
* @description Clamps the camera center to a world-space rectangle.
|
|
77
|
+
* @returns {CameraController} - this
|
|
78
|
+
*/
|
|
79
|
+
setBounds(minX, minY, maxX, maxY) {
|
|
80
|
+
this.bounds = { minX, minY, maxX, maxY };
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @method clearBounds
|
|
86
|
+
* @description Removes bounds clamping.
|
|
87
|
+
* @returns {CameraController} - this
|
|
88
|
+
*/
|
|
89
|
+
clearBounds() {
|
|
90
|
+
this.bounds = null;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @method setZoom
|
|
96
|
+
* @description Sets the camera zoom (1 = default, >1 zooms in).
|
|
97
|
+
* @returns {CameraController} - this
|
|
98
|
+
*/
|
|
99
|
+
setZoom(zoom) {
|
|
100
|
+
if (this.camera.setZoom) this.camera.setZoom(zoom);
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @method getZoom
|
|
106
|
+
* @returns {number} - The current zoom
|
|
107
|
+
*/
|
|
108
|
+
getZoom() {
|
|
109
|
+
return this.camera.getZoom ? this.camera.getZoom() : 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @method setPosition
|
|
114
|
+
* @description Immediately moves the camera center.
|
|
115
|
+
* @returns {CameraController} - this
|
|
116
|
+
*/
|
|
117
|
+
setPosition(x, y) {
|
|
118
|
+
this.position.x = x;
|
|
119
|
+
this.position.y = y;
|
|
120
|
+
this.camera.setPosition(x, y, 0);
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @method shake
|
|
126
|
+
* @description Triggers a screen shake that decays over its duration.
|
|
127
|
+
* @param {number} intensity - Max offset in world units
|
|
128
|
+
* @param {number} duration - Duration in seconds
|
|
129
|
+
* @returns {CameraController} - this
|
|
130
|
+
*/
|
|
131
|
+
shake(intensity, duration) {
|
|
132
|
+
this.shakeIntensity = intensity;
|
|
133
|
+
this.shakeDuration = duration;
|
|
134
|
+
this.shakeElapsed = 0;
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @private */
|
|
139
|
+
_targetPosition() {
|
|
140
|
+
const t = this.target;
|
|
141
|
+
if (!t) return null;
|
|
142
|
+
if (t.transform && t.transform.position) return t.transform.position;
|
|
143
|
+
return t;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @method update
|
|
148
|
+
* @description Advances follow/shake and applies the result to the camera.
|
|
149
|
+
* @param {number} dt - Seconds since the last frame
|
|
150
|
+
*/
|
|
151
|
+
update(dt) {
|
|
152
|
+
const targetPos = this._targetPosition();
|
|
153
|
+
if (targetPos) {
|
|
154
|
+
let goalX = targetPos.x;
|
|
155
|
+
let goalY = targetPos.y;
|
|
156
|
+
if (this.deadzone) {
|
|
157
|
+
goalX = this.position.x;
|
|
158
|
+
goalY = this.position.y;
|
|
159
|
+
const dx = targetPos.x - this.position.x;
|
|
160
|
+
const dy = targetPos.y - this.position.y;
|
|
161
|
+
if (dx > this.deadzone.x) goalX = targetPos.x - this.deadzone.x;
|
|
162
|
+
else if (dx < -this.deadzone.x) goalX = targetPos.x + this.deadzone.x;
|
|
163
|
+
if (dy > this.deadzone.y) goalY = targetPos.y - this.deadzone.y;
|
|
164
|
+
else if (dy < -this.deadzone.y) goalY = targetPos.y + this.deadzone.y;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const s = MathUtils.clamp(this.smoothing, 0, 1);
|
|
168
|
+
const a = s >= 1 ? 1 : 1 - Math.pow(1 - s, dt * 60);
|
|
169
|
+
this.position.x = MathUtils.lerp(this.position.x, goalX, a);
|
|
170
|
+
this.position.y = MathUtils.lerp(this.position.y, goalY, a);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let x = this.position.x;
|
|
174
|
+
let y = this.position.y;
|
|
175
|
+
|
|
176
|
+
if (this.bounds) {
|
|
177
|
+
x = MathUtils.clamp(x, this.bounds.minX, this.bounds.maxX);
|
|
178
|
+
y = MathUtils.clamp(y, this.bounds.minY, this.bounds.maxY);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (this.shakeElapsed < this.shakeDuration) {
|
|
182
|
+
this.shakeElapsed += dt;
|
|
183
|
+
const falloff = Math.max(0, 1 - this.shakeElapsed / this.shakeDuration);
|
|
184
|
+
x += (Math.random() * 2 - 1) * this.shakeIntensity * falloff;
|
|
185
|
+
y += (Math.random() * 2 - 1) * this.shakeIntensity * falloff;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.camera.setPosition(x, y, 0);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default CameraController;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import Drawable from "./Drawable.js";
|
|
2
|
+
import GameObject from "./components/GameObject.js";
|
|
3
|
+
import GLManager from "./managers/GLManager.js";
|
|
4
|
+
import { initVertexBuffer } from "./GLUtils.js";
|
|
5
|
+
import { Vector2, Vector3 } from "./Physics.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @class CanvasText
|
|
9
|
+
* @description Renders dynamic text using a system font by drawing to an
|
|
10
|
+
* offscreen 2D canvas and uploading it as a texture. Unlike BitmapText it needs
|
|
11
|
+
* no font sheet and supports any installed font. It's a Drawable component;
|
|
12
|
+
* `CanvasText.create()` returns a ready GameObject sized to the text.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const label = CanvasText.create("Score: 0", {
|
|
16
|
+
* font: "bold 28px monospace", color: "#6ee7b7", screenSpace: true,
|
|
17
|
+
* });
|
|
18
|
+
* label.transform.position = new Vector3(-300, 260, 100);
|
|
19
|
+
* scene.add(label);
|
|
20
|
+
* label.getComponent(CanvasText).setText("Score: 120");
|
|
21
|
+
*/
|
|
22
|
+
class CanvasText extends Drawable {
|
|
23
|
+
constructor(text, options = {}) {
|
|
24
|
+
const gl = GLManager.getGL();
|
|
25
|
+
const vertices = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];
|
|
26
|
+
const verticesBuffer = initVertexBuffer(gl, vertices);
|
|
27
|
+
const texCoords = [1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0];
|
|
28
|
+
const texCoordBuffer = initVertexBuffer(gl, texCoords);
|
|
29
|
+
|
|
30
|
+
super(
|
|
31
|
+
gl,
|
|
32
|
+
GLManager.getProgramInfo(),
|
|
33
|
+
verticesBuffer,
|
|
34
|
+
texCoordBuffer,
|
|
35
|
+
vertices,
|
|
36
|
+
false,
|
|
37
|
+
"",
|
|
38
|
+
0,
|
|
39
|
+
0,
|
|
40
|
+
1,
|
|
41
|
+
1,
|
|
42
|
+
0,
|
|
43
|
+
false,
|
|
44
|
+
false,
|
|
45
|
+
options.useLighting === true
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
this.font = options.font || "24px sans-serif";
|
|
49
|
+
this.fillStyle = options.color || "#ffffff";
|
|
50
|
+
this.padding = options.padding != null ? options.padding : 6;
|
|
51
|
+
this.maxWidth = options.maxWidth || 0;
|
|
52
|
+
this.align = options.align || "center";
|
|
53
|
+
this.lineHeight = options.lineHeight || 0;
|
|
54
|
+
/** @private */
|
|
55
|
+
this._canvas = document.createElement("canvas");
|
|
56
|
+
/** @private */
|
|
57
|
+
this._ctx = this._canvas.getContext("2d");
|
|
58
|
+
|
|
59
|
+
this.useTexture = true;
|
|
60
|
+
this.texture = gl.createTexture();
|
|
61
|
+
this.width = 1;
|
|
62
|
+
this.height = 1;
|
|
63
|
+
|
|
64
|
+
this.setText(text);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @method setText
|
|
69
|
+
* @description Re-renders the text and re-uploads the texture. If attached to
|
|
70
|
+
* a GameObject, its scale is updated to match the new size.
|
|
71
|
+
* @param {string} text
|
|
72
|
+
*/
|
|
73
|
+
setText(text) {
|
|
74
|
+
this.text = text;
|
|
75
|
+
const ctx = this._ctx;
|
|
76
|
+
const dpr = (typeof window !== "undefined" && window.devicePixelRatio) || 1;
|
|
77
|
+
|
|
78
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
79
|
+
ctx.font = this.font;
|
|
80
|
+
const fontSize = parseInt(this.font, 10) || 24;
|
|
81
|
+
const lineHeight = this.lineHeight || Math.ceil(fontSize * 1.4);
|
|
82
|
+
|
|
83
|
+
const lines = CanvasText.wrapText(
|
|
84
|
+
(s) => ctx.measureText(s).width,
|
|
85
|
+
String(text),
|
|
86
|
+
this.maxWidth
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
let maxLineW = 1;
|
|
90
|
+
for (const ln of lines) {
|
|
91
|
+
const m = ctx.measureText(ln);
|
|
92
|
+
let lw = m.width;
|
|
93
|
+
if (m.actualBoundingBoxLeft != null && m.actualBoundingBoxRight != null) {
|
|
94
|
+
lw = Math.max(lw, m.actualBoundingBoxLeft + m.actualBoundingBoxRight);
|
|
95
|
+
}
|
|
96
|
+
maxLineW = Math.max(maxLineW, lw);
|
|
97
|
+
}
|
|
98
|
+
const contentW = this.maxWidth
|
|
99
|
+
? Math.max(maxLineW, this.maxWidth)
|
|
100
|
+
: maxLineW;
|
|
101
|
+
const w = Math.max(1, Math.ceil(contentW) + this.padding * 2);
|
|
102
|
+
const h = lines.length * lineHeight + this.padding * 2;
|
|
103
|
+
|
|
104
|
+
this._canvas.width = Math.max(1, Math.round(w * dpr));
|
|
105
|
+
this._canvas.height = Math.max(1, Math.round(h * dpr));
|
|
106
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
107
|
+
ctx.clearRect(0, 0, w, h);
|
|
108
|
+
ctx.font = this.font;
|
|
109
|
+
ctx.textBaseline = "middle";
|
|
110
|
+
ctx.textAlign = this.align;
|
|
111
|
+
ctx.fillStyle = this.fillStyle;
|
|
112
|
+
|
|
113
|
+
const x =
|
|
114
|
+
this.align === "left"
|
|
115
|
+
? this.padding
|
|
116
|
+
: this.align === "right"
|
|
117
|
+
? w - this.padding
|
|
118
|
+
: w / 2;
|
|
119
|
+
for (let i = 0; i < lines.length; i++) {
|
|
120
|
+
const y = this.padding + lineHeight * (i + 0.5);
|
|
121
|
+
ctx.fillText(lines[i], x, y);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const gl = this.gl;
|
|
125
|
+
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
126
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
127
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
128
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
129
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
130
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
131
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
132
|
+
gl.texImage2D(
|
|
133
|
+
gl.TEXTURE_2D,
|
|
134
|
+
0,
|
|
135
|
+
gl.RGBA,
|
|
136
|
+
gl.RGBA,
|
|
137
|
+
gl.UNSIGNED_BYTE,
|
|
138
|
+
this._canvas
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
this.textureWidth = this._canvas.width;
|
|
142
|
+
this.textureHeight = this._canvas.height;
|
|
143
|
+
this.originalTexture = this.texture;
|
|
144
|
+
this.width = w;
|
|
145
|
+
this.height = h;
|
|
146
|
+
/** @private */
|
|
147
|
+
this._lastTextureWidth = -1;
|
|
148
|
+
|
|
149
|
+
if (this.parentObject && this.parentObject.transform) {
|
|
150
|
+
this.parentObject.transform.scale.x = w / 2;
|
|
151
|
+
this.parentObject.transform.scale.y = h / 2;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @method wrapText
|
|
157
|
+
* @description Pure helper that breaks `text` into rendered lines. Honors
|
|
158
|
+
* explicit `\n` line breaks and, when `maxWidth > 0`, greedily word-wraps each
|
|
159
|
+
* paragraph so no line measures wider than `maxWidth`. A single word longer
|
|
160
|
+
* than `maxWidth` is kept on its own line rather than being split.
|
|
161
|
+
* @param {(s:string)=>number} measure - Returns the pixel width of a string
|
|
162
|
+
* @param {string} text - The text to lay out
|
|
163
|
+
* @param {number} maxWidth - Wrap width in pixels (0 disables wrapping)
|
|
164
|
+
* @returns {string[]} - The lines to render top to bottom
|
|
165
|
+
*/
|
|
166
|
+
static wrapText(measure, text, maxWidth) {
|
|
167
|
+
const hardLines = text.split("\n");
|
|
168
|
+
if (!maxWidth || maxWidth <= 0) return hardLines;
|
|
169
|
+
const out = [];
|
|
170
|
+
for (const hard of hardLines) {
|
|
171
|
+
const words = hard.split(" ");
|
|
172
|
+
let line = "";
|
|
173
|
+
for (const word of words) {
|
|
174
|
+
const test = line ? line + " " + word : word;
|
|
175
|
+
if (line && measure(test) > maxWidth) {
|
|
176
|
+
out.push(line);
|
|
177
|
+
line = word;
|
|
178
|
+
} else {
|
|
179
|
+
line = test;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
out.push(line);
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @method setMaxWidth
|
|
189
|
+
* @description Sets the wrap width in pixels (0 disables wrapping) and
|
|
190
|
+
* re-renders.
|
|
191
|
+
* @param {number} px
|
|
192
|
+
*/
|
|
193
|
+
setMaxWidth(px) {
|
|
194
|
+
this.maxWidth = px || 0;
|
|
195
|
+
this.setText(this.text);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @method setAlign
|
|
200
|
+
* @description Sets horizontal alignment ("left" | "center" | "right") and
|
|
201
|
+
* re-renders.
|
|
202
|
+
* @param {string} align
|
|
203
|
+
*/
|
|
204
|
+
setAlign(align) {
|
|
205
|
+
this.align = align;
|
|
206
|
+
this.setText(this.text);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @method setColor
|
|
211
|
+
* @description Sets the text color (CSS color string) and re-renders.
|
|
212
|
+
* @param {string} cssColor
|
|
213
|
+
*/
|
|
214
|
+
setColor(cssColor) {
|
|
215
|
+
this.fillStyle = cssColor;
|
|
216
|
+
this.setText(this.text);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @method getSize
|
|
221
|
+
* @returns {{width:number, height:number}} - The canvas pixel size
|
|
222
|
+
*/
|
|
223
|
+
getSize() {
|
|
224
|
+
return { width: this.width, height: this.height };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @method _restoreGL
|
|
229
|
+
* @description After a context loss: rebuild the base buffers, recreate this
|
|
230
|
+
* text's own texture, and re-render the current string into it.
|
|
231
|
+
* @private
|
|
232
|
+
*/
|
|
233
|
+
_restoreGL() {
|
|
234
|
+
if (this._disposed) return;
|
|
235
|
+
super._restoreGL();
|
|
236
|
+
if (!this.gl) return;
|
|
237
|
+
this._canvas = this._canvas || document.createElement("canvas");
|
|
238
|
+
this._ctx = this._canvas.getContext("2d");
|
|
239
|
+
this.texture = this.gl.createTexture();
|
|
240
|
+
this.setText(this.text);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @method dispose
|
|
245
|
+
* @description Frees the GL texture this text owns (CanvasText renders into
|
|
246
|
+
* its own texture, unlike path-loaded Textures which are shared) plus the
|
|
247
|
+
* base Drawable buffers. Safe to call twice.
|
|
248
|
+
*/
|
|
249
|
+
dispose() {
|
|
250
|
+
if (this._disposed) return;
|
|
251
|
+
if (this.gl && this.texture) this.gl.deleteTexture(this.texture);
|
|
252
|
+
this.texture = null;
|
|
253
|
+
this.originalTexture = null;
|
|
254
|
+
this._canvas = null;
|
|
255
|
+
this._ctx = null;
|
|
256
|
+
super.dispose();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @method create
|
|
261
|
+
* @description Convenience factory returning a GameObject sized to the text.
|
|
262
|
+
* @param {string} text
|
|
263
|
+
* @param {Object} [options] - { name, position, screenSpace, font, color,
|
|
264
|
+
* maxWidth, align, lineHeight, padding }
|
|
265
|
+
* @returns {GameObject}
|
|
266
|
+
*/
|
|
267
|
+
static create(text, options = {}) {
|
|
268
|
+
const component = new CanvasText(text, options);
|
|
269
|
+
const go = new GameObject(
|
|
270
|
+
options.name || `text:${text}`,
|
|
271
|
+
options.position || new Vector3(0, 0, 0),
|
|
272
|
+
0,
|
|
273
|
+
new Vector2(component.width / 2, component.height / 2)
|
|
274
|
+
);
|
|
275
|
+
go.addComponent(component);
|
|
276
|
+
if (options.screenSpace) go.setScreenSpace(true);
|
|
277
|
+
return go;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export default CanvasText;
|