Skip to content

Commit 4ad7993

Browse files
committed
Added fixed timestep logic to Arcade Physics World
1 parent 2dc7f1d commit 4ad7993

1 file changed

Lines changed: 135 additions & 14 deletions

File tree

src/physics/arcade/World.js

Lines changed: 135 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,56 @@ var World = new Class({
245245
right: GetValue(config, 'checkCollision.right', true)
246246
};
247247

248+
/**
249+
* [description]
250+
*
251+
* @name Phaser.Physics.Arcade.World#desiredFps
252+
* @readOnly
253+
* @type {number}
254+
* @since 3.10.0
255+
*/
256+
this.desiredFps = GetValue(config, 'desiredFps', 60);
257+
258+
/**
259+
* [description]
260+
*
261+
* @name Phaser.Physics.Arcade.World#_elapsed
262+
* @private
263+
* @type {number}
264+
* @since 3.10.0
265+
*/
266+
this._elapsed = 0;
267+
268+
this._desiredFpsMult = 1 / this.desiredFps;
269+
270+
/**
271+
* @property {number} _lastCount - Remember how many 'catch-up' iterations were used on the logicUpdate last frame.
272+
* @private
273+
*/
274+
this._lastCount = 0;
275+
276+
/**
277+
* @property {number} _spiraling - If the 'catch-up' iterations are spiraling out of control, this counter is incremented.
278+
* @private
279+
*/
280+
this._spiraling = 0;
281+
282+
this._nextFpsNotification = 0;
283+
284+
/**
285+
* Scaling factor to make the game move smoothly in slow motion (or fast motion)
286+
*
287+
* - 1.0 = normal speed
288+
* - 2.0 = half speed
289+
* - 0.5 = double speed
290+
*
291+
* You likely need to adjust {@link #desiredFps} as well such that `desiredFps / timeScale === 60`.
292+
*
293+
* @property {number} timeScale
294+
* @default
295+
*/
296+
this.timeScale = 1.0;
297+
248298
/**
249299
* The maximum absolute difference of a Body's per-step velocity and its overlap with another Body that will result in separation on *each axis*.
250300
* Larger values favor separation.
@@ -436,7 +486,7 @@ var World = new Class({
436486
*/
437487
enableBody: function (object, bodyType)
438488
{
439-
if (object.body === null)
489+
if (!object.body)
440490
{
441491
if (bodyType === CONST.DYNAMIC_BODY)
442492
{
@@ -767,13 +817,73 @@ var World = new Class({
767817
return;
768818
}
769819

770-
// this.delta = Math.min(delta / 1000, this.maxStep) * this.timeScale;
771-
delta /= 1000;
820+
// if the logic time is spiraling upwards, skip a frame entirely
821+
if (this._spiraling > 1)
822+
{
823+
// cause an event to warn the program that this CPU can't keep up with the current desiredFps rate
824+
if (time > this._nextFpsNotification)
825+
{
826+
// only permit one fps notification per 10 seconds
827+
this._nextFpsNotification = time + 10000;
772828

773-
this.delta = delta;
829+
console.log('fps warning');
774830

775-
// Update all active bodies
831+
// dispatch the notification signal
832+
// this.fpsProblemNotifier.dispatch();
833+
}
834+
835+
// reset the _deltaTime accumulator which will cause all pending late updates to be permanently skipped
836+
// console.log('skipped', this._spiraling);
837+
838+
this._elapsed = 0;
839+
this._spiraling = 0;
840+
}
841+
else
842+
{
843+
// stepsThisFrame
844+
//
845+
// TODO - Allow you to run the update at a HIGHER frame rate, if required
846+
// TODO - Disable RTree entirely
847+
// TODO - Option to load the RTree so it doesn't get cleared every frame
848+
849+
// Step size taking into account the slow motion speed
850+
var slowStep = this.timeScale * 1000.0 / this.desiredFps;
776851

852+
// Accumulate time until the slowStep threshold is met or exceeded... up to a limit of 3 catch-up frames at slowStep intervals
853+
this._elapsed += Math.max(Math.min(slowStep * 3, delta), 0);
854+
855+
// Run the update logic multiple times if necessary to "catch up" with dropped frames
856+
var count = 0;
857+
858+
while (this._elapsed >= slowStep)
859+
{
860+
this._elapsed -= slowStep;
861+
862+
this.updateLogic(this._desiredFpsMult);
863+
864+
count++;
865+
}
866+
867+
// detect spiraling (if the catch-up loop isn't fast enough, the number of iterations will increase constantly)
868+
if (count > this._lastCount)
869+
{
870+
this._spiraling++;
871+
// console.log('spiral++', this._spiraling, 'count', count, 'last', this._lastCount);
872+
}
873+
else if (count < this._lastCount)
874+
{
875+
// looks like it caught up successfully, reset the spiral alert counter
876+
// console.log('spiral solved', this._spiraling, 'count', count, 'last', this._lastCount);
877+
this._spiraling = 0;
878+
}
879+
880+
this._lastCount = count;
881+
}
882+
},
883+
884+
updateLogic: function (delta)
885+
{
886+
// Update all active bodies
777887
var i;
778888
var body;
779889
var bodies = this.bodies.entries;
@@ -790,6 +900,10 @@ var World = new Class({
790900
}
791901

792902
// Populate our dynamic collision tree
903+
//
904+
// If we skip the tree clear + load we can
905+
// go from 10,000 bodies updating per frame to 75,000
906+
793907
this.tree.clear();
794908
this.tree.load(bodies);
795909

@@ -911,7 +1025,7 @@ var World = new Class({
9111025
var velocityDelta = this.computeVelocity(0, body, body.angularVelocity, body.angularAcceleration, body.angularDrag, body.maxAngular) - body.angularVelocity;
9121026

9131027
body.angularVelocity += velocityDelta;
914-
body.rotation += (body.angularVelocity * this.delta);
1028+
body.rotation += (body.angularVelocity * this._desiredFpsMult);
9151029
}
9161030

9171031
body.velocity.x = this.computeVelocity(1, body, body.velocity.x, body.acceleration.x, body.drag.x, body.maxVelocity.x);
@@ -937,22 +1051,24 @@ var World = new Class({
9371051
{
9381052
if (max === undefined) { max = 10000; }
9391053

1054+
var delta = this._desiredFpsMult;
1055+
9401056
if (axis === 1 && body.allowGravity)
9411057
{
942-
velocity += (this.gravity.x + body.gravity.x) * this.delta;
1058+
velocity += (this.gravity.x + body.gravity.x) * delta;
9431059
}
9441060
else if (axis === 2 && body.allowGravity)
9451061
{
946-
velocity += (this.gravity.y + body.gravity.y) * this.delta;
1062+
velocity += (this.gravity.y + body.gravity.y) * delta;
9471063
}
9481064

9491065
if (acceleration)
9501066
{
951-
velocity += acceleration * this.delta;
1067+
velocity += acceleration * delta;
9521068
}
9531069
else if (drag && body.allowDrag)
9541070
{
955-
drag *= this.delta;
1071+
drag *= delta;
9561072

9571073
if (velocity - drag > 0)
9581074
{
@@ -1263,16 +1379,18 @@ var World = new Class({
12631379
}
12641380
}
12651381

1382+
var delta = this._desiredFpsMult;
1383+
12661384
if (!body1.immovable)
12671385
{
1268-
body1.x += (body1.velocity.x * this.delta) - overlap * Math.cos(angleCollision);
1269-
body1.y += (body1.velocity.y * this.delta) - overlap * Math.sin(angleCollision);
1386+
body1.x += (body1.velocity.x * delta) - overlap * Math.cos(angleCollision);
1387+
body1.y += (body1.velocity.y * delta) - overlap * Math.sin(angleCollision);
12701388
}
12711389

12721390
if (!body2.immovable)
12731391
{
1274-
body2.x += (body2.velocity.x * this.delta) + overlap * Math.cos(angleCollision);
1275-
body2.y += (body2.velocity.y * this.delta) + overlap * Math.sin(angleCollision);
1392+
body2.x += (body2.velocity.x * delta) + overlap * Math.cos(angleCollision);
1393+
body2.y += (body2.velocity.y * delta) + overlap * Math.sin(angleCollision);
12761394
}
12771395

12781396
if (body1.onCollide || body2.onCollide)
@@ -1630,6 +1748,9 @@ var World = new Class({
16301748
minMax.maxX = bodyA.right;
16311749
minMax.maxY = bodyA.bottom;
16321750

1751+
// TODO - Optional tree
1752+
// TODO - Can we just get the body from the results array, rather than doing an indexOf???
1753+
16331754
var results = (group.physicsType === CONST.DYNAMIC_BODY) ? this.tree.search(minMax) : this.staticTree.search(minMax);
16341755

16351756
if (results.length === 0)

0 commit comments

Comments
 (0)