arcade-physics
Version:
Use Arcade Physics without Phaser.
1,180 lines • 83.4 kB
JavaScript
"use strict";
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2020 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.World = void 0;
const BetweenPoints_1 = __importDefault(require("../../math/angle/BetweenPoints"));
const Clamp_1 = __importDefault(require("../../math/Clamp"));
const DistanceBetween_1 = __importDefault(require("../../math/distance/DistanceBetween"));
const eventemitter3_1 = __importDefault(require("eventemitter3"));
const events_1 = __importDefault(require("./events"));
const Equal_1 = __importDefault(require("../../math/fuzzy/Equal"));
const GreaterThan_1 = __importDefault(require("../../math/fuzzy/GreaterThan"));
const LessThan_1 = __importDefault(require("../../math/fuzzy/LessThan"));
const GetOverlapX_1 = require("./GetOverlapX");
const GetOverlapY_1 = require("./GetOverlapY");
const GetValue_1 = __importDefault(require("../../utils/object/GetValue"));
const RTree_1 = __importDefault(require("../../structs/RTree"));
const Rectangle_1 = require("../../geom/rectangle/Rectangle");
const SeparateX_1 = require("./SeparateX");
const SeparateY_1 = require("./SeparateY");
const Wrap_1 = __importDefault(require("../../math/Wrap"));
const const_1 = __importDefault(require("./const"));
const const_2 = __importDefault(require("../../math/const"));
const Collider_1 = require("./Collider");
const ProcessQueue_1 = require("../../structs/ProcessQueue");
const Vector2_1 = require("../../math/Vector2");
class World extends eventemitter3_1.default {
/**
* The Arcade Physics World.
*
* The World is responsible for creating, managing, colliding and updating all of the bodies within it.
*
* An instance of the World belongs to a Phaser.Scene and is accessed via the property `physics.world`.
*
* @param scene The Scene this simulation belongs to.
* @param config An Arcade Physics Configuration object.
*/
constructor(scene, config) {
super();
this.scene = scene;
this.config = config;
/** Dynamic Bodies in this simulation. */
this.bodies = new Set();
/** Static Bodies in this simulation. */
this.staticBodies = new Set();
/** Static Bodies marked for deletion. */
this.pendingDestroy = new Set();
/** The amount of elapsed ms since the last frame. */
this._elapsed = 0;
/** This simulation's collision processors. */
this.colliders = new ProcessQueue_1.ProcessQueue();
/** Acceleration of Bodies due to gravity, in pixels per second. */
this.gravity = new Vector2_1.Vector2((0, GetValue_1.default)(config, 'gravity.x', 0), (0, GetValue_1.default)(config, 'gravity.y', 0));
this.bounds = new Rectangle_1.Rectangle((0, GetValue_1.default)(config, 'x', 0), (0, GetValue_1.default)(config, 'y', 0), (0, GetValue_1.default)(config, 'width', scene.sys.scale.width), (0, GetValue_1.default)(config, 'height', scene.sys.scale.height));
/** The boundary edges that Bodies can collide with. */
this.checkCollision = {
up: (0, GetValue_1.default)(config, 'checkCollision.up', true),
down: (0, GetValue_1.default)(config, 'checkCollision.down', true),
left: (0, GetValue_1.default)(config, 'checkCollision.left', true),
right: (0, GetValue_1.default)(config, 'checkCollision.right', true)
};
/**
* The number of physics steps to be taken per second.
*
* This property is read-only. Use the `setFPS` method to modify it at run-time.
*/
this.fps = (0, GetValue_1.default)(config, 'fps', 60);
/**
* Should Physics use a fixed update time-step (true) or sync to the render fps (false)?.
* False value of this property disables fps and timeScale properties.
*/
this.fixedStep = (0, GetValue_1.default)(config, 'fixedStep', true);
/** Internal frame time value. */
this._frameTime = 1 / this.fps;
/** Internal frame time ms value. */
this._frameTimeMS = 1000 * this._frameTime;
/** The number of steps that took place in the last frame. */
this.stepsLastFrame = 0;
/**
* Scaling factor applied to the frame rate.
*
* - 1.0 = normal speed
* - 2.0 = half speed
* - 0.5 = double speed
*/
this.timeScale = (0, GetValue_1.default)(config, 'timeScale', 1);
this.OVERLAP_BIAS = (0, GetValue_1.default)(config, 'overlapBias', 4);
/**
* The maximum absolute value of a Body's overlap with a tile that will result in separation on *each axis*.
* Larger values favor separation.
* Smaller values favor no separation.
* The optimum value may be similar to the tile size.
* @default 16
*/
this.TILE_BIAS = (0, GetValue_1.default)(config, 'tileBias', 16);
/**
* Always separate overlapping Bodies horizontally before vertically.
* False (the default) means Bodies are first separated on the axis of greater gravity, or the vertical axis if neither is greater.
* @default false
*/
this.forceX = (0, GetValue_1.default)(config, 'forceX', false);
/**
* Whether the simulation advances with the game loop.
* @default false
*/
this.isPaused = (0, GetValue_1.default)(config, 'isPaused', false);
/**
* Temporary total of colliding Bodies.
* @default 0
*/
this._total = 0;
/**
* Enables the debug display.
* @default false
*/
this.drawDebug = (0, GetValue_1.default)(config, 'debug', false);
/**
* The graphics object drawing the debug display.
*
* @name Phaser.Physics.Arcade.World#debugGraphic
* @type {Phaser.GameObjects.Graphics}
* @since 3.0.0
*/
this.debugGraphic;
this.defaults = {
debugShowBody: (0, GetValue_1.default)(config, 'debugShowBody', true),
debugShowStaticBody: (0, GetValue_1.default)(config, 'debugShowStaticBody', true),
debugShowVelocity: (0, GetValue_1.default)(config, 'debugShowVelocity', true),
bodyDebugColor: (0, GetValue_1.default)(config, 'debugBodyColor', 0xff00ff),
staticBodyDebugColor: (0, GetValue_1.default)(config, 'debugStaticBodyColor', 0x0000ff),
velocityDebugColor: (0, GetValue_1.default)(config, 'debugVelocityColor', 0x00ff00)
};
/**
* The maximum number of items per node on the RTree.
*
* This is ignored if `useTree` is `false`. If you have a large number of bodies in
* your world then you may find search performance improves by increasing this value,
* to allow more items per node and less node division.
*
* @name Phaser.Physics.Arcade.World#maxEntries
* @type {number}
* @default 16
* @since 3.0.0
*/
this.maxEntries = (0, GetValue_1.default)(config, 'maxEntries', 16);
/**
* Should this Arcade Physics World use an RTree for Dynamic bodies?
*
* An RTree is a fast way of spatially sorting of all the bodies in the world.
* However, at certain limits, the cost of clearing and inserting the bodies into the
* tree every frame becomes more expensive than the search speed gains it provides.
*
* If you have a large number of dynamic bodies in your world then it may be best to
* disable the use of the RTree by setting this property to `false` in the physics config.
*
* The number it can cope with depends on browser and device, but a conservative estimate
* of around 5,000 bodies should be considered the max before disabling it.
*
* This only applies to dynamic bodies. Static bodies are always kept in an RTree,
* because they don't have to be cleared every frame, so you benefit from the
* massive search speeds all the time.
*
* @name Phaser.Physics.Arcade.World#useTree
* @type {boolean}
* @default true
* @since 3.10.0
*/
this.useTree = (0, GetValue_1.default)(config, 'useTree', true);
/**
* The spatial index of Dynamic Bodies.
*
* @name Phaser.Physics.Arcade.World#tree
* @type {Phaser.Structs.RTree}
* @since 3.0.0
*/
this.tree = new RTree_1.default(this.maxEntries);
/**
* The spatial index of Static Bodies.
*
* @name Phaser.Physics.Arcade.World#staticTree
* @type {Phaser.Structs.RTree}
* @since 3.0.0
*/
this.staticTree = new RTree_1.default(this.maxEntries);
this.treeMinMax = { minX: 0, minY: 0, maxX: 0, maxY: 0 };
if (this.drawDebug) {
this.createDebugGraphic();
}
}
// /**
// * Adds an Arcade Physics Body to a Game Object, an array of Game Objects, or the children of a Group.
// *
// * The difference between this and the `enableBody` method is that you can pass arrays or Groups
// * to this method.
// *
// * You can specify if the bodies are to be Dynamic or Static. A dynamic body can move via velocity and
// * acceleration. A static body remains fixed in place and as such is able to use an optimized search
// * tree, making it ideal for static elements such as level objects. You can still collide and overlap
// * with static bodies.
// *
// * Normally, rather than calling this method directly, you'd use the helper methods available in the
// * Arcade Physics Factory, such as:
// *
// * ```javascript
// * this.physics.add.image(x, y, textureKey);
// * this.physics.add.sprite(x, y, textureKey);
// * ```
// *
// * Calling factory methods encapsulates the creation of a Game Object and the creation of its
// * body at the same time. If you are creating custom classes then you can pass them to this
// * method to have their bodies created.
// *
// * @method Phaser.Physics.Arcade.World#enable
// * @since 3.0.0
// *
// * @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[]|Phaser.GameObjects.Group|Phaser.GameObjects.Group[])} object - The object, or objects, on which to create the bodies.
// * @param {number} [bodyType] - The type of Body to create. Either `DYNAMIC_BODY` or `STATIC_BODY`.
// */
// enable(object, bodyType) {
// if (bodyType === undefined) {
// bodyType = CONST.DYNAMIC_BODY
// }
// if (!Array.isArray(object)) {
// object = [object]
// }
// for (let i = 0; i < object.length; i++) {
// let entry = object[i]
// if (entry.isParent) {
// let children = entry.getChildren()
// for (let c = 0; c < children.length; c++) {
// let child = children[c]
// if (child.isParent) {
// // Handle Groups nested inside of Groups
// this.enable(child, bodyType)
// } else {
// this.enableBody(child, bodyType)
// }
// }
// } else {
// this.enableBody(entry, bodyType)
// }
// }
// }
// /**
// * Creates an Arcade Physics Body on a single Game Object.
// *
// * If the Game Object already has a body, this method will simply add it back into the simulation.
// *
// * You can specify if the body is Dynamic or Static. A dynamic body can move via velocity and
// * acceleration. A static body remains fixed in place and as such is able to use an optimized search
// * tree, making it ideal for static elements such as level objects. You can still collide and overlap
// * with static bodies.
// *
// * Normally, rather than calling this method directly, you'd use the helper methods available in the
// * Arcade Physics Factory, such as:
// *
// * ```javascript
// * this.physics.add.image(x, y, textureKey);
// * this.physics.add.sprite(x, y, textureKey);
// * ```
// *
// * Calling factory methods encapsulates the creation of a Game Object and the creation of its
// * body at the same time. If you are creating custom classes then you can pass them to this
// * method to have their bodies created.
// *
// * @method Phaser.Physics.Arcade.World#enableBody
// * @since 3.0.0
// *
// * @param {Phaser.GameObjects.GameObject} object - The Game Object on which to create the body.
// * @param {number} [bodyType] - The type of Body to create. Either `DYNAMIC_BODY` or `STATIC_BODY`.
// *
// * @return {Phaser.GameObjects.GameObject} The Game Object on which the body was created.
// */
// enableBody(object, bodyType) {
// if (bodyType === undefined) {
// bodyType = CONST.DYNAMIC_BODY
// }
// if (!object.body) {
// if (bodyType === CONST.DYNAMIC_BODY) {
// object.body = new Body(this, object)
// } else if (bodyType === CONST.STATIC_BODY) {
// object.body = new StaticBody(this, object)
// }
// }
// this.add(object.body)
// return object
// }
/**
* Adds an existing Arcade Physics Body or StaticBody to the simulation.
*
* The body is enabled and added to the local search trees.
*
* @method Phaser.Physics.Arcade.World#add
* @since 3.10.0
*
* @param {(Phaser.Physics.Arcade.Body|Phaser.Physics.Arcade.StaticBody)} body - The Body to be added to the simulation.
*
* @return {(Phaser.Physics.Arcade.Body|Phaser.Physics.Arcade.StaticBody)} The Body that was added to the simulation.
*/
add(body) {
if (body.physicsType === const_1.default.PHYSICS_TYPE.DYNAMIC_BODY) {
this.bodies.add(body);
this.tree.insert(body);
}
else if (body.physicsType === const_1.default.PHYSICS_TYPE.STATIC_BODY) {
this.staticBodies.add(body);
this.staticTree.insert(body);
}
body.enable = true;
return body;
}
/**
* Disables the Arcade Physics Body of a Game Object, an array of Game Objects, or the children of a Group.
*
* The difference between this and the `disableBody` method is that you can pass arrays or Groups
* to this method.
*
* The body itself is not deleted, it just has its `enable` property set to false, which
* means you can re-enable it again at any point by passing it to enable `World.enable` or `World.add`.
*
* @method Phaser.Physics.Arcade.World#disable
* @since 3.0.0
*
* @param {(Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[]|Phaser.GameObjects.Group|Phaser.GameObjects.Group[])} object - The object, or objects, on which to disable the bodies.
*/
disable(object) {
if (!Array.isArray(object)) {
object = [object];
}
for (let i = 0; i < object.length; i++) {
const entry = object[i];
if (entry.isParent) {
const children = entry.getChildren();
for (let c = 0; c < children.length; c++) {
const child = children[c];
if (child.isParent) {
// Handle Groups nested inside of Groups
this.disable(child);
}
else {
this.disableBody(child.body);
}
}
}
else {
this.disableBody(entry.body);
}
}
}
/**
* Disables an existing Arcade Physics Body or StaticBody and removes it from the simulation.
*
* The body is disabled and removed from the local search trees.
*
* The body itself is not deleted, it just has its `enable` property set to false, which
* means you can re-enable it again at any point by passing it to enable `World.enable` or `World.add`.
*
* @method Phaser.Physics.Arcade.World#disableBody
* @since 3.0.0
*
* @param {(Phaser.Physics.Arcade.Body|Phaser.Physics.Arcade.StaticBody)} body - The Body to be disabled.
*/
disableBody(body) {
this.remove(body);
body.enable = false;
}
/**
* Removes an existing Arcade Physics Body or StaticBody from the simulation.
*
* The body is disabled and removed from the local search trees.
*
* The body itself is not deleted, it just has its `enabled` property set to false, which
* means you can re-enable it again at any point by passing it to enable `enable` or `add`.
*
* @method Phaser.Physics.Arcade.World#remove
* @since 3.0.0
*
* @param {(Phaser.Physics.Arcade.Body|Phaser.Physics.Arcade.StaticBody)} body - The body to be removed from the simulation.
*/
remove(body) {
if (body.physicsType === const_1.default.PHYSICS_TYPE.DYNAMIC_BODY) {
this.bodies.delete(body);
this.tree.remove(body);
}
else if (body.physicsType === const_1.default.PHYSICS_TYPE.STATIC_BODY) {
this.staticBodies.delete(body);
this.staticTree.remove(body);
}
}
/**
* Creates a Graphics Game Object that the world will use to render the debug display to.
*
* This is called automatically when the World is instantiated if the `debug` config property
* was set to `true`. However, you can call it at any point should you need to display the
* debug Graphic from a fixed point.
*
* You can control which objects are drawn to the Graphics object, and the colors they use,
* by setting the debug properties in the physics config.
*
* You should not typically use this in a production game. Use it to aid during debugging.
*
* @method Phaser.Physics.Arcade.World#createDebugGraphic
* @since 3.0.0
*
* @return {Phaser.GameObjects.Graphics} The Graphics object that was created for use by the World.
*/
createDebugGraphic() {
// let graphic = this.scene.sys.add.graphics({ x: 0, y: 0 })
// graphic.setDepth(Number.MAX_VALUE)
// this.debugGraphic = graphic
// this.drawDebug = true
// return graphic
}
/**
* Sets the position, size and properties of the World boundary.
*
* The World boundary is an invisible rectangle that defines the edges of the World.
* If a Body is set to collide with the world bounds then it will automatically stop
* when it reaches any of the edges. You can optionally set which edges of the boundary
* should be checked against.
*
* @method Phaser.Physics.Arcade.World#setBounds
* @since 3.0.0
*
* @param {number} x - The top-left x coordinate of the boundary.
* @param {number} y - The top-left y coordinate of the boundary.
* @param {number} width - The width of the boundary.
* @param {number} height - The height of the boundary.
* @param {boolean} [checkLeft] - Should bodies check against the left edge of the boundary?
* @param {boolean} [checkRight] - Should bodies check against the right edge of the boundary?
* @param {boolean} [checkUp] - Should bodies check against the top edge of the boundary?
* @param {boolean} [checkDown] - Should bodies check against the bottom edge of the boundary?
*
* @return {Phaser.Physics.Arcade.World} This World object.
*/
setBounds(x, y, width, height, checkLeft, checkRight, checkUp, checkDown) {
this.bounds.setTo(x, y, width, height);
if (checkLeft !== undefined) {
this.setBoundsCollision(checkLeft, checkRight, checkUp, checkDown);
}
return this;
}
/**
* Enables or disables collisions on each edge of the World boundary.
*
* @method Phaser.Physics.Arcade.World#setBoundsCollision
* @since 3.0.0
*
* @param {boolean} [left=true] - Should bodies check against the left edge of the boundary?
* @param {boolean} [right=true] - Should bodies check against the right edge of the boundary?
* @param {boolean} [up=true] - Should bodies check against the top edge of the boundary?
* @param {boolean} [down=true] - Should bodies check against the bottom edge of the boundary?
*
* @return {Phaser.Physics.Arcade.World} This World object.
*/
setBoundsCollision(left, right, up, down) {
if (left === undefined) {
left = true;
}
if (right === undefined) {
right = true;
}
if (up === undefined) {
up = true;
}
if (down === undefined) {
down = true;
}
this.checkCollision.left = left;
this.checkCollision.right = right;
this.checkCollision.up = up;
this.checkCollision.down = down;
return this;
}
/**
* Pauses the simulation.
*
* A paused simulation does not update any existing bodies, or run any Colliders.
*
* However, you can still enable and disable bodies within it, or manually run collide or overlap
* checks.
*
* @method Phaser.Physics.Arcade.World#pause
* @fires Phaser.Physics.Arcade.Events#PAUSE
* @since 3.0.0
*
* @return {Phaser.Physics.Arcade.World} This World object.
*/
pause() {
this.isPaused = true;
this.emit(events_1.default.PAUSE);
return this;
}
/**
* Resumes the simulation, if paused.
*
* @method Phaser.Physics.Arcade.World#resume
* @fires Phaser.Physics.Arcade.Events#RESUME
* @since 3.0.0
*
* @return {Phaser.Physics.Arcade.World} This World object.
*/
resume() {
this.isPaused = false;
this.emit(events_1.default.RESUME);
return this;
}
/**
* Creates a new Collider object and adds it to the simulation.
*
* A Collider is a way to automatically perform collision checks between two objects,
* calling the collide and process callbacks if they occur.
*
* Colliders are run as part of the World update, after all of the Bodies have updated.
*
* By creating a Collider you don't need then call `World.collide` in your `update` loop,
* as it will be handled for you automatically.
*
* @method Phaser.Physics.Arcade.World#addCollider
* @since 3.0.0
* @see Phaser.Physics.Arcade.World#collide
*
* @param {Phaser.Types.Physics.Arcade.ArcadeColliderType} body1 - The first object to check for collision.
* @param {Phaser.Types.Physics.Arcade.ArcadeColliderType} body2 - The second object to check for collision.
* @param {ArcadePhysicsCallback} [collideCallback] - The callback to invoke when the two objects collide.
* @param {ArcadePhysicsCallback} [processCallback] - The callback to invoke when the two objects collide. Must return a boolean.
* @param {*} [callbackContext] - The scope in which to call the callbacks.
*
* @return {Phaser.Physics.Arcade.Collider} The Collider that was created.
*/
addCollider(body1, body2, collideCallback, processCallback, callbackContext) {
if (collideCallback === undefined) {
collideCallback = null;
}
if (processCallback === undefined) {
processCallback = null;
}
if (callbackContext === undefined) {
callbackContext = collideCallback;
}
const collider = new Collider_1.Collider(this, false, body1, body2, collideCallback, processCallback, callbackContext);
this.colliders.add(collider);
return collider;
}
/**
* Creates a new Overlap Collider object and adds it to the simulation.
*
* A Collider is a way to automatically perform overlap checks between two objects,
* calling the collide and process callbacks if they occur.
*
* Colliders are run as part of the World update, after all of the Bodies have updated.
*
* By creating a Collider you don't need then call `World.overlap` in your `update` loop,
* as it will be handled for you automatically.
*
* @method Phaser.Physics.Arcade.World#addOverlap
* @since 3.0.0
*
* @param {Phaser.Types.Physics.Arcade.ArcadeColliderType} body1 - The first object to check for overlap.
* @param {Phaser.Types.Physics.Arcade.ArcadeColliderType} body2 - The second object to check for overlap.
* @param {ArcadePhysicsCallback} [collideCallback] - The callback to invoke when the two objects overlap.
* @param {ArcadePhysicsCallback} [processCallback] - The callback to invoke when the two objects overlap. Must return a boolean.
* @param {*} [callbackContext] - The scope in which to call the callbacks.
*
* @return {Phaser.Physics.Arcade.Collider} The Collider that was created.
*/
addOverlap(body1, body2, collideCallback, processCallback, callbackContext) {
if (collideCallback === undefined) {
collideCallback = null;
}
if (processCallback === undefined) {
processCallback = null;
}
if (callbackContext === undefined) {
callbackContext = collideCallback;
}
const collider = new Collider_1.Collider(this, true, body1, body2, collideCallback, processCallback, callbackContext);
this.colliders.add(collider);
return collider;
}
/**
* Removes a Collider from the simulation so it is no longer processed.
*
* This method does not destroy the Collider. If you wish to add it back at a later stage you can call
* `World.colliders.add(Collider)`.
*
* If you no longer need the Collider you can call the `Collider.destroy` method instead, which will
* automatically clear all of its references and then remove it from the World. If you call destroy on
* a Collider you _don't_ need to pass it to this method too.
*
* @method Phaser.Physics.Arcade.World#removeCollider
* @since 3.0.0
*
* @param {Phaser.Physics.Arcade.Collider} collider - The Collider to remove from the simulation.
*
* @return {Phaser.Physics.Arcade.World} This World object.
*/
removeCollider(collider) {
this.colliders.remove(collider);
return this;
}
/**
* Sets the frame rate to run the simulation at.
*
* The frame rate value is used to simulate a fixed update time step. This fixed
* time step allows for a straightforward implementation of a deterministic game state.
*
* This frame rate is independent of the frequency at which the game is rendering. The
* higher you set the fps, the more physics simulation steps will occur per game step.
* Conversely, the lower you set it, the less will take place.
*
* You can optionally advance the simulation directly yourself by calling the `step` method.
*
* @method Phaser.Physics.Arcade.World#setFPS
* @since 3.10.0
*
* @param {number} framerate - The frame rate to advance the simulation at.
*
* @return {this} This World object.
*/
setFPS(framerate) {
this.fps = framerate;
this._frameTime = 1 / this.fps;
this._frameTimeMS = 1000 * this._frameTime;
return this;
}
/**
* Advances the simulation based on the elapsed time and fps rate.
*
* This is called automatically by your Scene and does not need to be invoked directly.
*
* @method Phaser.Physics.Arcade.World#update
* @fires Phaser.Physics.Arcade.Events#WORLD_STEP
* @since 3.0.0
*
* @param {number} time - The current timestamp as generated by the Request Animation Frame or SetTimeout.
* @param {number} delta - The delta time, in ms, elapsed since the last frame.
*/
update(time, delta) {
if (this.isPaused || this.bodies.size === 0) {
return;
}
let i;
let fixedDelta = this._frameTime;
const msPerFrame = this._frameTimeMS * this.timeScale;
this._elapsed += delta;
// Update all active bodies
let body;
const bodies = this.bodies;
// Will a step happen this frame?
let willStep = this._elapsed >= msPerFrame;
if (!this.fixedStep) {
fixedDelta = delta * 0.001;
willStep = true;
this._elapsed = 0;
}
for (const body of bodies) {
if (body.enable) {
body.preUpdate(willStep, fixedDelta);
}
}
// We know that a step will happen this frame, so let's bundle it all together to save branching and iteration costs
if (willStep) {
this._elapsed -= msPerFrame;
this.stepsLastFrame = 1;
// Optionally populate our dynamic collision tree
if (this.useTree) {
this.tree.clear();
this.tree.load(Array.from(bodies));
}
// Process any colliders
const colliders = this.colliders.update();
for (i = 0; i < colliders.length; i++) {
const collider = colliders[i];
if (collider.active) {
collider.update();
}
}
this.emit(events_1.default.WORLD_STEP, fixedDelta);
}
// Process any additional steps this frame
while (this._elapsed >= msPerFrame) {
this._elapsed -= msPerFrame;
this.step(fixedDelta);
}
}
/**
* Advances the simulation by a time increment.
*
* @method Phaser.Physics.Arcade.World#step
* @fires Phaser.Physics.Arcade.Events#WORLD_STEP
* @since 3.10.0
*
* @param {number} delta - The delta time amount, in seconds, by which to advance the simulation.
*/
step(delta) {
// Update all active bodies
let i;
let body;
const bodies = this.bodies;
const len = bodies.size;
for (const body of bodies) {
if (body.enable) {
body.update(delta);
}
}
// Optionally populate our dynamic collision tree
if (this.useTree) {
this.tree.clear();
this.tree.load(Array.from(bodies));
}
// Process any colliders
const colliders = this.colliders.update();
for (i = 0; i < colliders.length; i++) {
const collider = colliders[i];
if (collider.active) {
collider.update();
}
}
this.emit(events_1.default.WORLD_STEP, delta);
this.stepsLastFrame++;
}
/**
* Updates bodies, draws the debug display, and handles pending queue operations.
*
* @method Phaser.Physics.Arcade.World#postUpdate
* @since 3.0.0
*/
postUpdate() {
let bodies = this.bodies;
let len;
const dynamic = this.bodies;
const staticBodies = this.staticBodies;
// We don't need to postUpdate if there wasn't a step this frame
if (this.stepsLastFrame) {
this.stepsLastFrame = 0;
for (const body of bodies) {
if (body.enable) {
body.postUpdate();
}
}
}
if (this.drawDebug) {
const graphics = this.debugGraphic;
graphics.clear();
for (const body of bodies) {
if (body.willDrawDebug()) {
body.drawDebug(graphics);
}
}
bodies = staticBodies;
len = bodies.size;
for (const body of bodies) {
if (body.willDrawDebug()) {
body.drawDebug(graphics);
}
}
}
const pending = this.pendingDestroy;
if (pending.size > 0) {
const dynamicTree = this.tree;
const staticTree = this.staticTree;
bodies = pending;
len = bodies.size;
for (const body of bodies) {
if (body.physicsType === const_1.default.PHYSICS_TYPE.DYNAMIC_BODY) {
dynamicTree.remove(body);
dynamic.delete(body);
}
else if (body.physicsType === const_1.default.PHYSICS_TYPE.STATIC_BODY) {
staticTree.remove(body);
staticBodies.delete(body);
}
// @ts-ignore
body.world = undefined;
// @ts-ignore
body.gameObject = undefined;
}
pending.clear();
}
}
/**
* Calculates a Body's velocity and updates its position.
*
* @method Phaser.Physics.Arcade.World#updateMotion
* @since 3.0.0
*
* @param {Phaser.Physics.Arcade.Body} body - The Body to be updated.
* @param {number} delta - The delta value to be used in the motion calculations, in seconds.
*/
updateMotion(body, delta) {
if (body.allowRotation) {
this.computeAngularVelocity(body, delta);
}
this.computeVelocity(body, delta);
}
/**
* Calculates a Body's angular velocity.
*
* @method Phaser.Physics.Arcade.World#computeAngularVelocity
* @since 3.10.0
*
* @param {Phaser.Physics.Arcade.Body} body - The Body to compute the velocity for.
* @param {number} delta - The delta value to be used in the calculation, in seconds.
*/
computeAngularVelocity(body, delta) {
let velocity = body.angularVelocity;
const acceleration = body.angularAcceleration;
let drag = body.angularDrag;
const max = body.maxAngular;
if (acceleration) {
velocity += acceleration * delta;
}
else if (body.allowDrag && drag) {
drag *= delta;
if ((0, GreaterThan_1.default)(velocity - drag, 0, 0.1)) {
velocity -= drag;
}
else if ((0, LessThan_1.default)(velocity + drag, 0, 0.1)) {
velocity += drag;
}
else {
velocity = 0;
}
}
velocity = (0, Clamp_1.default)(velocity, -max, max);
const velocityDelta = velocity - body.angularVelocity;
body.angularVelocity += velocityDelta;
body.rotation += body.angularVelocity * delta;
}
/**
* Calculates a Body's per-axis velocity.
*
* @method Phaser.Physics.Arcade.World#computeVelocity
* @since 3.0.0
*
* @param {Phaser.Physics.Arcade.Body} body - The Body to compute the velocity for.
* @param {number} delta - The delta value to be used in the calculation, in seconds.
*/
computeVelocity(body, delta) {
let velocityX = body.velocity.x;
const accelerationX = body.acceleration.x;
let dragX = body.drag.x;
const maxX = body.maxVelocity.x;
let velocityY = body.velocity.y;
const accelerationY = body.acceleration.y;
let dragY = body.drag.y;
const maxY = body.maxVelocity.y;
let speed = body.speed;
const maxSpeed = body.maxSpeed;
const allowDrag = body.allowDrag;
const useDamping = body.useDamping;
if (body.allowGravity) {
velocityX += (this.gravity.x + body.gravity.x) * delta;
velocityY += (this.gravity.y + body.gravity.y) * delta;
}
if (accelerationX) {
velocityX += accelerationX * delta;
}
else if (allowDrag && dragX) {
if (useDamping) {
// Damping based deceleration
dragX = Math.pow(dragX, delta);
velocityX *= dragX;
speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if ((0, Equal_1.default)(speed, 0, 0.001)) {
velocityX = 0;
}
}
else {
// Linear deceleration
dragX *= delta;
if ((0, GreaterThan_1.default)(velocityX - dragX, 0, 0.01)) {
velocityX -= dragX;
}
else if ((0, LessThan_1.default)(velocityX + dragX, 0, 0.01)) {
velocityX += dragX;
}
else {
velocityX = 0;
}
}
}
if (accelerationY) {
velocityY += accelerationY * delta;
}
else if (allowDrag && dragY) {
if (useDamping) {
// Damping based deceleration
dragY = Math.pow(dragY, delta);
velocityY *= dragY;
speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
if ((0, Equal_1.default)(speed, 0, 0.001)) {
velocityY = 0;
}
}
else {
// Linear deceleration
dragY *= delta;
if ((0, GreaterThan_1.default)(velocityY - dragY, 0, 0.01)) {
velocityY -= dragY;
}
else if ((0, LessThan_1.default)(velocityY + dragY, 0, 0.01)) {
velocityY += dragY;
}
else {
velocityY = 0;
}
}
}
velocityX = (0, Clamp_1.default)(velocityX, -maxX, maxX);
velocityY = (0, Clamp_1.default)(velocityY, -maxY, maxY);
body.velocity.set(velocityX, velocityY);
if (maxSpeed > -1 && speed > maxSpeed) {
body.velocity.normalize().scale(maxSpeed);
speed = maxSpeed;
}
body.speed = speed;
}
/**
* Separates two Bodies.
*
* @method Phaser.Physics.Arcade.World#separate
* @fires Phaser.Physics.Arcade.Events#COLLIDE
* @fires Phaser.Physics.Arcade.Events#OVERLAP
* @since 3.0.0
*
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body to be separated.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body to be separated.
* @param {ArcadePhysicsCallback} [processCallback] - The process callback.
* @param {*} [callbackContext] - The context in which to invoke the callback.
* @param {boolean} [overlapOnly] - If this a collide or overlap check?
* @param {boolean} [intersects] - Assert that the bodies intersect and should not be tested before separation.
*
* @return {boolean} True if separation occurred, otherwise false.
*/
separate(body1, body2, processCallback, callbackContext, overlapOnly, intersects) {
if ((!intersects && !body1.enable) ||
!body2.enable ||
body1.checkCollision.none ||
body2.checkCollision.none ||
!this.intersects(body1, body2)) {
return false;
}
// They overlap. Is there a custom process callback? If it returns true then we can carry on, otherwise we should abort.
if (processCallback && processCallback.call(callbackContext, body1, body2) === false) {
return false;
}
// Circle vs. Circle quick bail out
if (body1.isCircle && body2.isCircle) {
return this.separateCircle(body1, body2, overlapOnly);
}
// We define the behavior of bodies in a collision circle and rectangle
// If a collision occurs in the corner points of the rectangle, the body behave like circles
// Either body1 or body2 is a circle
if (body1.isCircle !== body2.isCircle) {
const bodyRect = body1.isCircle ? body2 : body1;
const bodyCircle = body1.isCircle ? body1 : body2;
const rect = {
x: bodyRect.x,
y: bodyRect.y,
right: bodyRect.right,
bottom: bodyRect.bottom
};
const circle = bodyCircle.center;
if (circle.y < rect.y || circle.y > rect.bottom) {
if (circle.x < rect.x || circle.x > rect.right) {
return this.separateCircle(body1, body2, overlapOnly);
}
}
}
let resultX = false;
let resultY = false;
// Do we separate on x first or y first or both?
if (overlapOnly) {
// No separation but we need to calculate overlapX, overlapY, etc.
resultX = (0, SeparateX_1.SeparateX)(body1, body2, overlapOnly, this.OVERLAP_BIAS);
resultY = (0, SeparateY_1.SeparateY)(body1, body2, overlapOnly, this.OVERLAP_BIAS);
}
else if (this.forceX || Math.abs(this.gravity.y + body1.gravity.y) < Math.abs(this.gravity.x + body1.gravity.x)) {
resultX = (0, SeparateX_1.SeparateX)(body1, body2, overlapOnly, this.OVERLAP_BIAS);
// Are they still intersecting? Let's do the other axis then
if (this.intersects(body1, body2)) {
resultY = (0, SeparateY_1.SeparateY)(body1, body2, overlapOnly, this.OVERLAP_BIAS);
}
}
else {
resultY = (0, SeparateY_1.SeparateY)(body1, body2, overlapOnly, this.OVERLAP_BIAS);
// Are they still intersecting? Let's do the other axis then
if (this.intersects(body1, body2)) {
resultX = (0, SeparateX_1.SeparateX)(body1, body2, overlapOnly, this.OVERLAP_BIAS);
}
}
const result = resultX || resultY;
if (result) {
if (overlapOnly) {
if (body1.onOverlap || body2.onOverlap) {
this.emit(events_1.default.OVERLAP, body1, body2);
}
}
else if (body1.onCollide || body2.onCollide) {
this.emit(events_1.default.COLLIDE, body1, body2);
}
}
return result;
}
/**
* Separates two Bodies, when both are circular.
*
* @method Phaser.Physics.Arcade.World#separateCircle
* @fires Phaser.Physics.Arcade.Events#COLLIDE
* @fires Phaser.Physics.Arcade.Events#OVERLAP
* @since 3.0.0
*
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body to be separated.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body to be separated.
* @param {boolean} [overlapOnly] - If this a collide or overlap check?
* @param {number} [bias] - A small value added to the calculations.
*
* @return {boolean} True if separation occurred, otherwise false.
*/
separateCircle(body1, body2, overlapOnly, bias) {
// Set the bounding box overlap values into the bodies themselves (hence we don't use the return values here)
(0, GetOverlapX_1.GetOverlapX)(body1, body2, false, bias);
(0, GetOverlapY_1.GetOverlapY)(body1, body2, false, bias);
let overlap = 0;
if (body1.isCircle !== body2.isCircle) {
const rect = {
x: body2.isCircle ? body1.position.x : body2.position.x,
y: body2.isCircle ? body1.position.y : body2.position.y,
right: body2.isCircle ? body1.right : body2.right,
bottom: body2.isCircle ? body1.bottom : body2.bottom
};
const circle = {
x: body1.isCircle ? body1.center.x : body2.center.x,
y: body1.isCircle ? body1.center.y : body2.center.y,
radius: body1.isCircle ? body1.halfWidth : body2.halfWidth
};
if (circle.y < rect.y) {
if (circle.x < rect.x) {
overlap = (0, DistanceBetween_1.default)(circle.x, circle.y, rect.x, rect.y) - circle.radius;
}
else if (circle.x > rect.right) {
overlap = (0, DistanceBetween_1.default)(circle.x, circle.y, rect.right, rect.y) - circle.radius;
}
}
else if (circle.y > rect.bottom) {
if (circle.x < rect.x) {
overlap = (0, DistanceBetween_1.default)(circle.x, circle.y, rect.x, rect.bottom) - circle.radius;
}
else if (circle.x > rect.right) {
overlap = (0, DistanceBetween_1.default)(circle.x, circle.y, rect.right, rect.bottom) - circle.radius;
}
}
overlap *= -1;
}
else {
overlap =
body1.halfWidth +
body2.halfWidth -
(0, DistanceBetween_1.default)(body1.center.x, body1.center.y, body2.center.x, body2.center.y);
}
body1.overlapR = overlap;
body2.overlapR = overlap;
// Can't separate two immovable bodies, or a body with its own custom separation logic
if (overlapOnly ||
overlap === 0 ||
(body1.immovable && body2.immovable) ||
body1.customSeparateX ||
body2.customSeparateX) {
if (overlap !== 0 && (body1.onOverlap || body2.onOverlap)) {
this.emit(events_1.default.OVERLAP, body1, body2);
}
// return true if there was some overlap, otherwise false
return overlap !== 0;
}
const dx = body1.center.x - body2.center.x;
const dy = body1.center.y - body2.center.y;
const d = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
const nx = (body2.center.x - body1.center.x) / d || 0;
const ny = (body2.center.y - body1.center.y) / d || 0;
let p = (2 * (body1.velocity.x * nx + body1.velocity.y * ny - body2.velocity.x * nx - body2.velocity.y * ny)) /
(body1.mass + body2.mass);
if (body1.immovable || body2.immovable) {
p *= 2;
}
if (!body1.immovable) {
body1.velocity.x = body1.velocity.x - (p / body1.mass) * nx;
body1.velocity.y = body1.velocity.y - (p / body1.mass) * ny;
}
if (!body2.immovable) {
body2.velocity.x = body2.velocity.x + (p / body2.mass) * nx;
body2.velocity.y = body2.velocity.y + (p / body2.mass) * ny;
}
if (!body1.immovable && !body2.immovable) {
overlap /= 2;
}
// Note: This is inadequate for circle-rectangle separation
const angle = (0, BetweenPoints_1.default)(body1.center, body2.center);
const overlapX = (overlap + const_2.default.EPSILON) * Math.cos(angle);
const overlapY = (overlap + const_2.default.EPSILON) * Math.sin(angle);
if (!body1.immovable) {
body1.x -= overlapX;
body1.y -= overlapY;
body1.updateCenter();
}
if (!body2.immovable) {
body2.x += overlapX;
body2.y += overlapY;
body2.updateCenter();
}
body1.velocity.x *= body1.bounce.x;
body1.velocity.y *= body1.bounce.y;
body2.velocity.x *= body2.bounce.x;
body2.velocity.y *= body2.bounce.y;
if (body1.onCollide || body2.onCollide) {
this.emit(events_1.default.COLLIDE, body1, body2);
}
return true;
}
/**
* Checks to see if two Bodies intersect at all.
*
* @method Phaser.Physics.Arcade.World#intersects
* @since 3.0.0
*
* @param {Phaser.Physics.Arcade.Body} body1 - The first body to check.
* @param {Phaser.Physics.Arcade.Body} body2 - The second body to check.
*
* @return {boolean} True if the two bodies intersect, otherwise false.
*/
intersects(body1, body2) {
if (body1 === body2)
return false;
if (!body1.isCircle && !body2.isCircle) {
// Rect vs. Rect
return !(body1.right <= body2.position.x ||
body1.bottom <= body2.position.y ||
body1.position.x >= body2.right ||
body1.position.y >= body2.bottom);
}
else if (body1.isCircle) {
if (body2.isCircle) {
// Circle vs. Circle
return ((0, DistanceBetween_1.default)(body1.center.x, body1.center.y, body2.center.x, body2.center.y) <=
body1.halfWidth + body2.halfWidth);
}
else {
// Circle vs. Rect
return this.circleBodyIntersects(body1, body2);
}
}
else {
// Rect vs. Circle
return this.circleBodyIntersects(body2, body1);
}
}
/**
* Tests if a circular Body intersects with another Body.
*
* @method Phaser.Physics.Arcade.World#circleBodyIntersects
* @since 3.0.0
*
* @param {Phaser.Physics.Arcade.Body} circle - The circular body to test.
* @param {Phaser.Physics.Arcade.Body} body - The rectangular body to test.
*
* @return {boolean} True if the two bodies intersect, otherwise false.
*/
circleBodyIntersects(circle, body) {
const x = (0, Clamp_1.default)(circle.center.x, body.left, body.right);
const y = (0, Clamp_1.default)(circle.center.y, body.top, body.bottom);
const dx = (circle.center.x - x) * (circle.center.x - x);
const dy = (circle.center.y - y) * (circle.center.y - y);
return dx + dy <= circle.h