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
|
@@ -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;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class CollisionLayers
|
|
3
|
+
* @description A small registry that maps human-readable layer names to the
|
|
4
|
+
* category bits the physics engine uses for collision filtering, so games can
|
|
5
|
+
* "players collide with ground and enemies, but not with each other" without
|
|
6
|
+
* juggling raw bitmasks.
|
|
7
|
+
*
|
|
8
|
+
* Two fixtures collide only if each one's category is in the other's mask, so
|
|
9
|
+
* filtering is symmetric by construction. Up to 16 distinct layers are
|
|
10
|
+
* supported (filter bits are 16-bit).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* CollisionLayers.define("ground", "player", "enemy", "pickup");
|
|
14
|
+
* playerCollider.setCategory("player");
|
|
15
|
+
* playerCollider.setCollidesWith(["ground", "enemy", "pickup"]);
|
|
16
|
+
* enemyCollider.setCategory("enemy");
|
|
17
|
+
* enemyCollider.setCollidesWith(["ground", "player"]); // enemies ignore each other
|
|
18
|
+
*/
|
|
19
|
+
const ALL_BITS = 0xffff;
|
|
20
|
+
|
|
21
|
+
class CollisionLayers {
|
|
22
|
+
/**
|
|
23
|
+
* @method define
|
|
24
|
+
* @description Registers one or more named layers (in order), assigning each
|
|
25
|
+
* the next free category bit. Re-defining an existing name returns its
|
|
26
|
+
* existing bit. Returns the bit of the last name passed.
|
|
27
|
+
* @param {...string} names
|
|
28
|
+
* @returns {number}
|
|
29
|
+
*/
|
|
30
|
+
static define(...names) {
|
|
31
|
+
let last = 0;
|
|
32
|
+
for (const name of names) last = CollisionLayers.bit(name);
|
|
33
|
+
return last;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @method bit
|
|
38
|
+
* @description Returns the category bit for a layer name, registering it on
|
|
39
|
+
* first use.
|
|
40
|
+
* @param {string} name
|
|
41
|
+
* @returns {number}
|
|
42
|
+
*/
|
|
43
|
+
static bit(name) {
|
|
44
|
+
if (CollisionLayers._bits.has(name)) return CollisionLayers._bits.get(name);
|
|
45
|
+
if (CollisionLayers._next > 15) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"[CollisionLayers] Exceeded 16 collision layers (filter bit limit)."
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const bit = 1 << CollisionLayers._next++;
|
|
51
|
+
CollisionLayers._bits.set(name, bit);
|
|
52
|
+
return bit;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @method mask
|
|
57
|
+
* @description Computes a combined mask from layer names. Accepts a single
|
|
58
|
+
* name, an array of names, a number (passed through), or "all"/null for
|
|
59
|
+
* "collide with everything".
|
|
60
|
+
* @param {string|string[]|number|null} layers
|
|
61
|
+
* @returns {number}
|
|
62
|
+
*/
|
|
63
|
+
static mask(layers) {
|
|
64
|
+
if (layers == null || layers === "all") return ALL_BITS;
|
|
65
|
+
if (typeof layers === "number") return layers;
|
|
66
|
+
const list = Array.isArray(layers) ? layers : [layers];
|
|
67
|
+
let m = 0;
|
|
68
|
+
for (const name of list) m |= CollisionLayers.bit(name);
|
|
69
|
+
return m;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @method reset
|
|
74
|
+
* @description Clears all registered layers (mainly for tests).
|
|
75
|
+
*/
|
|
76
|
+
static reset() {
|
|
77
|
+
CollisionLayers._bits.clear();
|
|
78
|
+
CollisionLayers._next = 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
CollisionLayers._bits = new Map();
|
|
83
|
+
CollisionLayers._next = 0;
|
|
84
|
+
CollisionLayers.ALL = ALL_BITS;
|
|
85
|
+
|
|
86
|
+
export default CollisionLayers;
|
package/src/Color.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @class Color
|
|
3
|
-
* @description Represents a color
|
|
4
|
-
* @param {number} r - The red value
|
|
5
|
-
* @param {number} g - The green value
|
|
6
|
-
* @param {number} b - The blue value
|
|
7
|
-
* @param {number} a - The alpha value
|
|
8
|
-
*/
|
|
9
|
-
class Color {
|
|
10
|
-
constructor(r, g, b, a = 255) {
|
|
11
|
-
this.r = r;
|
|
12
|
-
this.g = g;
|
|
13
|
-
this.b = b;
|
|
14
|
-
this.a = a;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @class Color
|
|
3
|
+
* @description Represents a color
|
|
4
|
+
* @param {number} r - The red value
|
|
5
|
+
* @param {number} g - The green value
|
|
6
|
+
* @param {number} b - The blue value
|
|
7
|
+
* @param {number} a - The alpha value
|
|
8
|
+
*/
|
|
9
|
+
class Color {
|
|
10
|
+
constructor(r, g, b, a = 255) {
|
|
11
|
+
this.r = r;
|
|
12
|
+
this.g = g;
|
|
13
|
+
this.b = b;
|
|
14
|
+
this.a = a;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static fromHex(hex) {
|
|
18
|
+
const value = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
19
|
+
const r = parseInt(value.slice(0, 2), 16);
|
|
20
|
+
const g = parseInt(value.slice(2, 4), 16);
|
|
21
|
+
const b = parseInt(value.slice(4, 6), 16);
|
|
22
|
+
return new Color(r, g, b, 255);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default Color;
|