emeraldengine 2.2.0 → 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 -555
- 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
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import GameObject from "./components/GameObject.js";
|
|
2
|
+
import Texture from "./Texture.js";
|
|
3
|
+
import Color from "./Color.js";
|
|
4
|
+
import { Vector2, Vector3 } from "./Physics.js";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CAPACITY = 256;
|
|
7
|
+
const PARK_Y = -1e9;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @class ParticleEmitter
|
|
11
|
+
* @description A reliable, allocation-free particle system built from a fixed
|
|
12
|
+
* pool of ordinary textured GameObjects. Each live particle's transform, tint
|
|
13
|
+
* and opacity are driven by hand every frame — there is no instanced-draw /
|
|
14
|
+
* dynamic-buffer lifecycle to desync, so it keeps drawing for the whole session
|
|
15
|
+
* (unlike the InstancedTexture-based `Particles`, which can stop emitting after
|
|
16
|
+
* heavy reuse on some GPUs).
|
|
17
|
+
*
|
|
18
|
+
* Spawn with `burst(n, cfg)` or `emit(cfg)`; advance with `update(dt)` once per
|
|
19
|
+
* frame. Every `cfg` field is optional and has a sensible default, so the
|
|
20
|
+
* minimal call is `emitter.burst(8, { x, y })`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const fx = new ParticleEmitter(scene, { texture: smokeImg, layer: 50 });
|
|
24
|
+
* fx.burst(12, {
|
|
25
|
+
* x: 100, y: 200, dir: -Math.PI / 2, spread: Math.PI, speed: 180,
|
|
26
|
+
* gy: -300, drag: 2, life: 0.4, size: 8, sFrom: 1, sTo: 0.1,
|
|
27
|
+
* aFrom: 0.7, aTo: 0, cr: 255, cg: 220, cb: 120, additive: true,
|
|
28
|
+
* shape: "circle", radius: 6,
|
|
29
|
+
* });
|
|
30
|
+
* // in the loop: fx.update(dt);
|
|
31
|
+
*/
|
|
32
|
+
class ParticleEmitter {
|
|
33
|
+
/**
|
|
34
|
+
* @param {Scene} scene - Scene the particle GameObjects are added to
|
|
35
|
+
* @param {Object} [options]
|
|
36
|
+
* @param {string|HTMLImageElement} [options.texture] - Particle sprite source
|
|
37
|
+
* @param {number} [options.capacity=256] - Pool size (max live particles)
|
|
38
|
+
* @param {number} [options.layer=0] - Render layer for every particle
|
|
39
|
+
* @param {boolean} [options.pixelart=false] - Pixel-art sampling for the sprite
|
|
40
|
+
* @param {Function} [options.spriteFactory] - `(texture, options) => Drawable`
|
|
41
|
+
* override used to build each particle's drawable (handy for tests / custom
|
|
42
|
+
* shaders). Defaults to a non-lit `Texture`.
|
|
43
|
+
*/
|
|
44
|
+
constructor(scene, options = {}) {
|
|
45
|
+
this.scene = scene;
|
|
46
|
+
this.capacity = options.capacity ?? DEFAULT_CAPACITY;
|
|
47
|
+
this.layer = options.layer ?? 0;
|
|
48
|
+
|
|
49
|
+
const texture = options.texture ?? options.texturePath ?? null;
|
|
50
|
+
const makeSprite =
|
|
51
|
+
options.spriteFactory ||
|
|
52
|
+
((tex) =>
|
|
53
|
+
new Texture(
|
|
54
|
+
tex,
|
|
55
|
+
0,
|
|
56
|
+
0,
|
|
57
|
+
1,
|
|
58
|
+
1,
|
|
59
|
+
0,
|
|
60
|
+
false,
|
|
61
|
+
options.pixelart ?? false,
|
|
62
|
+
false
|
|
63
|
+
));
|
|
64
|
+
|
|
65
|
+
this.parts = [];
|
|
66
|
+
this.free = [];
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < this.capacity; i++) {
|
|
69
|
+
const go = new GameObject(
|
|
70
|
+
"particle",
|
|
71
|
+
new Vector3(0, PARK_Y, 0),
|
|
72
|
+
0,
|
|
73
|
+
new Vector2(4, 4)
|
|
74
|
+
);
|
|
75
|
+
const drawable = makeSprite(texture, options);
|
|
76
|
+
if (drawable && typeof drawable.setUseLighting === "function") {
|
|
77
|
+
drawable.setUseLighting(false);
|
|
78
|
+
}
|
|
79
|
+
go.addComponent(drawable);
|
|
80
|
+
go.setLayer(this.layer);
|
|
81
|
+
go.setOpacity(0);
|
|
82
|
+
if (scene && typeof scene.add === "function") scene.add(go);
|
|
83
|
+
|
|
84
|
+
const p = {
|
|
85
|
+
go,
|
|
86
|
+
drawable,
|
|
87
|
+
color: new Color(255, 255, 255, 255),
|
|
88
|
+
alive: false,
|
|
89
|
+
x: 0,
|
|
90
|
+
y: 0,
|
|
91
|
+
vx: 0,
|
|
92
|
+
vy: 0,
|
|
93
|
+
gx: 0,
|
|
94
|
+
gy: 0,
|
|
95
|
+
drag: 0,
|
|
96
|
+
age: 0,
|
|
97
|
+
life: 1,
|
|
98
|
+
base: 4,
|
|
99
|
+
sFrom: 1,
|
|
100
|
+
sTo: 1,
|
|
101
|
+
aFrom: 1,
|
|
102
|
+
aTo: 0,
|
|
103
|
+
rot: 0,
|
|
104
|
+
rotSpeed: 0,
|
|
105
|
+
additive: false,
|
|
106
|
+
};
|
|
107
|
+
this.parts.push(p);
|
|
108
|
+
this.free.push(p);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @member activeCount
|
|
114
|
+
* @description Number of particles currently alive.
|
|
115
|
+
*/
|
|
116
|
+
get activeCount() {
|
|
117
|
+
return this.capacity - this.free.length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @method emit
|
|
122
|
+
* @description Spawns a single particle. Returns the emitter for chaining.
|
|
123
|
+
* @param {Object} cfg - Spawn config (see class example; all fields optional)
|
|
124
|
+
*/
|
|
125
|
+
emit(cfg = {}) {
|
|
126
|
+
this._spawn(cfg);
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @method burst
|
|
132
|
+
* @description Spawns `n` particles with the same config.
|
|
133
|
+
* @param {number} n - How many to spawn
|
|
134
|
+
* @param {Object} cfg - Spawn config
|
|
135
|
+
*/
|
|
136
|
+
burst(n, cfg = {}) {
|
|
137
|
+
for (let i = 0; i < n; i++) this._spawn(cfg);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @private */
|
|
142
|
+
_spawn(cfg) {
|
|
143
|
+
const p = this.free.pop();
|
|
144
|
+
if (!p) return;
|
|
145
|
+
|
|
146
|
+
p.alive = true;
|
|
147
|
+
|
|
148
|
+
const x = cfg.x || 0;
|
|
149
|
+
const y = cfg.y || 0;
|
|
150
|
+
let ox = 0;
|
|
151
|
+
let oy = 0;
|
|
152
|
+
const shape = cfg.shape;
|
|
153
|
+
if (shape === "ring") {
|
|
154
|
+
const a = Math.random() * Math.PI * 2;
|
|
155
|
+
const r = cfg.radius || 0;
|
|
156
|
+
ox = Math.cos(a) * r;
|
|
157
|
+
oy = Math.sin(a) * r;
|
|
158
|
+
} else if (shape === "circle") {
|
|
159
|
+
const a = Math.random() * Math.PI * 2;
|
|
160
|
+
const r = Math.sqrt(Math.random()) * (cfg.radius || 0);
|
|
161
|
+
ox = Math.cos(a) * r;
|
|
162
|
+
oy = Math.sin(a) * r;
|
|
163
|
+
} else if (shape === "box") {
|
|
164
|
+
ox = (Math.random() - 0.5) * (cfg.boxw || 0);
|
|
165
|
+
oy = (Math.random() - 0.5) * (cfg.boxh || 0);
|
|
166
|
+
}
|
|
167
|
+
p.x = x + ox;
|
|
168
|
+
p.y = y + oy;
|
|
169
|
+
|
|
170
|
+
const dir = cfg.dir || 0;
|
|
171
|
+
const spread = cfg.spread || 0;
|
|
172
|
+
const speed = cfg.speed || 0;
|
|
173
|
+
const ang = dir + (Math.random() - 0.5) * spread;
|
|
174
|
+
const spd = speed * (0.6 + Math.random() * 0.4);
|
|
175
|
+
p.vx = Math.cos(ang) * spd;
|
|
176
|
+
p.vy = Math.sin(ang) * spd;
|
|
177
|
+
|
|
178
|
+
p.gx = cfg.gx || 0;
|
|
179
|
+
p.gy = cfg.gy || 0;
|
|
180
|
+
p.drag = cfg.drag || 0;
|
|
181
|
+
p.age = 0;
|
|
182
|
+
p.life = cfg.life || 0.5;
|
|
183
|
+
p.base = cfg.size ?? 6;
|
|
184
|
+
p.sFrom = cfg.sFrom ?? 1;
|
|
185
|
+
p.sTo = cfg.sTo ?? 0;
|
|
186
|
+
p.aFrom = cfg.aFrom ?? 1;
|
|
187
|
+
p.aTo = cfg.aTo ?? 0;
|
|
188
|
+
p.rot = cfg.rot ?? Math.random() * Math.PI * 2;
|
|
189
|
+
p.rotSpeed = cfg.rotSpeed || 0;
|
|
190
|
+
|
|
191
|
+
p.color.r = cfg.cr ?? 255;
|
|
192
|
+
p.color.g = cfg.cg ?? 255;
|
|
193
|
+
p.color.b = cfg.cb ?? 255;
|
|
194
|
+
p.color.a = 255;
|
|
195
|
+
if (p.drawable) {
|
|
196
|
+
if (typeof p.drawable.setColor === "function")
|
|
197
|
+
p.drawable.setColor(p.color);
|
|
198
|
+
if (typeof p.drawable.setBlendMode === "function") {
|
|
199
|
+
p.drawable.setBlendMode(cfg.additive ? "additive" : "normal");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const tr = p.go.transform;
|
|
204
|
+
tr.position.x = p.x;
|
|
205
|
+
tr.position.y = p.y;
|
|
206
|
+
tr.scale.x = p.base * p.sFrom;
|
|
207
|
+
tr.scale.y = p.base * p.sFrom;
|
|
208
|
+
tr.rotation = p.rot;
|
|
209
|
+
p.go.setOpacity(p.aFrom);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @method update
|
|
214
|
+
* @description Advances and renders every live particle. Call once per frame.
|
|
215
|
+
* @param {number} dt - Seconds since the last frame
|
|
216
|
+
*/
|
|
217
|
+
update(dt) {
|
|
218
|
+
for (let i = 0; i < this.parts.length; i++) {
|
|
219
|
+
const p = this.parts[i];
|
|
220
|
+
if (!p.alive) continue;
|
|
221
|
+
|
|
222
|
+
p.age += dt;
|
|
223
|
+
if (p.age >= p.life) {
|
|
224
|
+
p.alive = false;
|
|
225
|
+
p.go.setOpacity(0);
|
|
226
|
+
p.go.transform.position.y = PARK_Y;
|
|
227
|
+
this.free.push(p);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const t = p.age / p.life;
|
|
232
|
+
p.vx += p.gx * dt;
|
|
233
|
+
p.vy += p.gy * dt;
|
|
234
|
+
if (p.drag) {
|
|
235
|
+
const d = Math.max(0, 1 - p.drag * dt);
|
|
236
|
+
p.vx *= d;
|
|
237
|
+
p.vy *= d;
|
|
238
|
+
}
|
|
239
|
+
p.x += p.vx * dt;
|
|
240
|
+
p.y += p.vy * dt;
|
|
241
|
+
p.rot += p.rotSpeed * dt;
|
|
242
|
+
|
|
243
|
+
const sc = p.base * (p.sFrom + (p.sTo - p.sFrom) * t);
|
|
244
|
+
const tr = p.go.transform;
|
|
245
|
+
tr.position.x = p.x;
|
|
246
|
+
tr.position.y = p.y;
|
|
247
|
+
tr.scale.x = sc;
|
|
248
|
+
tr.scale.y = sc;
|
|
249
|
+
tr.rotation = p.rot;
|
|
250
|
+
p.go.setOpacity(p.aFrom + (p.aTo - p.aFrom) * t);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @method reset
|
|
256
|
+
* @description Kills every live particle immediately (no fade).
|
|
257
|
+
*/
|
|
258
|
+
reset() {
|
|
259
|
+
for (let i = 0; i < this.parts.length; i++) {
|
|
260
|
+
const p = this.parts[i];
|
|
261
|
+
if (!p.alive) continue;
|
|
262
|
+
p.alive = false;
|
|
263
|
+
p.go.setOpacity(0);
|
|
264
|
+
p.go.transform.position.y = PARK_Y;
|
|
265
|
+
}
|
|
266
|
+
this.free = this.parts.slice();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @method destroy
|
|
271
|
+
* @description Removes every particle GameObject from the scene.
|
|
272
|
+
*/
|
|
273
|
+
destroy() {
|
|
274
|
+
if (this.scene && typeof this.scene.remove === "function") {
|
|
275
|
+
for (let i = 0; i < this.parts.length; i++) {
|
|
276
|
+
this.scene.remove(this.parts[i].go);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this.parts = [];
|
|
280
|
+
this.free = [];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export default ParticleEmitter;
|
package/src/Physics.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import * as planck from "planck";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @function dispatchCollision
|
|
5
|
+
* @description Calls onCollisionEnter/onCollisionExit on an object and its
|
|
6
|
+
* component list (Behaviours), passing the other object. Duck-typed so this
|
|
7
|
+
* module doesn't need to import the component classes.
|
|
8
|
+
* @private
|
|
9
|
+
*/
|
|
10
|
+
function dispatchCollision(object, other, contact, type) {
|
|
11
|
+
if (!object) return;
|
|
12
|
+
if (typeof object[type] === "function") {
|
|
13
|
+
object[type](other, contact);
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(object.components)) {
|
|
16
|
+
for (const comp of object.components) {
|
|
17
|
+
if (comp && typeof comp[type] === "function") {
|
|
18
|
+
comp[type](other, contact);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
/**
|
|
4
25
|
* @class Physics
|
|
5
26
|
* @description Represents the physics engine
|
|
@@ -15,6 +36,101 @@ class Physics {
|
|
|
15
36
|
planck.Settings.velocityThreshold = velocityThreshold;
|
|
16
37
|
this.gravity = gravity;
|
|
17
38
|
this.scale = scale;
|
|
39
|
+
|
|
40
|
+
this.fixedTimeStep = 1 / 60;
|
|
41
|
+
this.maxSubSteps = 5;
|
|
42
|
+
/** @private */
|
|
43
|
+
this._accumulator = 0;
|
|
44
|
+
|
|
45
|
+
this._dispatchContacts();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @method _dispatchContacts
|
|
50
|
+
* @description Routes planck contacts to the owning objects so Behaviour
|
|
51
|
+
* components receive onCollisionEnter/onCollisionExit. Bodies created through
|
|
52
|
+
* RigidBody carry the owner via userData.
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
_dispatchContacts() {
|
|
56
|
+
const fire = (contact, type) => {
|
|
57
|
+
const a = contact.getFixtureA().getBody().getUserData();
|
|
58
|
+
const b = contact.getFixtureB().getBody().getUserData();
|
|
59
|
+
const objA = a && a.parentObject ? a.parentObject : null;
|
|
60
|
+
const objB = b && b.parentObject ? b.parentObject : null;
|
|
61
|
+
dispatchCollision(objA, objB, contact, type);
|
|
62
|
+
dispatchCollision(objB, objA, contact, type);
|
|
63
|
+
};
|
|
64
|
+
this.world.on("begin-contact", (contact) =>
|
|
65
|
+
fire(contact, "onCollisionEnter")
|
|
66
|
+
);
|
|
67
|
+
this.world.on("end-contact", (contact) => fire(contact, "onCollisionExit"));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @method raycast
|
|
72
|
+
* @description Casts a ray through the world and returns the closest hit.
|
|
73
|
+
* @param {Object} origin - World-space { x, y } start point
|
|
74
|
+
* @param {Object} direction - Ray direction { x, y } (need not be normalized)
|
|
75
|
+
* @param {number} maxDistance - Max ray length in world units
|
|
76
|
+
* @returns {{object, rigidBody, point, normal, fraction}|null}
|
|
77
|
+
*/
|
|
78
|
+
raycast(origin, direction, maxDistance) {
|
|
79
|
+
const scale = this.scale;
|
|
80
|
+
const len = Math.hypot(direction.x, direction.y) || 1;
|
|
81
|
+
const dx = direction.x / len;
|
|
82
|
+
const dy = direction.y / len;
|
|
83
|
+
const p1 = new planck.Vec2(origin.x / scale, origin.y / scale);
|
|
84
|
+
const p2 = new planck.Vec2(
|
|
85
|
+
(origin.x + dx * maxDistance) / scale,
|
|
86
|
+
(origin.y + dy * maxDistance) / scale
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
let result = null;
|
|
90
|
+
this.world.rayCast(p1, p2, (fixture, point, normal, fraction) => {
|
|
91
|
+
const rb = fixture.getBody().getUserData();
|
|
92
|
+
result = {
|
|
93
|
+
object: rb && rb.parentObject ? rb.parentObject : null,
|
|
94
|
+
rigidBody: rb || null,
|
|
95
|
+
point: { x: point.x * scale, y: point.y * scale },
|
|
96
|
+
normal: { x: normal.x, y: normal.y },
|
|
97
|
+
fraction,
|
|
98
|
+
};
|
|
99
|
+
return fraction;
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @method queryPoint
|
|
106
|
+
* @description Returns the objects whose colliders contain a world-space point.
|
|
107
|
+
* @param {Object} point - World-space { x, y }
|
|
108
|
+
* @returns {Array} - Owning objects at the point
|
|
109
|
+
*/
|
|
110
|
+
queryPoint(point) {
|
|
111
|
+
const p = new planck.Vec2(point.x / this.scale, point.y / this.scale);
|
|
112
|
+
const results = [];
|
|
113
|
+
for (let body = this.world.getBodyList(); body; body = body.getNext()) {
|
|
114
|
+
for (let f = body.getFixtureList(); f; f = f.getNext()) {
|
|
115
|
+
if (f.testPoint(p)) {
|
|
116
|
+
const rb = body.getUserData();
|
|
117
|
+
if (rb && rb.parentObject) results.push(rb.parentObject);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @method setFixedTimeStep
|
|
127
|
+
* @description Sets the fixed physics step (seconds) and optional substep cap.
|
|
128
|
+
* @param {number} step - Fixed step in seconds (e.g. 1/60)
|
|
129
|
+
* @param {number} [maxSubSteps] - Max steps per process() call (spiral guard)
|
|
130
|
+
*/
|
|
131
|
+
setFixedTimeStep(step, maxSubSteps = this.maxSubSteps) {
|
|
132
|
+
if (step > 0) this.fixedTimeStep = step;
|
|
133
|
+
this.maxSubSteps = maxSubSteps;
|
|
18
134
|
}
|
|
19
135
|
|
|
20
136
|
/**
|
|
@@ -100,7 +216,22 @@ class Physics {
|
|
|
100
216
|
* @param {number} dt - The delta time
|
|
101
217
|
*/
|
|
102
218
|
process(dt) {
|
|
103
|
-
|
|
219
|
+
if (!isFinite(dt) || dt <= 0) return;
|
|
220
|
+
|
|
221
|
+
this._accumulator += dt;
|
|
222
|
+
let steps = 0;
|
|
223
|
+
while (
|
|
224
|
+
this._accumulator >= this.fixedTimeStep &&
|
|
225
|
+
steps < this.maxSubSteps
|
|
226
|
+
) {
|
|
227
|
+
this.world.step(this.fixedTimeStep);
|
|
228
|
+
this._accumulator -= this.fixedTimeStep;
|
|
229
|
+
steps++;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (steps === this.maxSubSteps) {
|
|
233
|
+
this._accumulator = 0;
|
|
234
|
+
}
|
|
104
235
|
}
|
|
105
236
|
|
|
106
237
|
/**
|
|
@@ -111,6 +242,7 @@ class Physics {
|
|
|
111
242
|
this.world = new planck.World({
|
|
112
243
|
gravity: new planck.Vec2(0, this.gravity),
|
|
113
244
|
});
|
|
245
|
+
this._accumulator = 0;
|
|
114
246
|
}
|
|
115
247
|
|
|
116
248
|
/**
|
|
@@ -152,8 +284,31 @@ class Physics {
|
|
|
152
284
|
* @param {number} y - The y coordinate of the vector
|
|
153
285
|
*/
|
|
154
286
|
class Vector2 {
|
|
155
|
-
constructor(x, y) {
|
|
156
|
-
|
|
287
|
+
constructor(x = 0, y = 0) {
|
|
288
|
+
this.x = x;
|
|
289
|
+
this.y = y;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @method set
|
|
294
|
+
* @description Sets the x and y coordinates in place
|
|
295
|
+
* @param {number} x - The x coordinate
|
|
296
|
+
* @param {number} y - The y coordinate
|
|
297
|
+
* @returns {Vector2} - This vector
|
|
298
|
+
*/
|
|
299
|
+
set(x, y) {
|
|
300
|
+
this.x = x;
|
|
301
|
+
this.y = y;
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @method clone
|
|
307
|
+
* @description Returns a copy of the vector
|
|
308
|
+
* @returns {Vector2} - The cloned vector
|
|
309
|
+
*/
|
|
310
|
+
clone() {
|
|
311
|
+
return new Vector2(this.x, this.y);
|
|
157
312
|
}
|
|
158
313
|
|
|
159
314
|
/**
|
|
@@ -195,8 +350,34 @@ class Vector2 {
|
|
|
195
350
|
* @param {number} z - The z coordinate of the vector
|
|
196
351
|
*/
|
|
197
352
|
class Vector3 {
|
|
198
|
-
constructor(x, y, z) {
|
|
199
|
-
|
|
353
|
+
constructor(x = 0, y = 0, z = 0) {
|
|
354
|
+
this.x = x;
|
|
355
|
+
this.y = y;
|
|
356
|
+
this.z = z;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @method set
|
|
361
|
+
* @description Sets the x, y and z coordinates in place
|
|
362
|
+
* @param {number} x - The x coordinate
|
|
363
|
+
* @param {number} y - The y coordinate
|
|
364
|
+
* @param {number} z - The z coordinate
|
|
365
|
+
* @returns {Vector3} - This vector
|
|
366
|
+
*/
|
|
367
|
+
set(x, y, z) {
|
|
368
|
+
this.x = x;
|
|
369
|
+
this.y = y;
|
|
370
|
+
this.z = z;
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* @method clone
|
|
376
|
+
* @description Returns a copy of the vector
|
|
377
|
+
* @returns {Vector3} - The cloned vector
|
|
378
|
+
*/
|
|
379
|
+
clone() {
|
|
380
|
+
return new Vector3(this.x, this.y, this.z);
|
|
200
381
|
}
|
|
201
382
|
|
|
202
383
|
/**
|
package/src/Pool.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @class Pool
|
|
3
|
+
* @description Generic object pool to avoid per-frame allocations for churny
|
|
4
|
+
* objects (bullets, particles, enemies). Provide a factory and an optional
|
|
5
|
+
* reset function; acquire reused objects and release them when done.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const bullets = new Pool(() => new Bullet(), (b, x, y) => b.spawn(x, y), 50);
|
|
9
|
+
* const b = bullets.acquire(px, py);
|
|
10
|
+
* // later...
|
|
11
|
+
* bullets.release(b);
|
|
12
|
+
*/
|
|
13
|
+
class Pool {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Function} factory - Creates a new pooled object
|
|
16
|
+
* @param {Function} [reset] - Called on acquire as reset(obj, ...args)
|
|
17
|
+
* @param {number} [initialSize] - Number of objects to pre-create
|
|
18
|
+
*/
|
|
19
|
+
constructor(factory, reset = null, initialSize = 0) {
|
|
20
|
+
if (typeof factory !== "function") {
|
|
21
|
+
throw new Error("[Pool] > factory must be a function");
|
|
22
|
+
}
|
|
23
|
+
this.factory = factory;
|
|
24
|
+
this.reset = reset;
|
|
25
|
+
this.available = [];
|
|
26
|
+
this.active = new Set();
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < initialSize; i++) {
|
|
29
|
+
this.available.push(factory());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @method acquire
|
|
35
|
+
* @description Returns a pooled object (reused or freshly created). Extra
|
|
36
|
+
* arguments are forwarded to the reset function.
|
|
37
|
+
* @returns {*} - The acquired object
|
|
38
|
+
*/
|
|
39
|
+
acquire(...args) {
|
|
40
|
+
const obj = this.available.length ? this.available.pop() : this.factory();
|
|
41
|
+
this.active.add(obj);
|
|
42
|
+
if (this.reset) this.reset(obj, ...args);
|
|
43
|
+
return obj;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @method release
|
|
48
|
+
* @description Returns an object to the pool.
|
|
49
|
+
* @param {*} obj - The object to release
|
|
50
|
+
*/
|
|
51
|
+
release(obj) {
|
|
52
|
+
if (this.active.delete(obj)) {
|
|
53
|
+
this.available.push(obj);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @method releaseAll
|
|
59
|
+
* @description Returns every active object to the pool.
|
|
60
|
+
*/
|
|
61
|
+
releaseAll() {
|
|
62
|
+
for (const obj of this.active) {
|
|
63
|
+
this.available.push(obj);
|
|
64
|
+
}
|
|
65
|
+
this.active.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @member activeCount
|
|
70
|
+
* @description Number of currently-acquired objects.
|
|
71
|
+
*/
|
|
72
|
+
get activeCount() {
|
|
73
|
+
return this.active.size;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @member size
|
|
78
|
+
* @description Total objects managed by the pool (active + available).
|
|
79
|
+
*/
|
|
80
|
+
get size() {
|
|
81
|
+
return this.available.length + this.active.size;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default Pool;
|