littlejsengine 1.19.3 → 1.21.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/FAQ.md +841 -820
- package/README.md +29 -25
- package/dist/littlejs.d.ts +2857 -955
- package/dist/littlejs.esm.js +33428 -20995
- package/dist/littlejs.esm.min.js +4 -3
- package/dist/littlejs.js +33386 -20996
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +27841 -20422
- package/package.json +10 -5
- package/plugins/audioEffects.js +28 -24
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +786 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -332
- package/plugins/math3d.js +148 -27
- package/plugins/medalSystem.js +326 -280
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +25 -0
- package/plugins/postProcess.js +96 -24
- package/plugins/render3d.js +2067 -1632
- package/plugins/render3dDebug.js +110 -0
- package/plugins/render3dEditor.js +1812 -0
- package/plugins/render3dExtras.js +1948 -0
- package/plugins/render3dLevel.js +189 -0
- package/plugins/render3dVoxels.js +514 -0
- package/plugins/sceneSystem.js +58 -0
- package/plugins/textureSheet.js +496 -458
- package/plugins/threejs.js +183 -163
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +265 -145
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +159 -159
- package/src/engine.js +378 -97
- package/src/engineAudio.js +231 -127
- package/src/engineBuild.mjs +292 -256
- package/src/engineDebug.js +967 -743
- package/src/engineDraw.js +391 -198
- package/src/engineEditor.js +2452 -0
- package/src/engineExport.js +18 -0
- package/src/engineInput.js +1466 -1293
- package/src/engineLogo.js +19 -0
- package/src/engineMath.js +1274 -1252
- package/src/engineObject.js +197 -82
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +79 -45
- package/src/engineSettings.js +115 -29
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +19 -13
- package/src/engineWebGL.js +239 -82
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/src/engineObject.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* - If an object starts or is moved inside tile collision, it will not collide with that tile
|
|
31
31
|
* - Collision for objects can be set to be solid to block other objects
|
|
32
32
|
* - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
33
|
+
* - A static solid (mass 0) moved by its velocity, like a door or an elevator, pushes objects out of its way
|
|
33
34
|
* - Solid objects are more performance intensive and should be used sparingly
|
|
34
35
|
* @memberof Engine
|
|
35
36
|
* @example
|
|
@@ -61,63 +62,81 @@ class EngineObject
|
|
|
61
62
|
this.pos = pos.copy();
|
|
62
63
|
/** @property {Vector2} - World space width and height of the object */
|
|
63
64
|
this.size = size.copy();
|
|
64
|
-
/** @property {Vector2} - Size of object used for drawing, uses size if not set
|
|
65
|
+
/** @property {Vector2|undefined} - Size of object used for drawing, uses size if not set
|
|
66
|
+
* @type {Vector2|undefined} */
|
|
65
67
|
this.drawSize = undefined;
|
|
66
|
-
/** @property {TileInfo} - Tile info to render object (undefined is untextured)
|
|
68
|
+
/** @property {TileInfo|undefined} - Tile info to render object (undefined is untextured)
|
|
69
|
+
* @type {TileInfo|undefined} */
|
|
67
70
|
this.tileInfo = tileInfo;
|
|
68
71
|
/** @property {number} - Angle to rotate the object */
|
|
69
72
|
this.angle = angle;
|
|
70
73
|
/** @property {Color} - Color to apply when rendered */
|
|
71
74
|
this.color = color.copy();
|
|
72
|
-
/** @property {Color} - Additive color to apply when rendered
|
|
75
|
+
/** @property {Color|undefined} - Additive color to apply when rendered
|
|
76
|
+
* @type {Color|undefined} */
|
|
73
77
|
this.additiveColor = undefined;
|
|
74
78
|
/** @property {Shader|undefined} - Custom shader to render with, undefined for the engine's own
|
|
75
79
|
* @type {Shader|undefined} */
|
|
76
80
|
this.shader = undefined;
|
|
81
|
+
/** @property {boolean} - Does this object draw into the light system's shadow map; false for a floor layer, a background, a pickup */
|
|
82
|
+
this.castShadow = true;
|
|
83
|
+
/** @property {number} - With the light system, how much it lights itself: 0 lit only by the lights, 1 full
|
|
84
|
+
* brightness in its own colors whatever the lights do, between partly; drawn into the lightmap through
|
|
85
|
+
* renderEmissive, as 3D's emissive. Exact for solid pixels; a partly transparent one is self lit by its alpha
|
|
86
|
+
* too, so a half alpha pixel shows at a quarter and a fading emissive sprite fades a little faster */
|
|
87
|
+
this.emissive = 0;
|
|
77
88
|
/** @property {boolean} - Should the rendered tile flip along the y axis. Affects rendering and the local→world transform of attached children (a mirrored parent flips its children's localPos.x and localAngle). Does not affect this object's own physics, collision, or localToWorld/worldToLocal. */
|
|
78
89
|
this.mirror = false;
|
|
79
90
|
/** @property {boolean} - Has object been destroyed? */
|
|
80
91
|
this.destroyed = false;
|
|
92
|
+
this.updatePass = 0; // the engine update pass it was last updated in, so nothing updates twice in one
|
|
81
93
|
|
|
82
94
|
// physical properties
|
|
83
|
-
/** @property {number} - How heavy the object is, static if 0
|
|
95
|
+
/** @property {number} - How heavy the object is, static if 0: a static object moves by its velocity but does not
|
|
96
|
+
* collide on its own, the moving ones collide with it */
|
|
84
97
|
this.mass = objectDefaultMass;
|
|
85
|
-
/** @property {number} -
|
|
98
|
+
/** @property {number} - Fraction of velocity kept each frame, 1 keeps all of it, 0 stops at once */
|
|
86
99
|
this.damping = objectDefaultDamping;
|
|
87
|
-
/** @property {number} -
|
|
100
|
+
/** @property {number} - Fraction of angular velocity kept each frame, 1 keeps all of it, 0 stops at once */
|
|
88
101
|
this.angleDamping = objectDefaultAngleDamping;
|
|
89
102
|
/** @property {number} - How bouncy the object is when colliding (0-1) */
|
|
90
103
|
this.restitution = objectDefaultRestitution;
|
|
91
|
-
/** @property {number} -
|
|
104
|
+
/** @property {number} - Fraction of sliding speed kept each frame on the ground, 1 is no friction, 0 stops at
|
|
105
|
+
* once, the more slippery of the object and its ground is used */
|
|
92
106
|
this.friction = objectDefaultFriction;
|
|
93
107
|
/** @property {number} - How much to scale gravity by for this object */
|
|
94
108
|
this.gravityScale = 1;
|
|
95
109
|
/** @property {number} - Objects are sorted by render order */
|
|
96
110
|
this.renderOrder = renderOrder;
|
|
97
|
-
/** @property {Vector2} - Velocity of the object */
|
|
111
|
+
/** @property {Vector2} - Velocity of the object, in world units per frame */
|
|
98
112
|
this.velocity = vec2();
|
|
99
|
-
/** @property {number} - Angular velocity of the object */
|
|
113
|
+
/** @property {number} - Angular velocity of the object, in radians per frame */
|
|
100
114
|
this.angleVelocity = 0;
|
|
101
115
|
/** @property {number} - Track when object was created */
|
|
102
116
|
this.spawnTime = time;
|
|
103
|
-
/** @property {Array<EngineObject>} - List of children of this object
|
|
117
|
+
/** @property {Array<EngineObject>} - List of children of this object
|
|
118
|
+
* @type {Array<EngineObject>} */
|
|
104
119
|
this.children = [];
|
|
105
120
|
/** @property {boolean} - Limit object speed along x and y axis */
|
|
106
121
|
this.clampSpeed = true;
|
|
107
|
-
/** @property {EngineObject} - Object we are standing on, if any
|
|
122
|
+
/** @property {EngineObject|undefined} - Object we are standing on, if any
|
|
123
|
+
* @type {EngineObject|undefined} */
|
|
108
124
|
this.groundObject = undefined;
|
|
109
125
|
|
|
110
126
|
// parent child system
|
|
111
|
-
/** @property {EngineObject} - Parent of object if in local space
|
|
127
|
+
/** @property {EngineObject|undefined} - Parent of object if in local space
|
|
128
|
+
* @type {EngineObject|undefined} */
|
|
112
129
|
this.parent = undefined;
|
|
113
|
-
/** @property {Vector2} -
|
|
114
|
-
|
|
130
|
+
/** @property {Vector2|undefined} - Position relative to the parent, only while attached to one
|
|
131
|
+
* @type {Vector2|undefined} */
|
|
132
|
+
this.localPos = undefined;
|
|
115
133
|
/** @property {number} - Local angle if child */
|
|
116
134
|
this.localAngle = 0;
|
|
117
135
|
|
|
118
136
|
// collision flags
|
|
119
|
-
/** @property {boolean} - Object collides with the tile collision
|
|
120
|
-
|
|
137
|
+
/** @property {boolean} - Object collides with the level, its tile collision layers
|
|
138
|
+
* @type {boolean} */
|
|
139
|
+
this.collideLevel = false;
|
|
121
140
|
/** @property {boolean} - Object collides with solid objects */
|
|
122
141
|
this.collideSolidObjects = false;
|
|
123
142
|
/** @property {boolean} - Object collides with and blocks other objects */
|
|
@@ -133,8 +152,9 @@ class EngineObject
|
|
|
133
152
|
engineObjects.push(this);
|
|
134
153
|
}
|
|
135
154
|
|
|
136
|
-
/** Update the object transform, called automatically by engine even when paused
|
|
137
|
-
|
|
155
|
+
/** Update the object transform, called automatically by engine even when paused
|
|
156
|
+
* @param {boolean} [updateChildren] - Also update the children's transforms */
|
|
157
|
+
updateTransforms(updateChildren=true)
|
|
138
158
|
{
|
|
139
159
|
const parent = this.parent;
|
|
140
160
|
if (parent)
|
|
@@ -154,8 +174,9 @@ class EngineObject
|
|
|
154
174
|
}
|
|
155
175
|
|
|
156
176
|
// update children
|
|
157
|
-
|
|
158
|
-
child.
|
|
177
|
+
if (updateChildren)
|
|
178
|
+
for (const child of this.children)
|
|
179
|
+
child.updateTransforms();
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
/** Update the object physics, called automatically by engine once each frame. Can be overridden to stop or change how physics works for an object. */
|
|
@@ -167,15 +188,21 @@ class EngineObject
|
|
|
167
188
|
// bail if a collision callback destroyed us mid-frame
|
|
168
189
|
if (this.destroyed) return;
|
|
169
190
|
|
|
170
|
-
|
|
191
|
+
// limit max speed to prevent missing collisions, only for what collides: with solids, or with tiles while it
|
|
192
|
+
// has a mass, which tile collision needs; anything else moves as fast as it is told
|
|
193
|
+
if (this.clampSpeed && enablePhysicsSolver && (this.collideSolidObjects || this.collideLevel && this.mass))
|
|
171
194
|
{
|
|
172
|
-
// limit max speed to prevent missing collisions
|
|
173
195
|
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
174
196
|
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
175
197
|
}
|
|
176
198
|
|
|
177
|
-
//
|
|
178
|
-
|
|
199
|
+
// physics sanity checks
|
|
200
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1, 'angleDamping must be 0 to 1, the fraction kept each frame');
|
|
201
|
+
ASSERT(this.damping >= 0 && this.damping <= 1, 'damping must be 0 to 1, the fraction of velocity kept each frame');
|
|
202
|
+
|
|
203
|
+
// apply physics; only the solver needs where the object was, so only then is it copied
|
|
204
|
+
const solve = enablePhysicsSolver && this.mass;
|
|
205
|
+
const oldPos = solve ? this.pos.copy() : undefined;
|
|
179
206
|
this.velocity.x *= this.damping;
|
|
180
207
|
this.velocity.y *= this.damping;
|
|
181
208
|
if (this.mass)
|
|
@@ -188,14 +215,12 @@ class EngineObject
|
|
|
188
215
|
this.pos.y += this.velocity.y;
|
|
189
216
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
190
217
|
|
|
191
|
-
// physics sanity checks
|
|
192
|
-
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
193
|
-
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
194
|
-
|
|
195
218
|
// don't do collision for static objects or if solver disabled
|
|
196
|
-
if (!
|
|
219
|
+
if (!solve) return;
|
|
197
220
|
|
|
198
|
-
|
|
221
|
+
// which way is down for this object, a negative gravityScale falls up and lands on ceilings
|
|
222
|
+
const gravityY = this.gravityScale < 0 ? -gravity.y : gravity.y;
|
|
223
|
+
const wasFalling = this.velocity.y < 0 && gravityY < 0 || this.velocity.y > 0 && gravityY > 0;
|
|
199
224
|
if (this.groundObject)
|
|
200
225
|
{
|
|
201
226
|
// apply friction in local space of ground object
|
|
@@ -210,7 +235,7 @@ class EngineObject
|
|
|
210
235
|
{
|
|
211
236
|
// check collisions against solid objects
|
|
212
237
|
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
213
|
-
for (const o of
|
|
238
|
+
for (const o of engineObjectsCollideStaticLast)
|
|
214
239
|
{
|
|
215
240
|
// skip destroyed, child objects, self collision, or objects with no box
|
|
216
241
|
if (o.destroyed || o.parent || o === this || !o.size.x || !o.size.y) continue;
|
|
@@ -221,29 +246,76 @@ class EngineObject
|
|
|
221
246
|
// check collision
|
|
222
247
|
if (!this.isOverlappingObject(o)) continue;
|
|
223
248
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
249
|
+
// each moving object checks its own contacts, so a pair the other one already asked about this frame
|
|
250
|
+
// is not asked twice: left overlapping, ignored or only nudged apart it is skipped, and one both said
|
|
251
|
+
// to resolve, pushed back together since, is resolved again
|
|
252
|
+
const answer = engineObjectsCollidePairAnswer(o, this);
|
|
253
|
+
if (answer === false) continue;
|
|
254
|
+
if (!answer)
|
|
255
|
+
{
|
|
256
|
+
// notify objects of collision and check if should be resolved
|
|
257
|
+
const collide1 = this.collideWithObject(o);
|
|
258
|
+
const collide2 = o.collideWithObject(this);
|
|
259
|
+
if (!collide1 || !collide2)
|
|
260
|
+
{
|
|
261
|
+
engineObjectsCollidePairAdd(this, o);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const wasOverlapping = isOverlapping(oldPos, this.size, o.pos, o.size);
|
|
267
|
+
if (wasOverlapping && (!o.mass || o.groundObject))
|
|
268
|
+
{
|
|
269
|
+
// a static solid that moved into it, like a door or an elevator, pushes it out the shortest way
|
|
270
|
+
// at once and carries it along, it would only drift out slowly and the solid would pass through;
|
|
271
|
+
// an object standing on something counts as fixed too, so a stack on an elevator rides together;
|
|
272
|
+
// it bounces off relative to the mover, a paddle moved by setting pos bounces a ball as a wall does
|
|
273
|
+
const push = collideBoxBox(this.pos, this.size, o.pos, o.size);
|
|
274
|
+
if (push)
|
|
275
|
+
{
|
|
276
|
+
this.pos.x += push.x + sign(push.x) * epsilon;
|
|
277
|
+
this.pos.y += push.y + sign(push.y) * epsilon;
|
|
278
|
+
const restitution = max(this.restitution, o.restitution);
|
|
279
|
+
if (push.x)
|
|
280
|
+
{
|
|
281
|
+
const v = this.velocity.x - o.velocity.x;
|
|
282
|
+
if (v * push.x < 0) // moving into it
|
|
283
|
+
this.velocity.x = o.velocity.x - v * restitution;
|
|
284
|
+
}
|
|
285
|
+
else
|
|
286
|
+
{
|
|
287
|
+
const v = this.velocity.y - o.velocity.y;
|
|
288
|
+
if (v * push.y < 0) // moving into it
|
|
289
|
+
this.velocity.y = o.velocity.y - v * restitution;
|
|
290
|
+
if (push.y * gravityY < 0) // pushed up against gravity, it stands on it
|
|
291
|
+
this.groundObject = o;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
engineObjectsCollidePairAdd(this, o);
|
|
228
295
|
|
|
229
|
-
|
|
296
|
+
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00', undefined, false);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (wasOverlapping)
|
|
230
300
|
{
|
|
231
301
|
// if already was touching, try to push away
|
|
232
302
|
const deltaPos = oldPos.subtract(o.pos);
|
|
233
303
|
const length = deltaPos.length();
|
|
234
304
|
const pushAwayAccel = .001;
|
|
235
|
-
const velocity = length < .001 ? vec2(0,
|
|
305
|
+
const velocity = length < .001 ? vec2(0, pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
236
306
|
this.velocity = this.velocity.add(velocity);
|
|
237
307
|
if (o.mass) // push away other object if not fixed
|
|
238
308
|
o.velocity = o.velocity.subtract(velocity);
|
|
309
|
+
engineObjectsCollidePairAdd(this, o);
|
|
239
310
|
|
|
240
|
-
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00');
|
|
311
|
+
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f00', undefined, false);
|
|
241
312
|
continue;
|
|
242
313
|
}
|
|
243
314
|
|
|
244
315
|
// check for collision
|
|
245
316
|
const sizeBoth = this.size.add(o.size);
|
|
246
|
-
|
|
317
|
+
// prefer to push up if small delta, up away from this object's own gravity
|
|
318
|
+
const smallStepUp = (gravityY > 0 ? o.pos.y - oldPos.y : oldPos.y - o.pos.y)*2 > sizeBoth.y - abs(gravityY);
|
|
247
319
|
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
248
320
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
249
321
|
const restitution = max(this.restitution, o.restitution);
|
|
@@ -258,10 +330,11 @@ class EngineObject
|
|
|
258
330
|
if (wasFalling)
|
|
259
331
|
this.groundObject = o;
|
|
260
332
|
|
|
261
|
-
// bounce if other object is fixed or grounded
|
|
262
|
-
|
|
333
|
+
// bounce if other object is fixed or grounded, relative to it so a rider keeps up with a
|
|
334
|
+
// platform moving down instead of landing on it again every few frames
|
|
335
|
+
this.velocity.y = o.velocity.y - (this.velocity.y - o.velocity.y) * restitution;
|
|
263
336
|
}
|
|
264
|
-
else
|
|
337
|
+
else // o has mass here, the other branch already handled the massless case
|
|
265
338
|
{
|
|
266
339
|
// inelastic collision
|
|
267
340
|
const inelastic = (this.mass * this.velocity.y + o.mass * o.velocity.y) / (this.mass + o.mass);
|
|
@@ -296,13 +369,14 @@ class EngineObject
|
|
|
296
369
|
this.velocity.x = lerp(inelastic, elastic0, restitution);
|
|
297
370
|
o.velocity.x = lerp(inelastic, elastic1, restitution);
|
|
298
371
|
}
|
|
299
|
-
else // bounce if other object is fixed
|
|
300
|
-
this.velocity.x
|
|
372
|
+
else // bounce if other object is fixed, relative to it as a landing is
|
|
373
|
+
this.velocity.x = o.velocity.x - (this.velocity.x - o.velocity.x) * restitution;
|
|
301
374
|
}
|
|
302
|
-
|
|
375
|
+
engineObjectsCollidePairAdd(this, o, true);
|
|
376
|
+
debugPhysics && debugOverlap(this.pos, this.size, o.pos, o.size, '#f0f', undefined, false);
|
|
303
377
|
}
|
|
304
378
|
}
|
|
305
|
-
if (this.
|
|
379
|
+
if (this.collideLevel)
|
|
306
380
|
{
|
|
307
381
|
// check collision against tiles
|
|
308
382
|
const hitLayer = tileCollisionTest(this.pos, this.size, this);
|
|
@@ -318,21 +392,17 @@ class EngineObject
|
|
|
318
392
|
const restitution = max(this.restitution, hitLayer.restitution);
|
|
319
393
|
if (isBlockedX)
|
|
320
394
|
{
|
|
321
|
-
//
|
|
322
|
-
//
|
|
323
|
-
// zero gravity defaults to the normal-gravity step-up direction)
|
|
395
|
+
// a ledge caught less than maxMove below its top lifts the object onto it instead of stopping it,
|
|
396
|
+
// down off a ceiling ledge when gravity points up; zero gravity counts as down
|
|
324
397
|
const epsilon = 1e-3;
|
|
325
398
|
const maxMove = .1;
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
floor(oldPos.y-this.size.y/2+1) + this.size.y/2 + epsilon
|
|
329
|
-
|
|
330
|
-
const delta = abs(y - this.pos.y);
|
|
331
|
-
if (delta < maxMove)
|
|
332
|
-
if (!tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
399
|
+
const y = gravityY > 0 ?
|
|
400
|
+
ceil( oldPos.y+this.size.y/2-1) - this.size.y/2 - epsilon :
|
|
401
|
+
floor(oldPos.y-this.size.y/2+1) + this.size.y/2 + epsilon;
|
|
402
|
+
if (abs(y - this.pos.y) < maxMove && !tileCollisionTest(vec2(this.pos.x, y), this.size, this))
|
|
333
403
|
{
|
|
334
404
|
this.pos.y = y;
|
|
335
|
-
debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
405
|
+
debugPhysics && debugRect(this.pos, this.size, '#ff0', 0, 0, false, false);
|
|
336
406
|
return;
|
|
337
407
|
}
|
|
338
408
|
|
|
@@ -348,9 +418,12 @@ class EngineObject
|
|
|
348
418
|
// this prevents gap between object and ground
|
|
349
419
|
const epsilon = .0001;
|
|
350
420
|
const offset = this.size.y/2 + epsilon;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
421
|
+
// rounded in the layer's space as its collision test is, or a bottom a hair below a
|
|
422
|
+
// grid line would round to the row under it, inside the floor, and fall through
|
|
423
|
+
const layerY = hitLayer.pos.y;
|
|
424
|
+
this.pos.y = layerY + (gravityY < 0 ?
|
|
425
|
+
floor(oldPos.y - layerY - this.size.y/2) + offset :
|
|
426
|
+
ceil( oldPos.y - layerY + this.size.y/2) - offset);
|
|
354
427
|
|
|
355
428
|
// set ground object for tile collision
|
|
356
429
|
this.groundObject = hitLayer;
|
|
@@ -364,7 +437,7 @@ class EngineObject
|
|
|
364
437
|
// bounce velocity
|
|
365
438
|
this.velocity.y *= -restitution;
|
|
366
439
|
}
|
|
367
|
-
debugPhysics && debugRect(this.pos, this.size, '#f00');
|
|
440
|
+
debugPhysics && debugRect(this.pos, this.size, '#f00', 0, 0, false, false);
|
|
368
441
|
}
|
|
369
442
|
}
|
|
370
443
|
}
|
|
@@ -377,14 +450,24 @@ class EngineObject
|
|
|
377
450
|
render()
|
|
378
451
|
{
|
|
379
452
|
// default object render
|
|
380
|
-
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
453
|
+
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor, glEnable, false);
|
|
381
454
|
}
|
|
382
455
|
|
|
383
456
|
/** Optional hook called during the light system plugin's lightmap pass to draw this object's lightmap contribution. Does nothing by default. */
|
|
384
457
|
renderLight() {}
|
|
385
458
|
|
|
459
|
+
/** Draw this object into the light system's shadow map, called during its shadow pass when castShadow is set.
|
|
460
|
+
* Calls render() by default so the object casts its own shape; override to cast a different one, like a blob at a character's feet so its body stays lit;
|
|
461
|
+
* screen space WebGL draws in render() are skipped during the pass */
|
|
462
|
+
renderShadow() { this.render(); }
|
|
463
|
+
|
|
464
|
+
/** Draw this object's shape into the light system's lightmap, called during its light pass when emissive is above
|
|
465
|
+
* 0; what it draws shows in its own colors that much brighter. Calls render() by default so the whole object
|
|
466
|
+
* glows; override to glow a part, like a robot's eyes */
|
|
467
|
+
renderEmissive() { this.render(); }
|
|
468
|
+
|
|
386
469
|
/** Destroy this object, destroy its children, detach its parent, and mark it for removal
|
|
387
|
-
* @param {boolean} [immediate] -
|
|
470
|
+
* @param {boolean} [immediate] - true removes attached effects like particle emitters at once, false lets them finish first */
|
|
388
471
|
destroy(immediate=false)
|
|
389
472
|
{
|
|
390
473
|
if (this.destroyed) return;
|
|
@@ -416,14 +499,21 @@ class EngineObject
|
|
|
416
499
|
worldToLocalVector(vec) { return vec.rotate(-this.angle); }
|
|
417
500
|
|
|
418
501
|
/** Called to check if a tile collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
|
|
502
|
+
* - Called for each solid tile the physics tests, which can be several times a frame for the same tile, and for
|
|
503
|
+
* positions it only tries, so keep it free of side effects or guard them to once a frame
|
|
504
|
+
* - this.pos has already moved, so a check on where it came from, like a one way platform, needs the position
|
|
505
|
+
* saved in update, as the platformer example does
|
|
419
506
|
* @param {number} tileData - the value of the tile at the position
|
|
420
|
-
* @param {Vector2} pos - tile
|
|
507
|
+
* @param {Vector2} pos - the tile's bottom left corner in world space
|
|
421
508
|
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity */
|
|
422
509
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
423
510
|
|
|
424
511
|
/** Called by the engine to check if an object collision should be resolved. Return true for physics to resolve the collision or false to ignore and resolve it manually.
|
|
512
|
+
* - Both objects of a touching pair are asked once a frame, whichever order they update in; an object that
|
|
513
|
+
* destroys itself here is gone at the end of the frame and is still asked about the pairs left this frame, so a
|
|
514
|
+
* bullet that should hit one thing checks its own destroyed flag first
|
|
425
515
|
* @param {EngineObject} object - the object to test against
|
|
426
|
-
* @param {
|
|
516
|
+
* @param {Vector3} [push] - what it would take to move this object clear, a Vector3 from the 3D plugin, undefined in 2D
|
|
427
517
|
* @return {boolean} - true if the collision should be resolved by modifying it's position and velocity
|
|
428
518
|
*/
|
|
429
519
|
collideWithObject(object, push) { return true; }
|
|
@@ -447,16 +537,19 @@ class EngineObject
|
|
|
447
537
|
getSpeed() { return this.velocity.length(); }
|
|
448
538
|
|
|
449
539
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
540
|
+
* - Does nothing on a static object (mass 0), set its velocity instead
|
|
450
541
|
* @param {Vector2} acceleration */
|
|
451
542
|
applyAcceleration(acceleration)
|
|
452
543
|
{ if (this.mass) this.velocity = this.velocity.add(acceleration); }
|
|
453
544
|
|
|
454
545
|
/** Apply angular acceleration to this object
|
|
546
|
+
* - Does nothing on a static object (mass 0), set its angleVelocity instead
|
|
455
547
|
* @param {number} acceleration */
|
|
456
548
|
applyAngularAcceleration(acceleration)
|
|
457
549
|
{ if (this.mass) this.angleVelocity += acceleration; }
|
|
458
550
|
|
|
459
551
|
/** Apply force to this object (adjust velocity, affected by mass)
|
|
552
|
+
* - Does nothing on a static object (mass 0), set its velocity instead
|
|
460
553
|
* @param {Vector2} force */
|
|
461
554
|
applyForce(force)
|
|
462
555
|
{ if (this.mass) this.applyAcceleration(force.scale(1/this.mass)); }
|
|
@@ -474,9 +567,11 @@ class EngineObject
|
|
|
474
567
|
{
|
|
475
568
|
ASSERT(!this.destroyed, 'cannot add child to destroyed object');
|
|
476
569
|
if (this.destroyed) return child;
|
|
477
|
-
ASSERT(!child.parent && !this.children.includes(child));
|
|
570
|
+
ASSERT(!child.parent && !this.children.includes(child), 'child already has a parent, removeChild it first or use attach');
|
|
478
571
|
ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
|
|
479
|
-
ASSERT(child
|
|
572
|
+
ASSERT(!child.destroyed, 'cannot add a destroyed child');
|
|
573
|
+
for (let p = /** @type {EngineObject} */ (this); p; p = p.parent)
|
|
574
|
+
ASSERT(p !== child, 'cannot add an object as a child of itself or of its own child');
|
|
480
575
|
this.children.push(child);
|
|
481
576
|
child.parent = this;
|
|
482
577
|
child.localPos = localPos.copy();
|
|
@@ -485,17 +580,33 @@ class EngineObject
|
|
|
485
580
|
return child;
|
|
486
581
|
}
|
|
487
582
|
|
|
488
|
-
/**
|
|
583
|
+
/** Attaches a child to this without moving it: the local transform is worked out from where the child is now,
|
|
584
|
+
* where addChild takes one; a child of something else is moved over, returns child for chaining
|
|
585
|
+
* @param {EngineObject} child
|
|
586
|
+
* @return {EngineObject} The child object attached */
|
|
587
|
+
attach(child)
|
|
588
|
+
{
|
|
589
|
+
ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
|
|
590
|
+
ASSERT(child !== this, 'cannot attach self');
|
|
591
|
+
child.parent?.removeChild(child);
|
|
592
|
+
// the local values that updateTransforms turns back into the child's current pos and angle
|
|
593
|
+
const mirror = this.getMirrorSign(), local = this.worldToLocal(child.pos);
|
|
594
|
+
local.x *= mirror;
|
|
595
|
+
return this.addChild(child, local, mirror * (child.angle - this.angle));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** Removes a child from this one, it stays where it is in the world
|
|
489
599
|
* @param {EngineObject} child */
|
|
490
600
|
removeChild(child)
|
|
491
601
|
{
|
|
492
602
|
ASSERT(child.parent === this && this.children.includes(child));
|
|
493
|
-
this.children.
|
|
494
|
-
child
|
|
603
|
+
const i = this.children.indexOf(child);
|
|
604
|
+
if (i < 0) return; // not a child of this one, release has no assert
|
|
605
|
+
this.children.splice(i, 1);
|
|
606
|
+
child.parent = child.localPos = undefined;
|
|
495
607
|
}
|
|
496
608
|
|
|
497
|
-
/** Check if
|
|
498
|
-
* Collisions are resolved to prevent overlaps
|
|
609
|
+
/** Check if this object's box overlaps another object's box
|
|
499
610
|
* @param {EngineObject} object
|
|
500
611
|
* @return {boolean} */
|
|
501
612
|
isOverlappingObject(object)
|
|
@@ -511,18 +622,23 @@ class EngineObject
|
|
|
511
622
|
/** Set how this object collides
|
|
512
623
|
* @param {boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
513
624
|
* @param {boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
|
514
|
-
* @param {boolean} [
|
|
625
|
+
* @param {boolean} [collideLevel] - Does it collide with the level, its tile collision layers?
|
|
515
626
|
* @param {boolean} [collideRaycast] - Does it collide with raycasts? */
|
|
516
|
-
setCollision(collideSolidObjects=true, isSolid=true,
|
|
627
|
+
setCollision(collideSolidObjects=true, isSolid=true, collideLevel=true, collideRaycast=true)
|
|
517
628
|
{
|
|
518
629
|
ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
|
|
519
630
|
|
|
520
631
|
this.collideSolidObjects = collideSolidObjects;
|
|
521
632
|
this.isSolid = isSolid;
|
|
522
|
-
this.
|
|
633
|
+
this.collideLevel = collideLevel;
|
|
523
634
|
this.collideRaycast = collideRaycast;
|
|
524
635
|
}
|
|
525
636
|
|
|
637
|
+
/** @deprecated since 1.20, use collideLevel
|
|
638
|
+
* @type {boolean} */
|
|
639
|
+
get collideTiles() { return this.collideLevel; }
|
|
640
|
+
set collideTiles(collide) { this.collideLevel = collide; }
|
|
641
|
+
|
|
526
642
|
/** Returns string containing info about this object for debugging
|
|
527
643
|
* @return {string} */
|
|
528
644
|
toString()
|
|
@@ -536,8 +652,7 @@ class EngineObject
|
|
|
536
652
|
text += '\nsize = ' + this.size;
|
|
537
653
|
if (this.angle)
|
|
538
654
|
text += '\nangle = ' + this.angle.toFixed(3);
|
|
539
|
-
|
|
540
|
-
text += '\ncolor = ' + this.color;
|
|
655
|
+
text += '\ncolor = ' + this.color;
|
|
541
656
|
return text;
|
|
542
657
|
}
|
|
543
658
|
|
|
@@ -547,15 +662,15 @@ class EngineObject
|
|
|
547
662
|
if (!debug) return;
|
|
548
663
|
|
|
549
664
|
// check if there is anything to show
|
|
550
|
-
const hasPhysics = this.
|
|
665
|
+
const hasPhysics = this.collideLevel || this.collideSolidObjects || this.isSolid;
|
|
551
666
|
if (!hasPhysics && !this.parent) return;
|
|
552
667
|
|
|
553
668
|
// show object info for debugging
|
|
554
669
|
const size = vec2(max(this.size.x, .2), max(this.size.y, .2));
|
|
555
|
-
const color = rgb(this.
|
|
556
|
-
debugRect(this.pos, size, color, 0, this.angle, hasPhysics);
|
|
670
|
+
const color = rgb(this.collideLevel?1:0, this.collideSolidObjects?1:0, this.isSolid?1:0, .5);
|
|
671
|
+
debugRect(this.pos, size, color, 0, hasPhysics ? 0 : this.angle, hasPhysics, false); // collision ignores the angle
|
|
557
672
|
if (this.parent)
|
|
558
|
-
debugRect(this.pos, size.scale(.8), rgb(1,1,1,.5), 0, this.angle);
|
|
559
|
-
this.parent && debugLine(this.pos, this.parent.pos, rgb(1,1,1,.5), .5);
|
|
673
|
+
debugRect(this.pos, size.scale(.8), rgb(1,1,1,.5), 0, this.angle, false, false);
|
|
674
|
+
this.parent && debugLine(this.pos, this.parent.pos, rgb(1,1,1,.5), .5, 0, false);
|
|
560
675
|
}
|
|
561
676
|
}
|