Skip to content

Commit 6db8b8e

Browse files
committed
[#1091] World physic implementation is now properly tight to its corresponding parent application/game
no more direct reference to the global namespace: - world is now properly attached to an "Application" - Body are now properly updated based on their "parent" world gravity - Quadtree also now refers to its parent World when required
1 parent a055364 commit 6db8b8e

File tree

5 files changed

+120
-78
lines changed

5 files changed

+120
-78
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Core: full ES6 refactoring of `me.device`, and API clean-up (@see https://github.com/melonjs/melonJS/wiki/Upgrade-Guide#120x-to-130x-stable)
1212
- Loader: `onload` and `onerror` callbacks are now optionals when directly loading assets (easier with base64 encoded assets)
1313
- Game: refactoring of the global `game` into an instantiable `Application` object, with `game` now being the default instance of it (@see #1091)
14+
- Physic: World physic implementation is now properly tight to its corresponding parent application/game (@see #1091)
1415

1516
### Fixed
1617
- Loader: fix loading/preloading of base64 audio assets, and base64 encoded FontFace

src/application/application.js

+3
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ class Application {
8484
* init the game instance (create a physic world, update starting time, etc..)
8585
*/
8686
init() {
87+
// create a new physic world
8788
this.world = new World();
89+
// set the reference to this application instance
90+
this.world.app = this;
8891
this.lastUpdate = globalThis.performance.now();
8992
event.emit(event.GAME_INIT, this);
9093
}

src/physics/body.js

+18-46
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import collision from "./collision.js";
77
import * as arrayUtil from "./../utils/array.js";
88
import timer from "./../system/timer.js";
99
import { clamp } from "./../math/math.js";
10-
import game from "./../game.js";
11-
1210

1311
/**
1412
* @classdesc
@@ -463,10 +461,12 @@ class Body {
463461
this.vel.y *= -this.bounce;
464462
}
465463

466-
// cancel the falling an jumping flags if necessary
467-
var dir = Math.sign(game.world.gravity.y * this.gravityScale) || 1;
468-
this.falling = overlap.y >= dir;
469-
this.jumping = overlap.y <= -dir;
464+
if (!this.ignoreGravity) {
465+
// cancel the falling an jumping flags if necessary
466+
var dir = this.falling === true ? 1 : this.jumping === true ? -1 : 0;
467+
this.falling = overlap.y >= dir;
468+
this.jumping = overlap.y <= -dir;
469+
}
470470
}
471471
}
472472

@@ -507,14 +507,12 @@ class Body {
507507
}
508508
}
509509

510-
511510
/**
512511
* Returns true if the any of the shape composing the body contains the given point.
513512
* @method Body#contains
514513
* @param {Vector2d} point
515514
* @returns {boolean} true if contains
516515
*/
517-
518516
/**
519517
* Returns true if the any of the shape composing the body contains the given point.
520518
* @param {number} x x coordinate
@@ -591,27 +589,23 @@ class Body {
591589
}
592590

593591
/**
594-
* compute the new velocity value
595-
* @ignore
592+
* Updates the parent's position as well as computes the new body's velocity based
593+
* on the values of force/friction. Velocity chages are proportional to the
594+
* me.timer.tick value (which can be used to scale velocities). The approach to moving the
595+
* parent renderable is to compute new values of the Body.vel property then add them to
596+
* the parent.pos value thus changing the postion the amount of Body.vel each time the
597+
* update call is made. <br>
598+
* Updates to Body.vel are bounded by maxVel (which defaults to viewport size if not set) <br>
599+
* At this time a call to Body.Update does not call the onBodyUpdate callback that is listed in the constructor arguments.
600+
* @protected
601+
* @param {number} dt time since the last update in milliseconds.
602+
* @returns {boolean} true if resulting velocity is different than 0
596603
*/
597-
computeVelocity(/* dt */) {
604+
update(dt) { // eslint-disable-line no-unused-vars
598605
// apply timer.tick to delta time for linear interpolation (when enabled)
599606
// #761 add delta time in body update
600607
var deltaTime = /* dt * */ timer.tick;
601608

602-
// apply gravity to the current velocity
603-
if (!this.ignoreGravity) {
604-
var worldGravity = game.world.gravity;
605-
606-
// apply gravity if defined
607-
this.vel.x += worldGravity.x * this.gravityScale * deltaTime;
608-
this.vel.y += worldGravity.y * this.gravityScale * deltaTime;
609-
610-
// check if falling / jumping
611-
this.falling = (this.vel.y * Math.sign(worldGravity.y * this.gravityScale)) > 0;
612-
this.jumping = (this.falling ? false : this.jumping);
613-
}
614-
615609
// apply force if defined
616610
if (this.force.x !== 0) {
617611
this.vel.x += this.force.x * deltaTime;
@@ -649,28 +643,6 @@ class Body {
649643
if (this.vel.x !== 0) {
650644
this.vel.x = clamp(this.vel.x, -this.maxVel.x, this.maxVel.x);
651645
}
652-
}
653-
654-
/**
655-
* Updates the parent's position as well as computes the new body's velocity based
656-
* on the values of force/friction/gravity. Velocity chages are proportional to the
657-
* me.timer.tick value (which can be used to scale velocities). The approach to moving the
658-
* parent renderable is to compute new values of the Body.vel property then add them to
659-
* the parent.pos value thus changing the postion the amount of Body.vel each time the
660-
* update call is made. <br>
661-
* Updates to Body.vel are bounded by maxVel (which defaults to viewport size if not set) <br>
662-
*
663-
* In addition, when the gravity calcuation is made, if the Body.vel.y > 0 then the Body.falling
664-
* property is set to true and Body.jumping is set to !Body.falling.
665-
*
666-
* At this time a call to Body.Update does not call the onBodyUpdate callback that is listed in the constructor arguments.
667-
* @protected
668-
* @param {number} dt time since the last update in milliseconds.
669-
* @returns {boolean} true if resulting velocity is different than 0
670-
*/
671-
update(dt) {
672-
// update the velocity
673-
this.computeVelocity(dt);
674646

675647
// update the body ancestor position
676648
this.ancestor.pos.add(this.vel);

src/physics/quadtree.js

+58-29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Vector2d from "./../math/vector2.js";
22
import Container from "./../renderable/container.js";
33
import * as arrayUtil from "./../utils/array.js";
4-
import game from "./../game.js";
54

65
/*
76
* A QuadTree implementation in JavaScript, a 2d spatial subdivision algorithm.
@@ -21,16 +20,17 @@ var QT_ARRAY = [];
2120
* or create a new one if the array is empty
2221
* @ignore
2322
*/
24-
function QT_ARRAY_POP(bounds, max_objects = 4, max_levels = 4, level = 0) {
23+
function QT_ARRAY_POP(world, bounds, max_objects = 4, max_levels = 4, level = 0) {
2524
if (QT_ARRAY.length > 0) {
2625
var _qt = QT_ARRAY.pop();
26+
_qt.world = world;
2727
_qt.bounds = bounds;
2828
_qt.max_objects = max_objects;
2929
_qt.max_levels = max_levels;
3030
_qt.level = level;
3131
return _qt;
3232
} else {
33-
return new QuadTree(bounds, max_objects, max_levels, level);
33+
return new QuadTree(world, bounds, max_objects, max_levels, level);
3434
}
3535
};
3636

@@ -55,12 +55,17 @@ var QT_VECTOR = new Vector2d();
5555
*/
5656
class QuadTree {
5757
/**
58+
* @param {World} world the physic world this QuadTree belongs to
5859
* @param {Bounds} bounds bounds of the node
5960
* @param {number} [max_objects=4] max objects a node can hold before splitting into 4 subnodes
6061
* @param {number} [max_levels=4] total max levels inside root Quadtree
6162
* @param {number} [level] deepth level, required for subnodes
6263
*/
63-
constructor(bounds, max_objects = 4, max_levels = 4, level = 0) {
64+
constructor(world, bounds, max_objects = 4, max_levels = 4, level = 0) {
65+
66+
this.world = world;
67+
this.bounds = bounds;
68+
6469
this.max_objects = max_objects;
6570
this.max_levels = max_levels;
6671

@@ -82,36 +87,60 @@ class QuadTree {
8287
top = this.bounds.top;
8388

8489
//top right node
85-
this.nodes[0] = QT_ARRAY_POP({
86-
left : left + subWidth,
87-
top : top,
88-
width : subWidth,
89-
height : subHeight
90-
}, this.max_objects, this.max_levels, nextLevel);
90+
this.nodes[0] = QT_ARRAY_POP(
91+
this.world,
92+
this.bounds, {
93+
left : left + subWidth,
94+
top : top,
95+
width : subWidth,
96+
height : subHeight
97+
},
98+
this.max_objects,
99+
this.max_levels,
100+
nextLevel
101+
);
91102

92103
//top left node
93-
this.nodes[1] = QT_ARRAY_POP({
94-
left : left,
95-
top: top,
96-
width : subWidth,
97-
height : subHeight
98-
}, this.max_objects, this.max_levels, nextLevel);
104+
this.nodes[1] = QT_ARRAY_POP(
105+
this.world,
106+
this.bounds, {
107+
left : left,
108+
top: top,
109+
width : subWidth,
110+
height : subHeight
111+
},
112+
this.max_objects,
113+
this.max_levels,
114+
nextLevel
115+
);
99116

100117
//bottom left node
101-
this.nodes[2] = QT_ARRAY_POP({
102-
left : left,
103-
top : top + subHeight,
104-
width : subWidth,
105-
height : subHeight
106-
}, this.max_objects, this.max_levels, nextLevel);
118+
this.nodes[2] = QT_ARRAY_POP(
119+
this.world,
120+
this.bounds, {
121+
left : left,
122+
top : top + subHeight,
123+
width : subWidth,
124+
height : subHeight
125+
},
126+
this.max_objects,
127+
this.max_levels,
128+
nextLevel
129+
);
107130

108131
//bottom right node
109-
this.nodes[3] = QT_ARRAY_POP({
110-
left : left + subWidth,
111-
top : top + subHeight,
112-
width : subWidth,
113-
height : subHeight
114-
}, this.max_objects, this.max_levels, nextLevel);
132+
this.nodes[3] = QT_ARRAY_POP(
133+
this.world,
134+
this.bounds, {
135+
left : left + subWidth,
136+
top : top + subHeight,
137+
width : subWidth,
138+
height : subHeight
139+
},
140+
this.max_objects,
141+
this.max_levels,
142+
nextLevel
143+
);
115144
}
116145

117146
/*
@@ -125,7 +154,7 @@ class QuadTree {
125154

126155
// use game world coordinates for floating items
127156
if (item.isFloating === true) {
128-
pos = game.viewport.localToWorld(bounds.left, bounds.top, QT_VECTOR);
157+
pos = this.world.app.viewport.localToWorld(bounds.left, bounds.top, QT_VECTOR);
129158
} else {
130159
pos = QT_VECTOR.set(item.left, item.top);
131160
}

src/physics/world.js

+40-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Container from "./../renderable/container.js";
55
import collision from "./collision.js";
66
import { collisionCheck } from "./detector.js";
77
import state from "./../state/state.js";
8+
import timer from "./../system/timer.js";
89

910
/**
1011
* @classdesc
@@ -28,6 +29,13 @@ class World extends Container {
2829
// to mimic the previous behavior
2930
this.anchorPoint.set(0, 0);
3031

32+
/**
33+
* the application (game) this physic world belong to
34+
* @public
35+
* @type {Application}
36+
*/
37+
this.app = null;
38+
3139
/**
3240
* the rate at which the game world is updated,
3341
* may be greater than or lower than the display fps
@@ -80,7 +88,7 @@ class World extends Container {
8088
* @public
8189
* @type {QuadTree}
8290
*/
83-
this.broadphase = new QuadTree(this.getBounds().clone(), collision.maxChildren, collision.maxDepth);
91+
this.broadphase = new QuadTree(this, this.getBounds().clone(), collision.maxChildren, collision.maxDepth);
8492

8593
// reset the world container on the game reset signal
8694
event.on(event.GAME_RESET, this.reset, this);
@@ -140,14 +148,41 @@ class World extends Container {
140148
return this;
141149
}
142150

151+
/**
152+
* Apply gravity to the given body
153+
* @name bodyApplyVelocity
154+
* @memberof World
155+
* @private
156+
* @param {Body} body
157+
* @param {number} dt the time passed since the last frame update
158+
*/
159+
bodyApplyGravity(body, dt) { // eslint-disable-line no-unused-vars
160+
// apply timer.tick to delta time for linear interpolation (when enabled)
161+
// #761 add delta time in body update
162+
var deltaTime = /*dt * */ timer.tick;
163+
164+
// apply gravity to the current velocity
165+
if (!body.ignoreGravity) {
166+
var worldGravity = this.gravity;
167+
168+
// apply gravity if defined
169+
body.vel.x += worldGravity.x * body.gravityScale * deltaTime;
170+
body.vel.y += worldGravity.y * body.gravityScale * deltaTime;
171+
172+
// check if falling / jumping
173+
body.falling = (body.vel.y * Math.sign(worldGravity.y * body.gravityScale)) > 0;
174+
body.jumping = (body.falling ? false : body.jumping);
175+
}
176+
}
177+
143178
/**
144179
* update the game world
145180
* @name reset
146181
* @memberof World
147182
* @param {number} dt the time passed since the last frame update
148183
* @returns {boolean} true if the word is dirty
149184
*/
150-
update (dt) {
185+
update(dt) {
151186
var isPaused = state.isPaused();
152187

153188
// clear the quadtree
@@ -163,7 +198,9 @@ class World extends Container {
163198
// if the game is not paused, and ancestor can be updated
164199
if (!(isPaused && (!ancestor.updateWhenPaused)) &&
165200
(ancestor.inViewport || ancestor.alwaysUpdate)) {
166-
// apply physics to the body (this moves it)
201+
// apply gravity to this body
202+
this.bodyApplyGravity(body, dt);
203+
// body update function (this moves it)
167204
if (body.update(dt) === true) {
168205
// mark ancestor as dirty
169206
ancestor.isDirty = true;

0 commit comments

Comments
 (0)