emeraldengine 2.2.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2198 -968
- package/dist/types/index.d.ts +75 -32
- package/dist/types/src/Animator.d.ts +50 -0
- package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
- package/dist/types/src/Camera.d.ts +122 -0
- package/dist/types/src/CameraController.d.ts +107 -0
- package/dist/types/src/CanvasText.d.ts +91 -0
- package/dist/types/src/CollisionLayers.d.ts +58 -0
- package/dist/types/{Color.d.ts → src/Color.d.ts} +6 -6
- package/dist/types/src/Coroutine.d.ts +111 -0
- package/dist/types/src/DebugOverlay.d.ts +86 -0
- package/dist/types/src/Drawable.d.ts +271 -0
- package/dist/types/src/Easing.d.ts +22 -0
- package/dist/types/src/Emerald.d.ts +420 -0
- package/dist/types/src/EmeraldDB.d.ts +159 -0
- package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
- package/dist/types/src/GLUtils.d.ts +4 -0
- package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
- package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +75 -23
- package/dist/types/src/Interpolator.d.ts +66 -0
- package/dist/types/src/Material.d.ts +87 -0
- package/dist/types/src/MathUtils.d.ts +81 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/src/Physics.d.ts +337 -0
- package/dist/types/src/Pool.d.ts +53 -0
- package/dist/types/src/PostEffects.d.ts +68 -0
- package/dist/types/src/PostProcessor.d.ts +124 -0
- package/dist/types/src/RenderTarget.d.ts +56 -0
- package/dist/types/src/Scene.d.ts +62 -0
- package/dist/types/src/ScreenEffects.d.ts +111 -0
- package/dist/types/src/Serializer.d.ts +86 -0
- package/dist/types/src/Shaders.d.ts +2 -0
- package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
- package/dist/types/src/SpatialGrid.d.ts +50 -0
- package/dist/types/src/SpriteBatch.d.ts +89 -0
- package/dist/types/src/StateMachine.d.ts +59 -0
- package/dist/types/src/Storage.d.ts +89 -0
- package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
- package/dist/types/src/TextureAtlas.d.ts +55 -0
- package/dist/types/src/Tilemap.d.ts +91 -0
- package/dist/types/src/Time.d.ts +53 -0
- package/dist/types/src/Timer.d.ts +54 -0
- package/dist/types/src/Transform.d.ts +94 -0
- package/dist/types/src/Tween.d.ts +81 -0
- package/dist/types/src/UI.d.ts +121 -0
- package/dist/types/src/components/Behaviour.d.ts +71 -0
- package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
- package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
- package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
- package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
- package/dist/types/src/components/Collider.d.ts +102 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/PolygonCollider.d.ts +33 -0
- package/dist/types/src/components/RigidBody.d.ts +456 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -0
- package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
- package/dist/types/src/importers/TiledMap.d.ts +62 -0
- package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
- package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
- package/dist/types/src/managers/AssetManager.d.ts +116 -0
- package/dist/types/src/managers/AudioManager.d.ts +259 -0
- package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
- package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
- package/dist/types/src/managers/GLManager.d.ts +84 -0
- package/dist/types/src/managers/GLState.d.ts +37 -0
- package/dist/types/src/managers/Gamepad.d.ts +102 -0
- package/dist/types/src/managers/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +381 -0
- package/dist/types/src/managers/NetworkManager.d.ts +93 -0
- package/dist/types/src/managers/RenderStats.d.ts +34 -0
- package/dist/types/src/managers/SceneManager.d.ts +44 -0
- package/dist/types/src/managers/ShaderManager.d.ts +55 -0
- package/dist/types/src/managers/TextureManager.d.ts +102 -0
- package/dist/types/src/particlesystem/Particle.d.ts +64 -0
- package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
- package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
- package/dist/types/src/physics/AABB.d.ts +92 -0
- package/dist/types/src/physics/Body.d.ts +435 -0
- package/dist/types/src/physics/BodyType.d.ts +6 -0
- package/dist/types/src/physics/BroadPhase.d.ts +210 -0
- package/dist/types/src/physics/Collision.d.ts +102 -0
- package/dist/types/src/physics/Contact.d.ts +206 -0
- package/dist/types/src/physics/ContactSolver.d.ts +108 -0
- package/dist/types/src/physics/Distance.d.ts +54 -0
- package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
- package/dist/types/src/physics/Fixture.d.ts +221 -0
- package/dist/types/src/physics/Island.d.ts +52 -0
- package/dist/types/src/physics/Joint.d.ts +59 -0
- package/dist/types/src/physics/Math2D.d.ts +371 -0
- package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
- package/dist/types/src/physics/Settings.d.ts +22 -0
- package/dist/types/src/physics/Shapes.d.ts +207 -0
- package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
- package/dist/types/src/physics/World.d.ts +274 -0
- package/dist/types/src/physics/index.d.ts +34 -0
- package/index.js +78 -0
- package/package.json +73 -60
- package/src/Animator.js +95 -0
- package/src/BitmapText.js +6 -5
- package/src/Camera.js +183 -0
- package/src/CameraController.js +192 -0
- package/src/CanvasText.js +281 -0
- package/src/CollisionLayers.js +86 -0
- package/src/Color.js +26 -18
- package/src/Coroutine.js +259 -0
- package/src/DebugOverlay.js +246 -0
- package/src/Drawable.js +842 -582
- package/src/Easing.js +57 -0
- package/src/Emerald.js +1150 -459
- package/src/EmeraldDB.js +328 -0
- package/src/FPSCounter.js +43 -43
- package/src/GLUtils.js +60 -67
- package/src/Instance.js +41 -5
- package/src/InstancedTexture.js +308 -127
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +134 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +431 -40
- package/src/Pool.js +85 -0
- package/src/PostEffects.js +296 -0
- package/src/PostProcessor.js +304 -0
- package/src/RenderTarget.js +134 -0
- package/src/Scene.js +115 -83
- package/src/ScreenEffects.js +266 -0
- package/src/Serializer.js +131 -0
- package/src/Shaders.js +150 -165
- package/src/Shapes.js +118 -129
- package/src/SpatialGrid.js +111 -0
- package/src/SpriteBatch.js +299 -0
- package/src/StateMachine.js +82 -0
- package/src/Storage.js +175 -47
- package/src/Texture.js +58 -67
- package/src/TextureAtlas.js +96 -0
- package/src/Tilemap.js +274 -0
- package/src/Time.js +51 -6
- package/src/Timer.js +99 -0
- package/src/Transform.js +100 -7
- package/src/Tween.js +160 -0
- package/src/UI.js +394 -0
- package/src/components/Behaviour.js +90 -0
- package/src/components/BoxCollider.js +29 -12
- package/src/components/BoxColliderDebug.js +3 -4
- package/src/components/CircleCollider.js +26 -12
- package/src/components/CircleColliderDebug.js +25 -24
- package/src/components/Collider.js +150 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/PolygonCollider.js +55 -0
- package/src/components/RigidBody.js +556 -8
- package/src/importers/Aseprite.js +142 -0
- package/src/importers/ForgeLevel.js +581 -0
- package/src/importers/TiledMap.js +158 -0
- package/src/lights/DirectionalLight.js +6 -15
- package/src/lights/PointLight.js +4 -4
- package/src/managers/AssetManager.js +239 -0
- package/src/managers/AudioManager.js +565 -146
- package/src/managers/EventManager.js +488 -477
- package/src/managers/GLManager.js +57 -0
- package/src/managers/GLState.js +70 -0
- package/src/managers/Gamepad.js +126 -0
- package/src/managers/IDManager.js +24 -2
- package/src/managers/InputManager.js +905 -0
- package/src/managers/NetworkManager.js +178 -0
- package/src/managers/RenderStats.js +34 -0
- package/src/managers/SceneManager.js +30 -0
- package/src/managers/ShaderManager.js +0 -2
- package/src/managers/TextureManager.js +218 -0
- package/src/particlesystem/Particle.js +82 -7
- package/src/particlesystem/ParticleSettings.js +21 -3
- package/src/particlesystem/Particles.js +80 -31
- package/src/physics/AABB.js +207 -0
- package/src/physics/Body.js +862 -0
- package/src/physics/BodyType.js +16 -0
- package/src/physics/BroadPhase.js +641 -0
- package/src/physics/Collision.js +534 -0
- package/src/physics/Contact.js +500 -0
- package/src/physics/ContactSolver.js +526 -0
- package/src/physics/Distance.js +403 -0
- package/src/physics/DistanceJoint.js +227 -0
- package/src/physics/Fixture.js +346 -0
- package/src/physics/Island.js +203 -0
- package/src/physics/Joint.js +78 -0
- package/src/physics/Math2D.js +573 -0
- package/src/physics/RevoluteJoint.js +278 -0
- package/src/physics/Settings.js +78 -0
- package/src/physics/Shapes.js +549 -0
- package/src/physics/TimeOfImpact.js +87 -0
- package/src/physics/World.js +731 -0
- package/src/physics/index.js +79 -0
- package/dist/types/Drawable.d.ts +0 -157
- package/dist/types/Emerald.d.ts +0 -73
- package/dist/types/GLUtils.d.ts +0 -6
- package/dist/types/Physics.d.ts +0 -139
- package/dist/types/Scene.d.ts +0 -39
- package/dist/types/Shaders.d.ts +0 -4
- package/dist/types/Storage.d.ts +0 -46
- package/dist/types/Time.d.ts +0 -22
- package/dist/types/Transform.d.ts +0 -41
- package/dist/types/components/BoxColliderDebug.d.ts +0 -19
- package/dist/types/components/CircleColliderDebug.d.ts +0 -19
- package/dist/types/components/Collider.d.ts +0 -53
- package/dist/types/components/GameObject.d.ts +0 -72
- package/dist/types/components/RigidBody.d.ts +0 -104
- package/dist/types/managers/AudioManager.d.ts +0 -60
- package/dist/types/managers/GLManager.d.ts +0 -47
- package/dist/types/managers/IDManager.d.ts +0 -21
- package/dist/types/managers/SceneManager.d.ts +0 -22
- package/dist/types/particlesystem/Particle.d.ts +0 -42
package/src/Physics.js
CHANGED
|
@@ -1,20 +1,218 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { World, Box, Vec2 as PhysicsVec2 } from "./physics/index.js";
|
|
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
|
+
}
|
|
2
23
|
|
|
3
24
|
/**
|
|
4
25
|
* @class Physics
|
|
5
|
-
* @description
|
|
26
|
+
* @description The game-facing front end of Emerald's own rigid-body engine
|
|
27
|
+
* (see `src/physics`). It owns the {@link World}, converts between world
|
|
28
|
+
* (pixel) units and physics units via `scale`, steps the simulation on a fixed
|
|
29
|
+
* timestep, and routes contacts to `onCollisionEnter`/`onCollisionExit` on your
|
|
30
|
+
* objects and {@link Behaviour} components.
|
|
6
31
|
* @param {number} gravity - The gravity of the physics engine
|
|
7
|
-
* @param {number} scale -
|
|
8
|
-
* @param {number} velocityThreshold -
|
|
32
|
+
* @param {number} scale - Pixels per physics unit (meter)
|
|
33
|
+
* @param {number} velocityThreshold - Relative speed below which impacts stop
|
|
34
|
+
* bouncing, which is what lets resting bodies settle instead of jittering
|
|
9
35
|
*/
|
|
10
36
|
class Physics {
|
|
11
37
|
constructor(gravity, scale, velocityThreshold = 0.1) {
|
|
12
|
-
this.world = new
|
|
13
|
-
gravity: new
|
|
38
|
+
this.world = new World({
|
|
39
|
+
gravity: new PhysicsVec2(0, gravity),
|
|
40
|
+
velocityThreshold,
|
|
14
41
|
});
|
|
15
|
-
planck.Settings.velocityThreshold = velocityThreshold;
|
|
16
42
|
this.gravity = gravity;
|
|
17
43
|
this.scale = scale;
|
|
44
|
+
|
|
45
|
+
this.fixedTimeStep = 1 / 60;
|
|
46
|
+
this.maxSubSteps = 5;
|
|
47
|
+
/** Largest slice the variable mode will simulate in one step. */
|
|
48
|
+
this.maxTimeStep = 1 / 30;
|
|
49
|
+
/** "fixed" or "variable", see {@link Physics#setVariableTimeStep}. */
|
|
50
|
+
this.timeStepMode = "fixed";
|
|
51
|
+
/** Steps taken by the last process() call. */
|
|
52
|
+
this.lastStepCount = 0;
|
|
53
|
+
/** @private */
|
|
54
|
+
this._accumulator = 0;
|
|
55
|
+
/** World-level listeners added via onCollisionEnter. @private */
|
|
56
|
+
this._enterCallbacks = [];
|
|
57
|
+
/** World-level listeners added via onCollisionExit. @private */
|
|
58
|
+
this._exitCallbacks = [];
|
|
59
|
+
|
|
60
|
+
this._dispatchContacts();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @method _dispatchContacts
|
|
65
|
+
* @description Binds contact routing to the current world: owning objects (so
|
|
66
|
+
* Behaviour components receive onCollisionEnter/onCollisionExit) first, then
|
|
67
|
+
* any world-level listeners. Bodies created through RigidBody carry the owner
|
|
68
|
+
* via userData. Re-run whenever the world is replaced, so subscriptions
|
|
69
|
+
* survive a clear().
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
_dispatchContacts() {
|
|
73
|
+
const fire = (contact, type, callbacks) => {
|
|
74
|
+
const fixtureA = contact.getFixtureA();
|
|
75
|
+
const fixtureB = contact.getFixtureB();
|
|
76
|
+
const bodyA = fixtureA.getBody();
|
|
77
|
+
const bodyB = fixtureB.getBody();
|
|
78
|
+
|
|
79
|
+
const a = bodyA.getUserData();
|
|
80
|
+
const b = bodyB.getUserData();
|
|
81
|
+
const objA = a && a.parentObject ? a.parentObject : null;
|
|
82
|
+
const objB = b && b.parentObject ? b.parentObject : null;
|
|
83
|
+
dispatchCollision(objA, objB, contact, type);
|
|
84
|
+
dispatchCollision(objB, objA, contact, type);
|
|
85
|
+
|
|
86
|
+
for (const callback of callbacks) callback(bodyA, bodyB, contact);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.world.on("begin-contact", (contact) =>
|
|
90
|
+
fire(contact, "onCollisionEnter", this._enterCallbacks)
|
|
91
|
+
);
|
|
92
|
+
this.world.on("end-contact", (contact) =>
|
|
93
|
+
fire(contact, "onCollisionExit", this._exitCallbacks)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @method raycast
|
|
99
|
+
* @description Casts a ray through the world and returns the closest hit.
|
|
100
|
+
* @param {Object} origin - World-space { x, y } start point
|
|
101
|
+
* @param {Object} direction - Ray direction { x, y } (need not be normalized)
|
|
102
|
+
* @param {number} maxDistance - Max ray length in world units
|
|
103
|
+
* @returns {{object, rigidBody, point, normal, fraction}|null}
|
|
104
|
+
*/
|
|
105
|
+
raycast(origin, direction, maxDistance) {
|
|
106
|
+
const scale = this.scale;
|
|
107
|
+
const len = Math.hypot(direction.x, direction.y) || 1;
|
|
108
|
+
const dx = direction.x / len;
|
|
109
|
+
const dy = direction.y / len;
|
|
110
|
+
const p1 = new PhysicsVec2(origin.x / scale, origin.y / scale);
|
|
111
|
+
const p2 = new PhysicsVec2(
|
|
112
|
+
(origin.x + dx * maxDistance) / scale,
|
|
113
|
+
(origin.y + dy * maxDistance) / scale
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
let result = null;
|
|
117
|
+
this.world.rayCast(p1, p2, (fixture, point, normal, fraction) => {
|
|
118
|
+
const rb = fixture.getBody().getUserData();
|
|
119
|
+
result = {
|
|
120
|
+
object: rb && rb.parentObject ? rb.parentObject : null,
|
|
121
|
+
rigidBody: rb || null,
|
|
122
|
+
point: { x: point.x * scale, y: point.y * scale },
|
|
123
|
+
normal: { x: normal.x, y: normal.y },
|
|
124
|
+
fraction,
|
|
125
|
+
};
|
|
126
|
+
return fraction;
|
|
127
|
+
});
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @method queryPoint
|
|
133
|
+
* @description Returns the objects whose colliders contain a world-space point.
|
|
134
|
+
* @param {Object} point - World-space { x, y }
|
|
135
|
+
* @returns {Array} - Owning objects at the point
|
|
136
|
+
*/
|
|
137
|
+
queryPoint(point) {
|
|
138
|
+
const p = new PhysicsVec2(point.x / this.scale, point.y / this.scale);
|
|
139
|
+
const results = [];
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
this.world.queryPoint(p, (fixture) => {
|
|
142
|
+
const body = fixture.getBody();
|
|
143
|
+
if (seen.has(body)) return true;
|
|
144
|
+
seen.add(body);
|
|
145
|
+
const rb = body.getUserData();
|
|
146
|
+
if (rb && rb.parentObject) results.push(rb.parentObject);
|
|
147
|
+
return true;
|
|
148
|
+
});
|
|
149
|
+
return results;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @method setFixedTimeStep
|
|
154
|
+
* @description Runs the simulation in constant-size slices, independent of
|
|
155
|
+
* the frame rate: `process()` banks the elapsed time and steps as many whole
|
|
156
|
+
* slices as fit. This is the default and the safe choice: the same inputs
|
|
157
|
+
* produce the same result on every machine, and a slow frame can't destabilise
|
|
158
|
+
* the solver.
|
|
159
|
+
*
|
|
160
|
+
* The cost is that motion updates at the step rate, not the display rate. On
|
|
161
|
+
* a 120Hz screen with a 1/60 step, every simulated position is shown for two
|
|
162
|
+
* frames, so anything that moves in the render frame (a smoothly lerped
|
|
163
|
+
* camera, for instance) will slide against sprites that only move every other
|
|
164
|
+
* frame. Either drive those from the same fixed step, or use
|
|
165
|
+
* {@link Physics#setVariableTimeStep}.
|
|
166
|
+
*
|
|
167
|
+
* @param {number} step - Fixed step in seconds (e.g. 1/60)
|
|
168
|
+
* @param {number} [maxSubSteps] - Max steps per process() call (spiral guard)
|
|
169
|
+
* @returns {Physics} - this
|
|
170
|
+
*/
|
|
171
|
+
setFixedTimeStep(step, maxSubSteps = this.maxSubSteps) {
|
|
172
|
+
if (step > 0) this.fixedTimeStep = step;
|
|
173
|
+
this.maxSubSteps = maxSubSteps;
|
|
174
|
+
this.timeStepMode = "fixed";
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @method setVariableTimeStep
|
|
180
|
+
* @description Advances the simulation once per `process()` call using the
|
|
181
|
+
* frame's own delta, so physics runs at exactly the rendering rate. Every
|
|
182
|
+
* rendered frame then shows a freshly simulated position, which is what
|
|
183
|
+
* removes the stepping you otherwise see when the display refreshes faster
|
|
184
|
+
* than the simulation.
|
|
185
|
+
*
|
|
186
|
+
* The trade-offs are real and worth knowing:
|
|
187
|
+
* - **Not deterministic.** Results depend on the frame timings the machine
|
|
188
|
+
* happened to produce, so replays and lockstep networking need fixed steps.
|
|
189
|
+
* - **Solver accuracy tracks the frame rate.** Contacts are resolved
|
|
190
|
+
* iteratively, so a long frame is a coarser solve; deep stacks and fast
|
|
191
|
+
* bodies are more forgiving under a fixed step.
|
|
192
|
+
*
|
|
193
|
+
* A frame longer than `maxStep` is split into several equal steps rather than
|
|
194
|
+
* simulated in one lump, up to `maxSubSteps`; beyond that the excess time is
|
|
195
|
+
* dropped instead of letting the world explode or spiral.
|
|
196
|
+
*
|
|
197
|
+
* @param {number} [maxStep=1/30] - Longest slice to simulate in one step
|
|
198
|
+
* @param {number} [maxSubSteps] - Max steps per process() call
|
|
199
|
+
* @returns {Physics} - this
|
|
200
|
+
*/
|
|
201
|
+
setVariableTimeStep(maxStep = 1 / 30, maxSubSteps = this.maxSubSteps) {
|
|
202
|
+
if (maxStep > 0) this.maxTimeStep = maxStep;
|
|
203
|
+
this.maxSubSteps = maxSubSteps;
|
|
204
|
+
this.timeStepMode = "variable";
|
|
205
|
+
this._accumulator = 0;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @method getTimeStepMode
|
|
211
|
+
* @description Returns "fixed" or "variable".
|
|
212
|
+
* @returns {string}
|
|
213
|
+
*/
|
|
214
|
+
getTimeStepMode() {
|
|
215
|
+
return this.timeStepMode;
|
|
18
216
|
}
|
|
19
217
|
|
|
20
218
|
/**
|
|
@@ -42,7 +240,7 @@ class Physics {
|
|
|
42
240
|
) {
|
|
43
241
|
const bodyRef = this.world.createBody({
|
|
44
242
|
type: type,
|
|
45
|
-
position: new
|
|
243
|
+
position: new PhysicsVec2(
|
|
46
244
|
position.x / this.scale,
|
|
47
245
|
position.y / this.scale
|
|
48
246
|
),
|
|
@@ -50,7 +248,7 @@ class Physics {
|
|
|
50
248
|
});
|
|
51
249
|
|
|
52
250
|
if (attachFixture) {
|
|
53
|
-
bodyRef.createFixture(
|
|
251
|
+
bodyRef.createFixture(Box(fixtureSize.x, fixtureSize.y), {
|
|
54
252
|
density: density,
|
|
55
253
|
friction: friction,
|
|
56
254
|
restitution: restitution,
|
|
@@ -61,56 +259,200 @@ class Physics {
|
|
|
61
259
|
}
|
|
62
260
|
|
|
63
261
|
/**
|
|
64
|
-
* @method
|
|
65
|
-
* @description
|
|
66
|
-
*
|
|
262
|
+
* @method createDistanceJoint
|
|
263
|
+
* @description Connects two rigid bodies with a fixed-length rod between two
|
|
264
|
+
* world-space anchor points, or a damped spring toward that length when
|
|
265
|
+
* `frequencyHz` is set.
|
|
266
|
+
* @param {RigidBody} rigidBodyA
|
|
267
|
+
* @param {RigidBody} rigidBodyB
|
|
268
|
+
* @param {Object} [options] - `{ anchorA, anchorB, length, frequencyHz,
|
|
269
|
+
* dampingRatio, collideConnected }`. `anchorA`/`anchorB` are world-space
|
|
270
|
+
* pixel points; each defaults to its body's own center. `length` is in
|
|
271
|
+
* pixels; it defaults to the current distance between the anchors.
|
|
272
|
+
* @returns {DistanceJoint}
|
|
67
273
|
*/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
274
|
+
createDistanceJoint(rigidBodyA, rigidBodyB, options = {}) {
|
|
275
|
+
const bodyA = rigidBodyA.body;
|
|
276
|
+
const bodyB = rigidBodyB.body;
|
|
277
|
+
const scale = this.scale;
|
|
278
|
+
const anchorA = options.anchorA
|
|
279
|
+
? new PhysicsVec2(options.anchorA.x / scale, options.anchorA.y / scale)
|
|
280
|
+
: bodyA.getWorldCenter();
|
|
281
|
+
const anchorB = options.anchorB
|
|
282
|
+
? new PhysicsVec2(options.anchorB.x / scale, options.anchorB.y / scale)
|
|
283
|
+
: bodyB.getWorldCenter();
|
|
284
|
+
const length =
|
|
285
|
+
options.length != null
|
|
286
|
+
? options.length / scale
|
|
287
|
+
: Math.hypot(anchorB.x - anchorA.x, anchorB.y - anchorA.y);
|
|
72
288
|
|
|
73
|
-
|
|
74
|
-
|
|
289
|
+
return this.world.createJoint({
|
|
290
|
+
type: "distance",
|
|
291
|
+
bodyA,
|
|
292
|
+
bodyB,
|
|
293
|
+
localAnchorA: bodyA.getLocalPoint(anchorA),
|
|
294
|
+
localAnchorB: bodyB.getLocalPoint(anchorB),
|
|
295
|
+
length,
|
|
296
|
+
frequencyHz: options.frequencyHz,
|
|
297
|
+
dampingRatio: options.dampingRatio,
|
|
298
|
+
collideConnected: options.collideConnected,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @method createRevoluteJoint
|
|
304
|
+
* @description Pins two rigid bodies together at a shared world-space point,
|
|
305
|
+
* like a hinge: a door, a pendulum arm, a see-saw. Set `enableMotor` to
|
|
306
|
+
* drive it toward a target angular speed instead of swinging freely.
|
|
307
|
+
* @param {RigidBody} rigidBodyA
|
|
308
|
+
* @param {RigidBody} rigidBodyB
|
|
309
|
+
* @param {Object} anchor - World-space pixel point both bodies pin to
|
|
310
|
+
* @param {Object} [options] - `{ enableMotor, motorSpeed, maxMotorTorque,
|
|
311
|
+
* collideConnected }`
|
|
312
|
+
* @returns {RevoluteJoint}
|
|
313
|
+
*/
|
|
314
|
+
createRevoluteJoint(rigidBodyA, rigidBodyB, anchor, options = {}) {
|
|
315
|
+
const bodyA = rigidBodyA.body;
|
|
316
|
+
const bodyB = rigidBodyB.body;
|
|
317
|
+
const worldAnchor = new PhysicsVec2(
|
|
318
|
+
anchor.x / this.scale,
|
|
319
|
+
anchor.y / this.scale
|
|
320
|
+
);
|
|
75
321
|
|
|
76
|
-
|
|
322
|
+
return this.world.createJoint({
|
|
323
|
+
type: "revolute",
|
|
324
|
+
bodyA,
|
|
325
|
+
bodyB,
|
|
326
|
+
localAnchorA: bodyA.getLocalPoint(worldAnchor),
|
|
327
|
+
localAnchorB: bodyB.getLocalPoint(worldAnchor),
|
|
328
|
+
enableMotor: options.enableMotor,
|
|
329
|
+
motorSpeed: options.motorSpeed,
|
|
330
|
+
maxMotorTorque: options.maxMotorTorque,
|
|
331
|
+
collideConnected: options.collideConnected,
|
|
77
332
|
});
|
|
78
333
|
}
|
|
79
334
|
|
|
80
335
|
/**
|
|
81
|
-
* @method
|
|
82
|
-
* @description
|
|
83
|
-
* @
|
|
336
|
+
* @method destroyJoint
|
|
337
|
+
* @description Removes a joint created by {@link Physics#createDistanceJoint}
|
|
338
|
+
* or {@link Physics#createRevoluteJoint}.
|
|
339
|
+
* @param {Joint} joint
|
|
84
340
|
*/
|
|
85
|
-
|
|
86
|
-
this.world.
|
|
87
|
-
|
|
88
|
-
const fixtureB = contact.getFixtureB();
|
|
341
|
+
destroyJoint(joint) {
|
|
342
|
+
this.world.destroyJoint(joint);
|
|
343
|
+
}
|
|
89
344
|
|
|
90
|
-
|
|
91
|
-
|
|
345
|
+
/**
|
|
346
|
+
* @method onCollisionEnter
|
|
347
|
+
* @description Registers a world-level listener called whenever any two
|
|
348
|
+
* fixtures start touching. Survives {@link Physics#clear}.
|
|
349
|
+
* @param {Function} callback - Called with (bodyA, bodyB, contact)
|
|
350
|
+
*/
|
|
351
|
+
onCollisionEnter(callback) {
|
|
352
|
+
if (typeof callback === "function") this._enterCallbacks.push(callback);
|
|
353
|
+
}
|
|
92
354
|
|
|
93
|
-
|
|
94
|
-
|
|
355
|
+
/**
|
|
356
|
+
* @method onCollisionExit
|
|
357
|
+
* @description Registers a world-level listener called whenever any two
|
|
358
|
+
* fixtures stop touching. Survives {@link Physics#clear}.
|
|
359
|
+
* @param {Function} callback - Called with (bodyA, bodyB, contact)
|
|
360
|
+
*/
|
|
361
|
+
onCollisionExit(callback) {
|
|
362
|
+
if (typeof callback === "function") this._exitCallbacks.push(callback);
|
|
95
363
|
}
|
|
96
364
|
|
|
97
365
|
/**
|
|
98
366
|
* @method process
|
|
99
|
-
* @description
|
|
100
|
-
*
|
|
367
|
+
* @description Advances the simulation by `dt` seconds, in whichever way the
|
|
368
|
+
* current time-step mode calls for. Call it once per frame.
|
|
369
|
+
* @param {number} dt - Seconds elapsed since the previous call
|
|
370
|
+
* @returns {number} - How many steps were simulated
|
|
101
371
|
*/
|
|
102
372
|
process(dt) {
|
|
103
|
-
this.
|
|
373
|
+
this.lastStepCount = 0;
|
|
374
|
+
if (!Number.isFinite(dt) || dt <= 0) return 0;
|
|
375
|
+
|
|
376
|
+
this.lastStepCount =
|
|
377
|
+
this.timeStepMode === "variable"
|
|
378
|
+
? this._stepVariable(dt)
|
|
379
|
+
: this._stepFixed(dt);
|
|
380
|
+
return this.lastStepCount;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* @method _stepVariable
|
|
385
|
+
* @description Simulates the frame's own delta. Normally that is a single
|
|
386
|
+
* step of exactly `dt`, so physics advances in lock-step with rendering. Only
|
|
387
|
+
* an unusually long frame is divided, and only far enough to keep each slice
|
|
388
|
+
* within `maxTimeStep`.
|
|
389
|
+
* @param {number} dt
|
|
390
|
+
* @returns {number} - Steps taken
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
_stepVariable(dt) {
|
|
394
|
+
const budget = this.maxTimeStep * this.maxSubSteps;
|
|
395
|
+
const total = Math.min(dt, budget);
|
|
396
|
+
const steps = Math.max(1, Math.ceil(total / this.maxTimeStep));
|
|
397
|
+
const step = total / steps;
|
|
398
|
+
|
|
399
|
+
for (let i = 0; i < steps; i++) {
|
|
400
|
+
this.world.step(step);
|
|
401
|
+
}
|
|
402
|
+
return steps;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* @method _stepFixed
|
|
407
|
+
* @description Banks elapsed time and simulates as many constant-size slices
|
|
408
|
+
* as have accumulated, leaving the remainder for next frame.
|
|
409
|
+
* @param {number} dt
|
|
410
|
+
* @returns {number} - Steps taken
|
|
411
|
+
* @private
|
|
412
|
+
*/
|
|
413
|
+
_stepFixed(dt) {
|
|
414
|
+
this._accumulator += dt;
|
|
415
|
+
let steps = 0;
|
|
416
|
+
while (
|
|
417
|
+
this._accumulator >= this.fixedTimeStep &&
|
|
418
|
+
steps < this.maxSubSteps
|
|
419
|
+
) {
|
|
420
|
+
this.world.step(this.fixedTimeStep);
|
|
421
|
+
this._accumulator -= this.fixedTimeStep;
|
|
422
|
+
steps++;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (steps === this.maxSubSteps) this._accumulator = 0;
|
|
426
|
+
return steps;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* @method getInterpolationAlpha
|
|
431
|
+
* @description How far the fixed-step simulation currently sits between its
|
|
432
|
+
* last completed step and the next one (0..1). Useful if you interpolate
|
|
433
|
+
* renderables between physics states. Always 0 in variable mode, where every
|
|
434
|
+
* frame already renders a freshly simulated position.
|
|
435
|
+
* @returns {number}
|
|
436
|
+
*/
|
|
437
|
+
getInterpolationAlpha() {
|
|
438
|
+
if (this.timeStepMode === "variable" || this.fixedTimeStep <= 0) return 0;
|
|
439
|
+
return Math.min(1, this._accumulator / this.fixedTimeStep);
|
|
104
440
|
}
|
|
105
441
|
|
|
106
442
|
/**
|
|
107
443
|
* @method clear
|
|
108
|
-
* @description
|
|
444
|
+
* @description Drops every body and starts a fresh world with the original
|
|
445
|
+
* gravity; use it when tearing a level down. Collision routing and any
|
|
446
|
+
* listeners added through onCollisionEnter/onCollisionExit are re-bound to
|
|
447
|
+
* the new world, so they keep working afterwards.
|
|
109
448
|
*/
|
|
110
449
|
clear() {
|
|
111
|
-
this.world = new
|
|
112
|
-
gravity: new
|
|
450
|
+
this.world = new World({
|
|
451
|
+
gravity: new PhysicsVec2(0, this.gravity),
|
|
452
|
+
velocityThreshold: this.world.velocityThreshold,
|
|
113
453
|
});
|
|
454
|
+
this._accumulator = 0;
|
|
455
|
+
this._dispatchContacts();
|
|
114
456
|
}
|
|
115
457
|
|
|
116
458
|
/**
|
|
@@ -152,8 +494,31 @@ class Physics {
|
|
|
152
494
|
* @param {number} y - The y coordinate of the vector
|
|
153
495
|
*/
|
|
154
496
|
class Vector2 {
|
|
155
|
-
constructor(x, y) {
|
|
156
|
-
|
|
497
|
+
constructor(x = 0, y = 0) {
|
|
498
|
+
this.x = x;
|
|
499
|
+
this.y = y;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @method set
|
|
504
|
+
* @description Sets the x and y coordinates in place
|
|
505
|
+
* @param {number} x - The x coordinate
|
|
506
|
+
* @param {number} y - The y coordinate
|
|
507
|
+
* @returns {Vector2} - This vector
|
|
508
|
+
*/
|
|
509
|
+
set(x, y) {
|
|
510
|
+
this.x = x;
|
|
511
|
+
this.y = y;
|
|
512
|
+
return this;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @method clone
|
|
517
|
+
* @description Returns a copy of the vector
|
|
518
|
+
* @returns {Vector2} - The cloned vector
|
|
519
|
+
*/
|
|
520
|
+
clone() {
|
|
521
|
+
return new Vector2(this.x, this.y);
|
|
157
522
|
}
|
|
158
523
|
|
|
159
524
|
/**
|
|
@@ -195,8 +560,34 @@ class Vector2 {
|
|
|
195
560
|
* @param {number} z - The z coordinate of the vector
|
|
196
561
|
*/
|
|
197
562
|
class Vector3 {
|
|
198
|
-
constructor(x, y, z) {
|
|
199
|
-
|
|
563
|
+
constructor(x = 0, y = 0, z = 0) {
|
|
564
|
+
this.x = x;
|
|
565
|
+
this.y = y;
|
|
566
|
+
this.z = z;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* @method set
|
|
571
|
+
* @description Sets the x, y and z coordinates in place
|
|
572
|
+
* @param {number} x - The x coordinate
|
|
573
|
+
* @param {number} y - The y coordinate
|
|
574
|
+
* @param {number} z - The z coordinate
|
|
575
|
+
* @returns {Vector3} - This vector
|
|
576
|
+
*/
|
|
577
|
+
set(x, y, z) {
|
|
578
|
+
this.x = x;
|
|
579
|
+
this.y = y;
|
|
580
|
+
this.z = z;
|
|
581
|
+
return this;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* @method clone
|
|
586
|
+
* @description Returns a copy of the vector
|
|
587
|
+
* @returns {Vector3} - The cloned vector
|
|
588
|
+
*/
|
|
589
|
+
clone() {
|
|
590
|
+
return new Vector3(this.x, this.y, this.z);
|
|
200
591
|
}
|
|
201
592
|
|
|
202
593
|
/**
|
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;
|