* The Game display area will not be scaled - even if it is too large for the canvas/screen.
* This mode _ignores_ any applied scaling factor and displays the canvas at the Game size.
*
*
{@link Phaser.ScaleManager.EXACT_FIT}
*
* The Game display area will be _stretched_ to fill the entire size of the canvas's parent element and/or screen.
* Proportions are not maintained.
*
*
{@link Phaser.ScaleManager.SHOW_ALL}
*
* Show the entire game display area while _maintaining_ the original aspect ratio.
*
*
{@link Phaser.ScaleManager.RESIZE}
*
* The dimensions of the game display area are changed to match the size of the parent container.
* That is, this mode _changes the Game size_ to match the display size.
*
* Any manually set Game size (see {@link #setGameSize}) is ignored while in effect.
*
*
{@link Phaser.ScaleManager.USER_SCALE}
*
* The game Display is scaled according to the user-specified scale set by {@link Phaser.ScaleManager#setUserScale setUserScale}.
*
* This scale can be adjusted in the {@link Phaser.ScaleManager#setResizeCallback resize callback}
* for flexible custom-sizing needs.
*
*
*
* @name Phaser.ScaleManager#scaleMode
* @property {integer} scaleMode
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'scaleMode', {
get: function ()
{
return this._scaleMode;
},
set: function (value)
{
if (value !== this._scaleMode)
{
if (!this.isFullScreen)
{
this.updateDimensions(this._gameSize.width, this._gameSize.height, true);
this.queueUpdate(true);
}
this._scaleMode = value;
}
return this._scaleMode;
}
});
/**
* The scaling method used by the ScaleManager when in fullscreen.
*
* See {@link Phaser.ScaleManager#scaleMode scaleMode} for the different modes allowed.
*
* @name Phaser.ScaleManager#fullScreenScaleMode
* @property {integer} fullScreenScaleMode
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'fullScreenScaleMode', {
get: function ()
{
return this._fullScreenScaleMode;
},
set: function (value)
{
if (value !== this._fullScreenScaleMode)
{
// If in fullscreen then need a wee bit more work
if (this.isFullScreen)
{
this.prepScreenMode(false);
this._fullScreenScaleMode = value;
this.prepScreenMode(true);
this.queueUpdate(true);
}
else
{
this._fullScreenScaleMode = value;
}
}
return this._fullScreenScaleMode;
}
});
/**
* Returns the current scale mode - for normal or fullscreen operation.
*
* See {@link Phaser.ScaleManager#scaleMode scaleMode} for the different modes allowed.
*
* @name Phaser.ScaleManager#currentScaleMode
* @property {number} currentScaleMode
* @protected
* @readonly
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'currentScaleMode', {
get: function ()
{
return this.isFullScreen ? this._fullScreenScaleMode : this._scaleMode;
}
});
/**
* When enabled the Display canvas will be horizontally-aligned _in the Parent container_ (or {@link Phaser.ScaleManager#parentIsWindow window}).
*
* To align horizontally across the page the Display canvas should be added directly to page;
* or the parent container should itself be horizontally aligned.
*
* Horizontal alignment is not applicable with the {@link .RESIZE} scaling mode.
*
* @name Phaser.ScaleManager#pageAlignHorizontally
* @property {boolean} pageAlignHorizontally
* @default false
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'pageAlignHorizontally', {
get: function ()
{
return this._pageAlignHorizontally;
},
set: function (value)
{
if (value !== this._pageAlignHorizontally)
{
this._pageAlignHorizontally = value;
this.queueUpdate(true);
}
}
});
/**
* When enabled the Display canvas will be vertically-aligned _in the Parent container_ (or {@link Phaser.ScaleManager#parentIsWindow window}).
*
* To align vertically the Parent element should have a _non-collapsible_ height, such that it will maintain
* a height _larger_ than the height of the contained Game canvas - the game canvas will then be scaled vertically
* _within_ the remaining available height dictated by the Parent element.
*
* One way to prevent the parent from collapsing is to add an absolute "min-height" CSS property to the parent element.
* If specifying a relative "min-height/height" or adjusting margins, the Parent height must still be non-collapsible (see note).
*
* _Note_: In version 2.2 the minimum document height is _not_ automatically set to the viewport/window height.
* To automatically update the minimum document height set {@link Phaser.ScaleManager#compatibility compatibility.forceMinimumDocumentHeight} to true.
*
* Vertical alignment is not applicable with the {@link .RESIZE} scaling mode.
*
* @name Phaser.ScaleManager#pageAlignVertically
* @property {boolean} pageAlignVertically
* @default false
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'pageAlignVertically', {
get: function ()
{
return this._pageAlignVertically;
},
set: function (value)
{
if (value !== this._pageAlignVertically)
{
this._pageAlignVertically = value;
this.queueUpdate(true);
}
}
});
/**
* Returns true if the browser is in fullscreen mode, otherwise false.
* @name Phaser.ScaleManager#isFullScreen
* @property {boolean} isFullScreen
* @readonly
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'isFullScreen', {
get: function ()
{
return !!(document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement);
}
});
/**
* Returns true if the screen orientation is in portrait mode.
*
* @name Phaser.ScaleManager#isPortrait
* @property {boolean} isPortrait
* @readonly
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'isPortrait', {
get: function ()
{
return this.classifyOrientation(this.screenOrientation) === 'portrait';
}
});
/**
* Returns true if the screen orientation is in landscape mode.
*
* @name Phaser.ScaleManager#isLandscape
* @property {boolean} isLandscape
* @readonly
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'isLandscape', {
get: function ()
{
return this.classifyOrientation(this.screenOrientation) === 'landscape';
}
});
/**
* Returns true if the game dimensions are portrait (height > width).
* This is especially useful to check when using the RESIZE scale mode
* but wanting to maintain game orientation on desktop browsers,
* where typically the screen orientation will always be landscape regardless of the browser viewport.
*
* @name Phaser.ScaleManager#isGamePortrait
* @property {boolean} isGamePortrait
* @readonly
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'isGamePortrait', {
get: function ()
{
return (this.height > this.width);
}
});
/**
* Returns true if the game dimensions are landscape (width > height).
* This is especially useful to check when using the RESIZE scale mode
* but wanting to maintain game orientation on desktop browsers,
* where typically the screen orientation will always be landscape regardless of the browser viewport.
*
* @name Phaser.ScaleManager#isGameLandscape
* @property {boolean} isGameLandscape
* @readonly
*/
Object.defineProperty(Phaser.ScaleManager.prototype, 'isGameLandscape', {
get: function ()
{
return (this.width > this.height);
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A collection of methods for displaying debug information about game objects.
*
* If your game is running in Canvas mode, then you should invoke all of the Debug methods from
* your game's `render` function. This is because they are drawn directly onto the game canvas
* itself, so if you call any debug methods outside of `render` they are likely to be overwritten
* by the game itself.
*
* If your game is running in WebGL then Debug will create a Sprite that is placed at the top of the Stage display list and bind a canvas texture
* to it, which must be uploaded every frame. Be advised: this is very expensive, especially in browsers like Firefox. So please only enable Debug
* in WebGL mode if you really need it (or your desktop can cope with it well) and disable it for production!
*
* @class Phaser.Utils.Debug
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.Utils.Debug = function (game)
{
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
/**
* @property {Phaser.Image} sprite - If debugging in WebGL mode, this is the Image displaying the debug {@link #bmd BitmapData}.
*/
this.sprite = null;
/**
* @property {Phaser.BitmapData} bmd - In WebGL mode this BitmapData contains a copy of the debug canvas.
*/
this.bmd = null;
/**
* @property {HTMLCanvasElement} canvas - The canvas to which Debug calls draws.
*/
this.canvas = null;
/**
* @property {CanvasRenderingContext2D} context - The 2d context of the canvas.
*/
this.context = null;
/**
* @property {string} font - The font that the debug information is rendered in.
* @default
*/
this.font = '14px monospace';
/**
* @property {number} columnWidth - The spacing between columns.
* @default
*/
this.columnWidth = 100;
/**
* @property {number} lineHeight - The line height between the debug text.
* @default
*/
this.lineHeight = 16;
/**
* @property {number} lineWidth - The width of the stroke on lines and shapes. A positive number.
* @default
*/
this.lineWidth = 1;
/**
* @property {boolean} renderShadow - Should the text be rendered with a slight shadow? Makes it easier to read on different types of background.
* @default
*/
this.renderShadow = true;
/**
* @property {string} currentColor - The color last set by {@link #start} or {@link #text}.
* @default
* @protected
*/
this.currentColor = null;
/**
* @property {number} currentX - The current X position the debug information will be rendered at.
* @default
*/
this.currentX = 0;
/**
* @property {number} currentY - The current Y position the debug information will be rendered at.
* @default
*/
this.currentY = 0;
/**
* @property {number} currentAlpha - The alpha of the Debug context, set before all debug information is rendered to it.
* @default
*/
this.currentAlpha = 1;
/**
* @property {boolean} dirty - Does the canvas need re-rendering?
* @default
*/
this.dirty = false;
/**
* @property {boolean} isDisabled - If `enableDebug: false` is passed to {@link Phaser.Game} or if Phaser is built without the Debug class, this will be true.
* @default
* @readonly
*/
this.isDisabled = false;
/**
* @property {Phaser.Line} _line - A reusable rendering line.
* @private
*/
this._line = null;
/**
* @property {Phaser.Rectangle} _rect - A reusable rendering rectangle.
* @private
*/
this._rect = null;
};
/**
* @constant
* @type {integer}
*/
Phaser.Utils.Debug.GEOM_AUTO = 0;
/**
* @constant
* @type {integer}
*/
Phaser.Utils.Debug.GEOM_RECTANGLE = 1;
/**
* @constant
* @type {integer}
*/
Phaser.Utils.Debug.GEOM_CIRCLE = 2;
/**
* @constant
* @type {integer}
*/
Phaser.Utils.Debug.GEOM_POINT = 3;
/**
* @constant
* @type {integer}
*/
Phaser.Utils.Debug.GEOM_LINE = 4;
/**
* @constant
* @type {integer}
*/
Phaser.Utils.Debug.GEOM_ELLIPSE = 5;
Phaser.Utils.Debug.prototype = {
/**
* Internal method that boots the debug displayer.
*
* @method Phaser.Utils.Debug#boot
* @protected
*/
boot: function ()
{
if (this.game.renderType === Phaser.CANVAS)
{
this.context = this.game.context;
}
else
{
this.bmd = new Phaser.BitmapData(this.game, '__DEBUG', this.game.width, this.game.height, true);
this.sprite = this.game.make.image(0, 0, this.bmd);
this.game.stage.addChild(this.sprite);
this.game.scale.onSizeChange.add(this.resize, this);
this.canvas = Phaser.CanvasPool.create(this, this.game.width, this.game.height);
this.context = this.canvas.getContext('2d');
}
this._line = new Phaser.Line();
this._rect = new Phaser.Rectangle();
},
/**
* Internal method that resizes the BitmapData and Canvas.
* Called by ScaleManager.onSizeChange only in WebGL mode.
*
* @method Phaser.Utils.Debug#resize
* @protected
*/
resize: function ()
{
this.bmd.resize(this.game.width, this.game.height);
this.canvas.width = this.game.width;
this.canvas.height = this.game.height;
},
/**
* Internal method that clears the canvas (if a Sprite) ready for a new debug session.
*
* @method Phaser.Utils.Debug#preUpdate
* @protected
*/
preUpdate: function ()
{
if (this.dirty && this.sprite)
{
this.bmd.clear();
this.bmd.draw(this.canvas, 0, 0);
this.context.clearRect(0, 0, this.game.width, this.game.height);
this.dirty = false;
}
},
/**
* Clears the Debug canvas.
*
* @method Phaser.Utils.Debug#reset
*/
reset: function ()
{
if (this.context)
{
this.context.clearRect(0, 0, this.game.width, this.game.height);
}
if (this.sprite)
{
this.bmd.clear();
}
},
/**
* Internal method that resets and starts the debug output values.
*
* @method Phaser.Utils.Debug#start
* @protected
* @param {number} [x=0] - The X value the debug info will start from.
* @param {number} [y=0] - The Y value the debug info will start from.
* @param {string} [color='rgb(255,255,255)'] - The color the debug text will drawn in.
* @param {number} [columnWidth=0] - The spacing between columns.
*/
start: function (x, y, color, columnWidth)
{
if (typeof x !== 'number') { x = 0; }
if (typeof y !== 'number') { y = 0; }
color = color || 'rgb(255,255,255)';
if (columnWidth === undefined) { columnWidth = 0; }
this.currentX = x;
this.currentY = y;
this.currentColor = color;
this.columnWidth = columnWidth;
this.dirty = true;
this.context.save();
this.context.setTransform(1, 0, 0, 1, 0, 0);
this.context.strokeStyle = color;
this.context.fillStyle = color;
this.context.font = this.font;
this.context.globalAlpha = this.currentAlpha;
},
/**
* Internal method that stops the debug output.
*
* @method Phaser.Utils.Debug#stop
* @protected
*/
stop: function ()
{
this.context.restore();
},
/**
* Internal method that outputs a single line of text split over as many columns as needed, one per parameter.
*
* @method Phaser.Utils.Debug#line
* @protected
*/
line: function ()
{
var x = this.currentX;
for (var i = 0; i < arguments.length; i++)
{
if (this.renderShadow)
{
this.context.fillStyle = 'rgb(0,0,0)';
this.context.fillText(arguments[i], x + 1, this.currentY + 1);
this.context.fillStyle = this.currentColor;
}
this.context.fillText(arguments[i], x, this.currentY);
x += this.columnWidth;
}
this.currentY += this.lineHeight;
},
/**
* Render Sound Manager information, including volume, mute, audio mode, and locked status.
*
* @method Phaser.Utils.Debug#sound
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
sound: function (x, y, color)
{
var sound = this.game.sound;
this.start(x, y, color);
if (sound.noAudio)
{
this.line('Audio is disabled');
}
else
{
this.line('Volume: ' + sound.volume.toFixed(2) + (sound.mute ? ' (Mute)' : ''));
this.line('Mute on pause: ' + sound.muteOnPause);
this.line('Using: ' + (sound.usingWebAudio ? ('Web Audio - ' + sound.context.state) : 'Audio Tag'));
this.line('Touch locked: ' + sound.touchLocked);
this.line('Sounds: ' + sound._sounds.length);
}
this.stop();
},
/**
* Render Sound information, including decoded state, duration, volume and more.
*
* @method Phaser.Utils.Debug#soundInfo
* @param {Phaser.Sound} sound - The sound object to debug.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
soundInfo: function (sound, x, y, color)
{
this.start(x, y, color);
this.line('Sound: ' + sound.key + ' Touch locked: ' + sound.game.sound.touchLocked);
this.line('Is Ready?: ' + this.game.cache.isSoundReady(sound.key) + ' Pending Playback: ' + sound.pendingPlayback);
this.line('Decoded: ' + sound.isDecoded + ' Decoding: ' + sound.isDecoding);
this.line('Playing: ' + sound.isPlaying + ' Loop: ' + sound.loop);
this.line('Time: ' + (sound.currentTime / 1000).toFixed(3) + 's Total: ' + sound.totalDuration.toFixed(3) + 's');
this.line('Volume: ' + sound.volume.toFixed(2) + (sound.mute ? ' (Mute)' : ''));
this.line('Using: ' + (sound.usingWebAudio ? 'Web Audio' : 'Audio Tag') + ' ' +
(sound.usingWebAudio ? ('Source: ' + (sound.sourceId || 'none')) : ''));
if (sound.currentMarker !== '')
{
this.line('Marker: ' + sound.currentMarker + ' Duration: ' + sound.duration.toFixed(3) + 's (' + sound.durationMS + 'ms)');
this.line('Start: ' + sound.markers[sound.currentMarker].start + ' Stop: ' + sound.markers[sound.currentMarker].stop);
this.line('Position: ' + sound.position);
}
this.stop();
},
/**
* Marks the follow {@link #target} and {@link #deadzone}.
*
* @method Phaser.Utils.Debug#camera
* @param {Phaser.Camera} camera - The Phaser.Camera to show the debug information for.
* @param {string} [color] - Color of the debug shapes to be rendered (format is css color string).
* @param {boolean} [filled=true] - Render the shapes filled (default, true) or stroked (false).
*/
camera: function (camera, color, filled)
{
var deadzone = camera.deadzone;
var target = camera.target;
var view = camera.view;
if (deadzone)
{
this._rect.setTo(view.x + deadzone.x, view.y + deadzone.y, deadzone.width, deadzone.height);
this.rectangle(this._rect, color, filled);
}
if (target)
{
this._line.setTo(view.centerX, view.centerY, target.x, target.y);
this.geom(this._line, color, filled);
this.geom(target, color, false, 3);
}
},
/**
* Render camera information including dimensions and location.
*
* @method Phaser.Utils.Debug#cameraInfo
* @param {Phaser.Camera} camera - The Phaser.Camera to show the debug information for.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
cameraInfo: function (camera, x, y, color)
{
var bounds = camera.bounds;
var deadzone = camera.deadzone;
var target = camera.target;
var view = camera.view;
this.start(x, y, color);
this.line('Camera (' + camera.width + ' x ' + camera.height + ')');
this.line('x: ' + camera.x + ' y: ' + camera.y);
this.line('Bounds: ' + (bounds ? ('x: ' + bounds.x + ' y: ' + bounds.y + ' w: ' + bounds.width + ' h: ' + bounds.height) : 'none'));
this.line('View: x: ' + view.x + ' y: ' + view.y + ' w: ' + view.width + ' h: ' + view.height);
this.line('Center: x: ' + camera.centerX + ' y: ' + camera.centerY);
this.line('Deadzone: ' + (deadzone ? ('x: ' + deadzone.x + ' y: ' + deadzone.y + ' w: ' + deadzone.width + ' h: ' + deadzone.height) : deadzone));
this.line('Total in view: ' + camera.totalInView);
this.line('At limit: x: ' + camera.atLimit.x + ' y: ' + camera.atLimit.y);
this.line('Target: ' + (target ? (target.name || target) : 'none'));
this.stop();
},
/**
* Render Timer information.
*
* @method Phaser.Utils.Debug#timer
* @param {Phaser.Timer} timer - The Phaser.Timer to show the debug information for.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
timer: function (timer, x, y, color)
{
this.start(x, y, color);
this.line('Timer (running: ' + timer.running + ' expired: ' + timer.expired + ')');
this.line('Next Tick: ' + timer.next + ' Duration: ' + timer.duration);
this.line('Paused: ' + timer.paused + ' Length: ' + timer.length);
this.stop();
},
/**
* Renders the Pointer.circle object onto the stage in green if down or yellow if up along with debug text.
*
* @method Phaser.Utils.Debug#pointer
* @param {Phaser.Pointer} pointer - The Pointer you wish to display.
* @param {boolean} [hideIfUp=false] - Doesn't render the circle if the pointer is up.
* @param {string} [downColor='rgba(0,255,0,0.5)'] - The color the circle is rendered in if the Pointer is down.
* @param {string} [upColor='rgba(255,255,0,0.5)'] - The color the circle is rendered in if the Pointer is up (and hideIfUp is false).
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
* @param {string} [inactiveColor='rgb(255,0,0,0.5)'] - The color the circle is rendered in if the Pointer is inactive.
*/
pointer: function (pointer, hideIfUp, downColor, upColor, color, inactiveColor)
{
if (pointer == null)
{
return;
}
if (hideIfUp === undefined) { hideIfUp = false; }
downColor = downColor || 'rgba(0,255,0,0.5)';
upColor = upColor || 'rgba(255,255,0,0.5)';
inactiveColor = inactiveColor || 'rgba(255,0,0,0.5)';
if (hideIfUp === true && pointer.isUp === true)
{
return;
}
this.start(pointer.x, pointer.y - 150, color);
this.context.beginPath();
this.context.arc(pointer.x, pointer.y, pointer.circle.radius, 0, Math.PI * 2);
if (pointer.active)
{
this.context.fillStyle = pointer.isDown ? downColor : upColor;
}
else
{
this.context.fillStyle = inactiveColor;
}
this.context.fill();
this.context.closePath();
// Render the points
this.context.beginPath();
this.context.moveTo(pointer.positionDown.x, pointer.positionDown.y);
this.context.lineTo(pointer.position.x, pointer.position.y);
this.context.lineWidth = 2;
this.context.stroke();
this.context.closePath();
var mx = pointer.movementX;
var my = pointer.movementY;
if (mx || my)
{
this.context.beginPath();
this.context.moveTo(mx + pointer.position.x, my + pointer.position.y);
this.context.lineTo(pointer.position.x, pointer.position.y);
this.context.lineWidth = 2;
this.context.stroke();
this.context.closePath();
}
// Render the text
this.line('ID: ' + pointer.id + ' Active: ' + pointer.active);
this.line('World X: ' + pointer.worldX.toFixed(1) + ' World Y: ' + pointer.worldY.toFixed(1));
this.line('Screen X: ' + pointer.x.toFixed(1) + ' Screen Y: ' + pointer.y.toFixed(1) + ' In: ' + pointer.withinGame);
this.line('Movement: X: ' + mx + ' Y: ' + my);
this.line('Duration: ' + pointer.duration + ' ms');
this.line('is Down: ' + pointer.isDown + ' is Up: ' + pointer.isUp);
if (pointer.isMouse)
{
this.line('Buttons: ' + this._pointerButtonIcon(pointer.leftButton) + ' ' +
this._pointerButtonIcon(pointer.middleButton) + ' ' +
this._pointerButtonIcon(pointer.rightButton));
}
this.stop();
},
_pointerButtonIcon: function (btn)
{
if (btn.isDown) { return 'x'; }
else if (btn.isUp) { return 'o'; }
return '-';
},
/**
* Render Sprite Input Debug information.
*
* @method Phaser.Utils.Debug#spriteInputInfo
* @param {Phaser.Sprite|Phaser.Image} sprite - The sprite to display the input data for.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
spriteInputInfo: function (sprite, x, y, color)
{
this.start(x, y, color);
this.line('Sprite Input: (' + sprite.width + ' x ' + sprite.height + ')');
this.line('x: ' + sprite.input.pointerX().toFixed(1) + ' y: ' + sprite.input.pointerY().toFixed(1));
this.line('over: ' + sprite.input.pointerOver() + ' duration: ' + sprite.input.overDuration().toFixed(0));
this.line('down: ' + sprite.input.pointerDown() + ' duration: ' + sprite.input.downDuration().toFixed(0));
this.line('just over: ' + sprite.input.justOver() + ' just out: ' + sprite.input.justOut());
this.stop();
},
/**
* Renders Phaser.Key object information.
*
* @method Phaser.Utils.Debug#key
* @param {Phaser.Key} key - The Key to render the information for.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
key: function (key, x, y, color)
{
this.start(x, y, color, 150);
this.line('Key:', key.keyCode, 'isDown:', key.isDown);
this.line('justDown:', key.justDown, 'justUp:', key.justUp);
this.line('Time Down:', key.timeDown.toFixed(0), 'duration:', key.duration.toFixed(0));
this.stop();
},
/**
* Render debug information about the Input object.
*
* @method Phaser.Utils.Debug#inputInfo
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
* @param {boolean} [showDetails=true] - Also describe input sources and pointers.
*/
inputInfo: function (x, y, color, showDetails)
{
var input = this.game.input;
if (showDetails === undefined)
{
showDetails = true;
}
this.start(x, y, color);
this.line('Input');
this.line('X: ' + input.x + ' Y: ' + input.y);
this.line('World X: ' + input.worldX + ' World Y: ' + input.worldY);
this.line('Scale X: ' + input.scale.x.toFixed(2) + ' Scale Y: ' + input.scale.x.toFixed(2));
this.line('Screen X: ' + input.activePointer.screenX.toFixed(1) + ' Screen Y: ' + input.activePointer.screenY.toFixed(1));
if (!showDetails)
{
this.stop();
return;
}
this.line('Sources:');
this.line(' ' + this._inputHandler(input.mouse, 'mouse'));
this.line(' ' + this._inputHandler(input.mspointer, 'mspointer'));
this.line(' ' + this._inputHandler(input.touch, 'touch'));
var pointers = input.pointers;
var mousePointer = input.mousePointer;
var modes = Phaser.PointerModes;
this.line('Pointers: (Max: ' + input.maxPointers + ')');
this.line(' ' + (mousePointer.isDown ? 'x' : 'o') + ' ' + modes[mousePointer.pointerMode] + ' ' + mousePointer.identifier);
for (var i = 0; i < pointers.length; i++)
{
var p = pointers[i];
this.line(' ' + (p.active ? '+' : '-') + ' ' + modes[p.pointerMode] + ' ' + p.identifier);
}
this.stop();
},
_inputHandler: function (handler, name)
{
return this._inputHandlerStatusIcon(handler) + ' ' + name + ' ' + this._inputHandlerCaptureIcon(handler);
},
_inputHandlerStatusIcon: function (handler)
{
if (!handler.active)
{
return ' ';
}
return handler.enabled ? '+' : '-';
},
_inputHandlerCaptureIcon: function (handler)
{
if (!handler.active)
{
return ' ';
}
return (handler.capture || handler.preventDefault) ? '*' : ' ';
},
/**
* Renders the Sprites bounds. Note: This is really expensive as it has to calculate the bounds every time you call it!
*
* @method Phaser.Utils.Debug#spriteBounds
* @param {Phaser.Sprite|Phaser.Image} sprite - The sprite to display the bounds of.
* @param {string} [color] - Color of the debug info to be rendered (format is css color string).
* @param {boolean} [filled=true] - Render the rectangle as a fillRect (default, true) or a strokeRect (false)
*/
spriteBounds: function (sprite, color, filled)
{
var bounds = sprite.getBounds();
bounds.x += this.game.camera.x;
bounds.y += this.game.camera.y;
this.rectangle(bounds, color, filled);
},
/**
* Renders the Rope's segments. Note: This is really expensive as it has to calculate new segments every time you call it
*
* @method Phaser.Utils.Debug#ropeSegments
* @param {Phaser.Rope} rope - The rope to display the segments of.
* @param {string} [color] - Color of the debug info to be rendered (format is css color string).
* @param {boolean} [filled=true] - Render the rectangle as a fillRect (default, true) or a strokeRect (false)
*/
ropeSegments: function (rope, color, filled)
{
var segments = rope.segments;
var self = this;
segments.forEach(function (segment)
{
self.rectangle(segment, color, filled);
}, this);
},
/**
* Render debug infos (including name, bounds info, position and some other properties) about the Sprite.
*
* @method Phaser.Utils.Debug#spriteInfo
* @param {Phaser.Sprite} sprite - The Sprite to display the information of.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
spriteInfo: function (sprite, x, y, color)
{
this.start(x, y, color);
this.line('Sprite: ' + (sprite.name || '') + ' (' + sprite.width + ' x ' + sprite.height + ') anchor: ' + sprite.anchor.x + ' x ' + sprite.anchor.y);
this.line('x: ' + sprite.x.toFixed(1) + ' y: ' + sprite.y.toFixed(1));
this.line('angle: ' + sprite.angle.toFixed(1) + ' rotation: ' + sprite.rotation.toFixed(1));
this.line('visible: ' + sprite.visible + ' in camera: ' + sprite.inCamera);
this.line('bounds x: ' + sprite._bounds.x.toFixed(1) + ' y: ' + sprite._bounds.y.toFixed(1) + ' w: ' + sprite._bounds.width.toFixed(1) + ' h: ' + sprite._bounds.height.toFixed(1));
this.line('parent: ' + (sprite.parent ? (sprite.parent.name || '(DisplayObject)') : '(none)'));
this.stop();
},
/**
* Renders the sprite coordinates in local, positional and world space.
*
* @method Phaser.Utils.Debug#spriteCoords
* @param {Phaser.Sprite|Phaser.Image} sprite - The sprite to display the coordinates for.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
spriteCoords: function (sprite, x, y, color)
{
this.start(x, y, color, 100);
if (sprite.name)
{
this.line(sprite.name);
}
this.line('x:', sprite.x.toFixed(2), 'y:', sprite.y.toFixed(2));
this.line('pos x:', sprite.position.x.toFixed(2), 'pos y:', sprite.position.y.toFixed(2));
this.line('world x:', sprite.world.x.toFixed(2), 'world y:', sprite.world.y.toFixed(2));
this.stop();
},
/**
* Renders Line information in the given color.
*
* @method Phaser.Utils.Debug#lineInfo
* @param {Phaser.Line} line - The Line to display the data for.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
lineInfo: function (line, x, y, color)
{
this.start(x, y, color, 80);
this.line('start.x:', line.start.x.toFixed(2), 'start.y:', line.start.y.toFixed(2));
this.line('end.x:', line.end.x.toFixed(2), 'end.y:', line.end.y.toFixed(2));
this.line('length:', line.length.toFixed(2), 'angle:', line.angle);
this.stop();
},
/**
* Renders a single pixel at the given size.
*
* @method Phaser.Utils.Debug#pixel
* @param {number} x - X position of the pixel to be rendered.
* @param {number} y - Y position of the pixel to be rendered.
* @param {string} [color] - Color of the pixel (format is css color string).
* @param {number} [size=2] - The width and height of the rendered pixel.
*/
pixel: function (x, y, color, size)
{
size = size || 2;
this.start();
this.context.fillStyle = color;
this.context.fillRect(x, y, size, size);
this.stop();
},
/**
* Renders a Phaser geometry object including Rectangle, Circle, Ellipse, Point or Line.
*
* @method Phaser.Utils.Debug#geom
* @param {Phaser.Rectangle|Phaser.Circle|Phaser.Ellipse|Phaser.Point|Phaser.Line} object - The geometry object to render.
* @param {string} [color] - Color of the debug info to be rendered (format is css color string).
* @param {boolean} [filled=true] - Render the objected as a filled (default, true) or a stroked (false)
* @param {number} [forceType=Phaser.Utils.Debug.GEOM_AUTO] - Force rendering of a specific type: (0) GEOM_AUTO, 1 GEOM_RECTANGLE, (2) GEOM_CIRCLE, (3) GEOM_POINT, (4) GEOM_LINE, (5) GEOM_ELLIPSE.
*/
geom: function (object, color, filled, forceType)
{
if (filled === undefined) { filled = true; }
if (forceType === undefined) { forceType = 0; }
color = color || 'rgba(0,255,0,0.4)';
this.start();
this.context.fillStyle = color;
this.context.strokeStyle = color;
this.context.lineWidth = this.lineWidth;
var Debug = Phaser.Utils.Debug;
if (forceType === Debug.GEOM_RECTANGLE || object instanceof Phaser.Rectangle)
{
if (filled)
{
this.context.fillRect(object.x - this.game.camera.x, object.y - this.game.camera.y, object.width, object.height);
}
else
{
this.context.strokeRect(object.x - this.game.camera.x, object.y - this.game.camera.y, object.width, object.height);
}
}
else if (forceType === Debug.GEOM_CIRCLE || object instanceof Phaser.Circle)
{
this.context.beginPath();
this.context.arc(object.x - this.game.camera.x, object.y - this.game.camera.y, object.radius, 0, Math.PI * 2, false);
this.context.closePath();
if (filled)
{
this.context.fill();
}
else
{
this.context.stroke();
}
}
else if (forceType === Debug.GEOM_POINT || object instanceof Phaser.Point)
{
this.context.fillRect(object.x - this.game.camera.x, object.y - this.game.camera.y, 4, 4);
}
else if (forceType === Debug.GEOM_LINE || object instanceof Phaser.Line)
{
this.context.beginPath();
this.context.moveTo((object.start.x + 0.5) - this.game.camera.x, (object.start.y + 0.5) - this.game.camera.y);
this.context.lineTo((object.end.x + 0.5) - this.game.camera.x, (object.end.y + 0.5) - this.game.camera.y);
this.context.closePath();
this.context.stroke();
}
else if (forceType === Debug.GEOM_ELLIPSE || object instanceof Phaser.Ellipse)
{
this.context.beginPath();
this.context.ellipse(object.centerX - this.game.camera.x, object.centerY - this.game.camera.y, object.width / 2, object.height / 2, 0, 2 * Math.PI, false);
this.context.closePath();
if (filled)
{
this.context.fill();
}
else
{
this.context.stroke();
}
}
this.stop();
},
/**
* Renders a Rectangle.
*
* @method Phaser.Utils.Debug#rectangle
* @param {Phaser.Rectangle|object} object - The rectangle to render.
* @param {string} [color] - Color of the debug info to be rendered (format is css color string).
* @param {boolean} [filled=true] - Render the rectangle as filled (default, true) or a stroked (false)
*/
rectangle: function (object, color, filled)
{
if (filled === undefined) { filled = true; }
color = color || 'rgba(0, 255, 0, 0.4)';
this.start();
if (filled)
{
this.context.fillStyle = color;
this.context.fillRect(object.x - this.game.camera.x, object.y - this.game.camera.y, object.width, object.height);
}
else
{
this.context.lineWidth = this.lineWidth;
this.context.strokeStyle = color;
this.context.strokeRect(object.x - this.game.camera.x, object.y - this.game.camera.y, object.width, object.height);
}
this.stop();
},
/**
* Render a string of text.
*
* @method Phaser.Utils.Debug#text
* @param {string} text - The line of text to draw.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color] - Color of the debug info to be rendered (format is css color string).
* @param {string} [font] - The font of text to draw.
*/
text: function (text, x, y, color, font)
{
color = color || 'rgb(255,255,255)';
font = font || this.font;
this.start();
this.context.font = font;
if (this.renderShadow)
{
this.context.fillStyle = 'rgb(0,0,0)';
this.context.fillText(text, x + 1, y + 1);
}
this.context.fillStyle = color;
this.context.fillText(text, x, y);
this.stop();
},
/**
* Visually renders a QuadTree to the display.
*
* @method Phaser.Utils.Debug#quadTree
* @param {Phaser.QuadTree} quadtree - The quadtree to render.
* @param {string} color - The color of the lines in the quadtree.
*/
quadTree: function (quadtree, color)
{
color = color || 'rgba(255,0,0,0.3)';
this.start();
var bounds = quadtree.bounds;
if (quadtree.nodes.length === 0)
{
this.context.strokeStyle = color;
this.context.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
this.text('size: ' + quadtree.objects.length, bounds.x + 4, bounds.y + 16, 'rgb(0,200,0)', '12px Courier');
this.context.strokeStyle = 'rgb(0,255,0)';
for (var i = 0; i < quadtree.objects.length; i++)
{
this.context.strokeRect(quadtree.objects[i].x, quadtree.objects[i].y, quadtree.objects[i].width, quadtree.objects[i].height);
}
}
else
{
for (var i = 0; i < quadtree.nodes.length; i++)
{
this.quadTree(quadtree.nodes[i]);
}
}
this.stop();
},
/**
* Render a Sprites Physics body if it has one set. The body is rendered as a filled or stroked rectangle.
* This only works for Arcade Physics, Ninja Physics (AABB and Circle only) and Box2D Physics bodies.
* To display a P2 Physics body you should enable debug mode on the body when creating it.
*
* @method Phaser.Utils.Debug#body
* @param {Phaser.Sprite} sprite - The Sprite who's body will be rendered.
* @param {string} [color='rgba(0,255,0,0.4)'] - Color of the debug rectangle to be rendered. The format is a CSS color string such as '#ff0000' or 'rgba(255,0,0,0.5)'.
* @param {boolean} [filled=true] - Render the body as a filled rectangle (true) or a stroked rectangle (false)
*/
body: function (sprite, color, filled)
{
if (sprite.body)
{
this.start();
if (sprite.body.type === Phaser.Physics.ARCADE)
{
Phaser.Physics.Arcade.Body.render(this.context, sprite.body, color, filled, this.lineWidth);
}
else if (sprite.body.type === Phaser.Physics.NINJA)
{
Phaser.Physics.Ninja.Body.render(this.context, sprite.body, color, filled);
}
else if (sprite.body.type === Phaser.Physics.BOX2D)
{
Phaser.Physics.Box2D.renderBody(this.context, sprite.body, color);
}
this.stop();
}
},
/**
* Render a Sprites Physic Body information.
*
* @method Phaser.Utils.Debug#bodyInfo
* @param {Phaser.Sprite} sprite - The sprite to be rendered.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
bodyInfo: function (sprite, x, y, color)
{
if (sprite.body)
{
this.start(x, y, color, 210);
if (sprite.body.type === Phaser.Physics.ARCADE)
{
Phaser.Physics.Arcade.Body.renderBodyInfo(this, sprite.body);
}
else if (sprite.body.type === Phaser.Physics.BOX2D)
{
this.game.physics.box2d.renderBodyInfo(this, sprite.body);
}
this.stop();
}
},
/**
* Renders 'debug draw' data for the Box2D world if it exists.
* This uses the standard debug drawing feature of Box2D, so colors will be decided by
* the Box2D engine.
*
* @method Phaser.Utils.Debug#box2dWorld
*/
box2dWorld: function ()
{
this.start();
this.context.translate(-this.game.camera.view.x, -this.game.camera.view.y, 0);
this.game.physics.box2d.renderDebugDraw(this.context);
this.stop();
},
/**
* Renders 'debug draw' data for the given Box2D body.
* This uses the standard debug drawing feature of Box2D, so colors will be decided by the Box2D engine.
*
* @method Phaser.Utils.Debug#box2dBody
* @param {Phaser.Physics.Box2D.Body} body - The body to be rendered.
* @param {string} [color='rgb(0,255,0)'] - Color of the rendering (format is css color string).
*/
box2dBody: function (body, color)
{
this.start();
Phaser.Physics.Box2D.renderBody(this.context, body, color);
this.stop();
},
/**
* Call this function from the Dev Tools console.
*
* It will scan the display list and output all of the Objects it finds, and their renderOrderIDs.
*
* **Note** Requires a browser that supports console.group and console.groupEnd (such as Chrome)
*
* @method Phaser.Utils.Debug#displayList
* @param {Object} [displayObject] - The displayObject level display object to start from. Defaults to the World.
*/
displayList: function (displayObject)
{
if (displayObject === undefined) { displayObject = this.game.world; }
if (displayObject.hasOwnProperty('renderOrderID'))
{
console.log('[' + displayObject.renderOrderID + ']', displayObject);
}
else
{
console.log('[]', displayObject);
}
if (displayObject.children && displayObject.children.length > 0)
{
for (var i = 0; i < displayObject.children.length; i++)
{
this.game.debug.displayList(displayObject.children[i]);
}
}
},
/**
* Prints a description of the {@link Phaser.Game#renderer renderer} and render session.
*
* @method Phaser.Utils.Debug#renderer
* @param {number} [x=0] - The X value the debug info will start from.
* @param {number} [y=0] - The Y value the debug info will start from.
* @param {string} [color='rgb(255,255,255)'] - The color the debug text will drawn in.
*/
renderer: function (x, y, color)
{
var r = this.game.renderer;
var s = r.renderSession;
this.start(x, y, color);
this.line((r.gl ? 'WebGL' : 'Canvas') + ' Renderer (' + r.width + ' x ' + r.height + ')');
this.line('autoResize: ' + r.autoResize);
this.line('clearBeforeRender: ' + r.clearBeforeRender);
this.line('resolution: ' + r.resolution);
this.line('transparent: ' + r.transparent);
this.line('renderSession:');
if (r.gl)
{
this.line(' currentBatchedTextures: (' + r.currentBatchedTextures.length + ')');
for (var i = 0; i < r.currentBatchedTextures.length; i++)
{
this.line(' ' + r.currentBatchedTextures[i]);
}
this.line(' drawCount: ' + s.drawCount);
this.line(' maxTextures: ' + r.maxTextures);
this.line(' maxTextureSize: ' + r.maxTextureSize);
this.line(' maxTextureAvailableSpace: ' + s.maxTextureAvailableSpace);
this.line(' roundPixels: ' + s.roundPixels);
}
else
{
this.line(' roundPixels: ' + s.roundPixels);
this.line(' scaleMode: ' + (s.scaleMode === 0 ? 'LINEAR' : (s.scaleMode === 1 ? 'NEAREST' : s.scaleMode)));
}
this.stop();
},
canvasPool: function (x, y, color, columnWidth)
{
var pool = Phaser.CanvasPool;
this.start(x, y, color, columnWidth || 100);
this.line('Canvas Pool');
this.line('Used:', pool.getTotal());
this.line('Free:', pool.getFree());
this.line('Total:', pool.length);
this.stop();
},
/**
* Render each physics {@link #body} in a group.
*
* @method Phaser.Utils.Debug#physicsGroup
* @param {Phaser.Group} group - A group containing physics-enabled sprites.
* @param {string} [color='rgba(0,255,0,0.4)'] - Color of the debug rectangle to be rendered. The format is a CSS color string such as '#ff0000' or 'rgba(255,0,0,0.5)'.
* @param {boolean} [filled=true] - Render the body as a filled rectangle (true) or a stroked rectangle (false).
* @param {boolean} [checkExists=false] Render only children with `exists=true`.
*/
physicsGroup: function (group, color, filled, checkExists)
{
group.forEach(this.body, this, checkExists, color, filled);
},
/**
* Prints Phaser {@link Phaser.VERSION version}, {@link Phaser.Game.#renderType rendering mode}, and {@link Phaser.Device#webAudio device audio support}.
*
* @method Phaser.Utils.Debug#phaser
* @param {number} x - The X value the debug info will start from.
* @param {number} y - The Y value the debug info will start from.
* @param {string} [color='rgb(255,255,255)'] - The color the debug text will drawn in.
*/
phaser: function (x, y, color)
{
this.text('Phaser v' + Phaser.VERSION + ' ' +
(this.game.renderType === Phaser.WEBGL ? 'WebGL' : 'Canvas') + ' ' +
(this.game.device.webAudio ? 'WebAudio' : 'HTML Audio'),
x, y, color, this.font);
},
/**
* Prints game/canvas dimensions and {@link Phaser.ScaleManager game scale} settings.
*
* @method Phaser.Utils.Debug#scale
* @param {number} x - The X value the debug info will start from.
* @param {number} y - The Y value the debug info will start from.
* @param {string} [color='rgb(255,255,255)'] - The color the debug text will drawn in.
*/
scale: function (x, y, color)
{
this.start(x, y, color);
var scale = this.game.scale;
var factor = scale.scaleFactorInversed;
var bounds = scale._parentBounds;
var x = ' x ';
this.line('Game: ' + this.game.width + x + this.game.height);
this.line('Canvas: ' + scale.width + x + scale.height +
' (' + factor.x.toFixed(2) + x + factor.y.toFixed(2) + ')' +
' [' + scale.aspectRatio.toFixed(2) + ']');
this.line('Mode: ' + Phaser.ScaleManager.MODES[scale.currentScaleMode] +
(scale.currentScaleMode === Phaser.ScaleManager.USER_SCALE ?
(' (' + scale._userScaleFactor.x + x + scale._userScaleFactor.y + ')') :
''));
this.line('Parent: ' + (scale.parentIsWindow ? 'window' : scale.parentNode) +
(bounds.empty ? '' : (' (' + bounds.width + x + bounds.height + ')')));
this.line('Screen: ' + scale.classifyOrientation(scale.screenOrientation) +
(scale.incorrectOrientation ? ' (incorrect)' : ''));
this.stop();
},
/**
* Prints the progress of a {@link Phaser.Loader}.
*
* Typically you would call this within a {@link State#loadRender} callback and pass `game.load` ({@link Phaser.Game#load}).
*
* You can enable {@link Phaser.Loader#resetLocked} to temporarily hold the loader in its 'complete' state.
* Just remember to disable it before restarting the loader (such as when changing states).
*
* @method Phaser.Utils.Debug#loader
* @param {Phaser.Loader} loader - The loader. Usually `game.load` ({@link Phaser.Game#load}).
* @param {number} x - The X value the debug info will start from.
* @param {number} y - The Y value the debug info will start from.
* @param {string} [color='rgb(255,255,255)'] - The color the debug text will drawn in.
*/
loader: function (loader, x, y, color)
{
var pad = Phaser.Utils.pad;
this.start(x, y, color);
if (loader.hasLoaded)
{
this.line('Complete' + (loader.resetLocked ? ' [locked]' : ''));
}
else if (loader.isLoading)
{
this.line('Loading');
}
else
{
this.line('Not started');
}
if (!loader.hasLoaded || loader.resetLocked)
{
this.line('Progress: ' + (pad(loader.progress, 3) + '%'));
this.line('Files: ' + loader._loadedFileCount + ' of ' +
loader._totalFileCount);
this.line('Packs: ' + loader._loadedPackCount + ' of ' +
loader._loadedPackCount);
}
this.stop();
},
/**
* Shows device capabilities: Pointer Events, Touch Events, Web Audio, WebGL.
*
* @method Phaser.Utils.Debug#device
* @param {number} x
* @param {number} y
* @param {string} [color]
*/
device: function (x, y, color)
{
var device = this.game.device;
this.start(x, y, color);
this.line('Device');
this.line('Pointer Events: ' + device.mspointer);
this.line('Touch: ' + device.touch);
this.line('Web Audio: ' + device.webAudio);
this.line('WebGL: ' + device.webGL);
this.stop();
},
/**
* Destroy this object.
*
* @method Phaser.Utils.Debug#destroy
*/
destroy: function ()
{
Phaser.CanvasPool.remove(this);
}
};
Phaser.Utils.Debug.prototype.constructor = Phaser.Utils.Debug;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* DOM utility class.
*
* Provides a useful Window and Element functions as well as cross-browser compatibility buffer.
*
* Some code originally derived from {@link https://github.com/ryanve/verge verge}.
* Some parts were inspired by the research of Ryan Van Etten, released under MIT License 2013.
*
* @class Phaser.DOM
* @static
*/
Phaser.DOM = {
/**
* Get the [absolute] position of the element relative to the Document.
*
* The value may vary slightly as the page is scrolled due to rounding errors.
*
* @method Phaser.DOM.getOffset
* @param {DOMElement} element - The targeted element that we want to retrieve the offset.
* @param {Phaser.Point} [point] - The point we want to take the x/y values of the offset.
* @return {Phaser.Point} - A point objet with the offsetX and Y as its properties.
*/
getOffset: function (element, point)
{
point = point || new Phaser.Point();
var box = element.getBoundingClientRect();
var scrollTop = Phaser.DOM.scrollY;
var scrollLeft = Phaser.DOM.scrollX;
var clientTop = document.documentElement.clientTop;
var clientLeft = document.documentElement.clientLeft;
point.x = box.left + scrollLeft - clientLeft;
point.y = box.top + scrollTop - clientTop;
return point;
},
/**
* A cross-browser element.getBoundingClientRect method with optional cushion.
*
* Returns a plain object containing the properties `top/bottom/left/right/width/height` with respect to the top-left corner of the current viewport.
* Its properties match the native rectangle.
* The cushion parameter is an amount of pixels (+/-) to cushion the element.
* It adjusts the measurements such that it is possible to detect when an element is near the viewport.
*
* @method Phaser.DOM.getBounds
* @param {DOMElement|Object} element - The element or stack (uses first item) to get the bounds for.
* @param {number} [cushion] - A +/- pixel adjustment amount.
* @return {Object|boolean} A plain object containing the properties `top/bottom/left/right/width/height` or `false` if a non-valid element is given.
*/
getBounds: function (element, cushion)
{
if (cushion === undefined) { cushion = 0; }
element = element && !element.nodeType ? element[0] : element;
if (!element || element.nodeType !== 1)
{
return false;
}
else
{
return this.calibrate(element.getBoundingClientRect(), cushion);
}
},
/**
* Calibrates element coordinates for `inLayoutViewport` checks.
*
* @method Phaser.DOM.calibrate
* @private
* @param {object} coords - An object containing the following properties: `{top: number, right: number, bottom: number, left: number}`
* @param {number} [cushion] - A value to adjust the coordinates by.
* @return {object} The calibrated element coordinates
*/
calibrate: function (coords, cushion)
{
cushion = +cushion || 0;
var output = { width: 0, height: 0, left: 0, right: 0, top: 0, bottom: 0 };
output.width = (output.right = coords.right + cushion) - (output.left = coords.left - cushion);
output.height = (output.bottom = coords.bottom + cushion) - (output.top = coords.top - cushion);
return output;
},
/**
* Get the Visual viewport aspect ratio (or the aspect ratio of an object or element)
*
* @method Phaser.DOM.getAspectRatio
* @param {(DOMElement|Object)} [object=(visualViewport)] - The object to determine the aspect ratio for. Must have public `width` and `height` properties or methods.
* @return {number} The aspect ratio.
*/
getAspectRatio: function (object)
{
object = object == null ? this.visualBounds : object.nodeType === 1 ? this.getBounds(object) : object;
var w = object.width;
var h = object.height;
if (typeof w === 'function')
{
w = w.call(object);
}
if (typeof h === 'function')
{
h = h.call(object);
}
return w / h;
},
/**
* Tests if the given DOM element is within the Layout viewport.
*
* The optional cushion parameter allows you to specify a distance.
*
* inLayoutViewport(element, 100) is `true` if the element is in the viewport or 100px near it.
* inLayoutViewport(element, -100) is `true` if the element is in the viewport or at least 100px near it.
*
* @method Phaser.DOM.inLayoutViewport
* @param {DOMElement|Object} element - The DOM element to check. If no element is given it defaults to the Phaser game canvas.
* @param {number} [cushion] - The cushion allows you to specify a distance within which the element must be within the viewport.
* @return {boolean} True if the element is within the viewport, or within `cushion` distance from it.
*/
inLayoutViewport: function (element, cushion)
{
var r = this.getBounds(element, cushion);
return !!r && r.bottom >= 0 && r.right >= 0 && r.top <= this.layoutBounds.width && r.left <= this.layoutBounds.height;
},
/**
* Returns the device screen orientation.
*
* Orientation values: 'portrait-primary', 'landscape-primary', 'portrait-secondary', 'landscape-secondary'.
*
* Order of resolving:
* - Screen Orientation API, or variation of - Future track. Most desktop and mobile browsers.
* - Screen size ratio check - If fallback is 'screen', suited for desktops.
* - Viewport size ratio check - If fallback is 'viewport', suited for mobile.
* - window.orientation - If fallback is 'window.orientation', works iOS and probably most Android; non-recommended track.
* - Media query
* - Viewport size ratio check (probably only IE9 and legacy mobile gets here..)
*
* See
* - https://w3c.github.io/screen-orientation/ (conflicts with mozOrientation/msOrientation)
* - https://developer.mozilla.org/en-US/docs/Web/API/Screen.orientation (mozOrientation)
* - http://msdn.microsoft.com/en-us/library/ie/dn342934(v=vs.85).aspx
* - https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Testing_media_queries
* - http://stackoverflow.com/questions/4917664/detect-viewport-orientation
* - http://www.matthewgifford.com/blog/2011/12/22/a-misconception-about-window-orientation
*
* @method Phaser.DOM.getScreenOrientation
* @protected
* @param {string} [primaryFallback=(none)] - Specify 'screen', 'viewport', or 'window.orientation'.
*/
getScreenOrientation: function (primaryFallback)
{
var screen = window.screen;
var orientation = screen.orientation || screen.mozOrientation || screen.msOrientation;
if (orientation && typeof orientation.type === 'string')
{
// Screen Orientation API specification
return orientation.type;
}
else if (typeof orientation === 'string')
{
// moz/ms-orientation are strings
return orientation;
}
var PORTRAIT = 'portrait-primary';
var LANDSCAPE = 'landscape-primary';
if (primaryFallback === 'screen')
{
return (screen.height > screen.width) ? PORTRAIT : LANDSCAPE;
}
else if (primaryFallback === 'viewport')
{
return (this.visualBounds.height > this.visualBounds.width) ? PORTRAIT : LANDSCAPE;
}
else if (primaryFallback === 'window.orientation' && typeof window.orientation === 'number')
{
// This may change by device based on "natural" orientation.
return (window.orientation === 0 || window.orientation === 180) ? PORTRAIT : LANDSCAPE;
}
else if (window.matchMedia)
{
if (window.matchMedia('(orientation: portrait)').matches)
{
return PORTRAIT;
}
else if (window.matchMedia('(orientation: landscape)').matches)
{
return LANDSCAPE;
}
}
return (this.visualBounds.height > this.visualBounds.width) ? PORTRAIT : LANDSCAPE;
},
/**
* The bounds of the Visual viewport, as discussed in
* {@link http://www.quirksmode.org/mobile/viewports.html A tale of two viewports — part one}
* with one difference: the viewport size _excludes_ scrollbars, as found on some desktop browsers.
*
* Supported mobile:
* iOS/Safari, Android 4, IE10, Firefox OS (maybe not Firefox Android), Opera Mobile 16
*
* The properties change dynamically.
*
* @type {Phaser.Rectangle}
* @property {number} x - Scroll, left offset - eg. "scrollX"
* @property {number} y - Scroll, top offset - eg. "scrollY"
* @property {number} width - Viewport width in pixels.
* @property {number} height - Viewport height in pixels.
* @readonly
*/
visualBounds: new Phaser.Rectangle(),
/**
* The bounds of the Layout viewport, as discussed in
* {@link http://www.quirksmode.org/mobile/viewports2.html A tale of two viewports — part two};
* but honoring the constraints as specified applicable viewport meta-tag.
*
* The bounds returned are not guaranteed to be fully aligned with CSS media queries (see
* {@link http://www.matanich.com/2013/01/07/viewport-size/ What size is my viewport?}).
*
* This is _not_ representative of the Visual bounds: in particular the non-primary axis will
* generally be significantly larger than the screen height on mobile devices when running with a
* constrained viewport.
*
* The properties change dynamically.
*
* @type {Phaser.Rectangle}
* @property {number} width - Viewport width in pixels.
* @property {number} height - Viewport height in pixels.
* @readonly
*/
layoutBounds: new Phaser.Rectangle(),
/**
* The size of the document / Layout viewport.
*
* This incorrectly reports the dimensions in IE.
*
* The properties change dynamically.
*
* @type {Phaser.Rectangle}
* @property {number} width - Document width in pixels.
* @property {number} height - Document height in pixels.
* @readonly
*/
documentBounds: new Phaser.Rectangle()
};
Phaser.Device.whenReady(function (device)
{
// All target browsers should support page[XY]Offset.
var scrollX = window && ('pageXOffset' in window) ?
function () { return window.pageXOffset; } :
function () { return document.documentElement.scrollLeft; };
var scrollY = window && ('pageYOffset' in window) ?
function () { return window.pageYOffset; } :
function () { return document.documentElement.scrollTop; };
/**
* A cross-browser window.scrollX.
*
* @name Phaser.DOM.scrollX
* @property {number} scrollX
* @readonly
* @protected
*/
Object.defineProperty(Phaser.DOM, 'scrollX', {get: scrollX});
/**
* A cross-browser window.scrollY.
*
* @name Phaser.DOM.scrollY
* @property {number} scrollY
* @readonly
* @protected
*/
Object.defineProperty(Phaser.DOM, 'scrollY', {get: scrollY});
Object.defineProperty(Phaser.DOM.visualBounds, 'x', {get: scrollX});
Object.defineProperty(Phaser.DOM.visualBounds, 'y', {get: scrollY});
Object.defineProperty(Phaser.DOM.layoutBounds, 'x', {value: 0});
Object.defineProperty(Phaser.DOM.layoutBounds, 'y', {value: 0});
var treatAsDesktop = device.desktop &&
(document.documentElement.clientWidth <= window.innerWidth) &&
(document.documentElement.clientHeight <= window.innerHeight);
// Desktop browsers align the layout viewport with the visual viewport.
// This differs from mobile browsers with their zooming design.
// Ref. http://quirksmode.org/mobile/tableViewport.html
if (treatAsDesktop)
{
// PST- When scrollbars are not included this causes upstream issues in ScaleManager.
// So reverted to the old "include scrollbars."
var clientWidth = function ()
{
return Math.max(window.innerWidth, document.documentElement.clientWidth);
};
var clientHeight = function ()
{
return Math.max(window.innerHeight, document.documentElement.clientHeight);
};
// Interested in area sans-scrollbar
Object.defineProperty(Phaser.DOM.visualBounds, 'width', {get: clientWidth});
Object.defineProperty(Phaser.DOM.visualBounds, 'height', {get: clientHeight});
Object.defineProperty(Phaser.DOM.layoutBounds, 'width', {get: clientWidth});
Object.defineProperty(Phaser.DOM.layoutBounds, 'height', {get: clientHeight});
}
else
{
Object.defineProperty(Phaser.DOM.visualBounds, 'width', {
get: function ()
{
return window.innerWidth;
}
});
Object.defineProperty(Phaser.DOM.visualBounds, 'height', {
get: function ()
{
return window.innerHeight;
}
});
Object.defineProperty(Phaser.DOM.layoutBounds, 'width', {
get: function ()
{
var a = document.documentElement.clientWidth;
var b = window.innerWidth;
return a < b ? b : a; // max
}
});
Object.defineProperty(Phaser.DOM.layoutBounds, 'height', {
get: function ()
{
var a = document.documentElement.clientHeight;
var b = window.innerHeight;
return a < b ? b : a; // max
}
});
}
// For Phaser.DOM.documentBounds
// Ref. http://www.quirksmode.org/mobile/tableViewport_desktop.html
Object.defineProperty(Phaser.DOM.documentBounds, 'x', {value: 0});
Object.defineProperty(Phaser.DOM.documentBounds, 'y', {value: 0});
Object.defineProperty(Phaser.DOM.documentBounds, 'width', {
get: function ()
{
var d = document.documentElement;
return Math.max(d.clientWidth, d.offsetWidth, d.scrollWidth);
}
});
Object.defineProperty(Phaser.DOM.documentBounds, 'height', {
get: function ()
{
var d = document.documentElement;
return Math.max(d.clientHeight, d.offsetHeight, d.scrollHeight);
}
});
}, null, true);
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* ArraySet is a Set data structure (items must be unique within the set) that also maintains order.
* This allows specific items to be easily added or removed from the Set.
*
* Item equality (and uniqueness) is determined by the behavior of `Array.indexOf`.
*
* This used primarily by the Input subsystem.
*
* @class Phaser.ArraySet
* @constructor
* @param {any[]} [list=(new array)] - The backing array: if specified the items in the list _must_ be unique, per `Array.indexOf`, and the ownership of the array _should_ be relinquished to the ArraySet.
*/
Phaser.ArraySet = function (list)
{
/**
* Current cursor position as established by `first` and `next`.
* @property {integer} position
* @default
*/
this.position = 0;
/**
* The backing array.
* @property {any[]} list
*/
this.list = list || [];
};
Phaser.ArraySet.prototype = {
/**
* Adds a new element to the end of the list.
* If the item already exists in the list it is not moved.
*
* @method Phaser.ArraySet#add
* @param {any} item - The element to add to this list.
* @return {any} The item that was added.
*/
add: function (item)
{
if (!this.exists(item))
{
this.list.push(item);
}
return item;
},
/**
* Gets the index of the item in the list, or -1 if it isn't in the list.
*
* @method Phaser.ArraySet#getIndex
* @param {any} item - The element to get the list index for.
* @return {integer} The index of the item or -1 if not found.
*/
getIndex: function (item)
{
return this.list.indexOf(item);
},
/**
* Gets an item from the set based on the property strictly equaling the value given.
* Returns null if not found.
*
* @method Phaser.ArraySet#getByKey
* @param {string} property - The property to check against the value.
* @param {any} value - The value to check if the property strictly equals.
* @return {any} The item that was found, or null if nothing matched.
*/
getByKey: function (property, value)
{
var i = this.list.length;
while (i--)
{
if (this.list[i][property] === value)
{
return this.list[i];
}
}
return null;
},
/**
* Checks for the item within this list.
*
* @method Phaser.ArraySet#exists
* @param {any} item - The element to get the list index for.
* @return {boolean} True if the item is found in the list, otherwise false.
*/
exists: function (item)
{
return (this.list.indexOf(item) > -1);
},
/**
* Removes all the items.
*
* @method Phaser.ArraySet#reset
*/
reset: function ()
{
this.list.length = 0;
},
/**
* Removes the given element from this list if it exists.
*
* @method Phaser.ArraySet#remove
* @param {any} item - The item to be removed from the list.
* @return {any} item - The item that was removed.
*/
remove: function (item)
{
var idx = this.list.indexOf(item);
if (idx > -1)
{
this.list.splice(idx, 1);
return item;
}
},
/**
* Sets the property `key` to the given value on all members of this list.
*
* @method Phaser.ArraySet#setAll
* @param {any} key - The property of the item to set.
* @param {any} value - The value to set the property to.
*/
setAll: function (key, value)
{
var i = this.list.length;
while (i--)
{
if (this.list[i])
{
this.list[i][key] = value;
}
}
},
/**
* Calls a function on all members of this list, using the member as the context for the callback.
*
* If the `key` property is present it must be a function.
* The function is invoked using the item as the context.
*
* @method Phaser.ArraySet#callAll
* @param {string} key - The name of the property with the function to call.
* @param {...*} parameter - Additional parameters that will be passed to the callback.
*/
callAll: function (key)
{
var args = Array.prototype.slice.call(arguments, 1);
var i = this.list.length;
while (i--)
{
if (this.list[i] && this.list[i][key])
{
this.list[i][key].apply(this.list[i], args);
}
}
},
/**
* Removes every member from this ArraySet and optionally destroys it.
*
* @method Phaser.ArraySet#removeAll
* @param {boolean} [destroy=false] - Call `destroy` on each member as it's removed from this set.
*/
removeAll: function (destroy)
{
if (destroy === undefined) { destroy = false; }
var i = this.list.length;
while (i--)
{
if (this.list[i])
{
var item = this.remove(this.list[i]);
if (destroy)
{
item.destroy();
}
}
}
this.position = 0;
this.list = [];
}
};
/**
* Number of items in the ArraySet. Same as `list.length`.
*
* @name Phaser.ArraySet#total
* @property {integer} total
*/
Object.defineProperty(Phaser.ArraySet.prototype, 'total', {
get: function ()
{
return this.list.length;
}
});
/**
* Returns the first item and resets the cursor to the start.
*
* @name Phaser.ArraySet#first
* @property {any} first
*/
Object.defineProperty(Phaser.ArraySet.prototype, 'first', {
get: function ()
{
this.position = 0;
if (this.list.length > 0)
{
return this.list[0];
}
else
{
return null;
}
}
});
/**
* Returns the the next item (based on the cursor) and advances the cursor.
*
* @name Phaser.ArraySet#next
* @property {any} next
*/
Object.defineProperty(Phaser.ArraySet.prototype, 'next', {
get: function ()
{
if (this.position < this.list.length)
{
this.position++;
return this.list[this.position];
}
else
{
return null;
}
}
});
Phaser.ArraySet.prototype.constructor = Phaser.ArraySet;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Utility functions for dealing with Arrays.
*
* @class Phaser.ArrayUtils
* @static
*/
Phaser.ArrayUtils = {
/**
* Fetch a random entry from the given array.
*
* Will return null if there are no array items that fall within the specified range
* or if there is no item for the randomly chosen index.
*
* @method Phaser.ArrayUtils.getRandomItem
* @param {any[]} objects - An array of objects.
* @param {integer} startIndex - Optional offset off the front of the array. Default value is 0, or the beginning of the array.
* @param {integer} length - Optional restriction on the number of values you want to randomly select from.
* @return {object} The random object that was selected.
*/
getRandomItem: function (objects, startIndex, length)
{
if (objects === null) { return null; }
if (startIndex === undefined) { startIndex = 0; }
if (length === undefined) { length = objects.length; }
var randomIndex = startIndex + Math.floor(Math.random() * length);
return objects[randomIndex] === undefined ? null : objects[randomIndex];
},
/**
* Removes a random object from the given array and returns it.
*
* Will return null if there are no array items that fall within the specified range
* or if there is no item for the randomly chosen index.
*
* @method Phaser.ArrayUtils.removeRandomItem
* @param {any[]} objects - An array of objects.
* @param {integer} startIndex - Optional offset off the front of the array. Default value is 0, or the beginning of the array.
* @param {integer} length - Optional restriction on the number of values you want to randomly select from.
* @return {object} The random object that was removed.
*/
removeRandomItem: function (objects, startIndex, length)
{
if (objects == null)
{ // undefined or null
return null;
}
if (startIndex === undefined) { startIndex = 0; }
if (length === undefined) { length = objects.length; }
var randomIndex = startIndex + Math.floor(Math.random() * length);
if (randomIndex < objects.length)
{
var removed = objects.splice(randomIndex, 1);
return removed[0] === undefined ? null : removed[0];
}
else
{
return null;
}
},
/**
* Remove one or more items at the given index and reorder the array.
*
* The new array length will be `array.length - count`.
*
* This is an alternative to `array.splice(startIndex, count)`.
*
* @see https://github.com/mreinstein/remove-array-items
* @see https://gamealchemist.wordpress.com/2013/05/01/lets-get-those-javascript-arrays-to-work-fast/
*
* @method Phaser.ArrayUtils.remove
* @param {any[]} array
* @param {integer} startIndex
* @param {integer} [count=1]
* @return {any[]} The modified array.
*/
remove: function (array, startIndex, count)
{
var length = array.length;
if (startIndex >= length || count === 0) { return; }
if (count == null) { count = 1; }
var newLength = length - count;
for (var i = startIndex; i < newLength; ++i)
{
array[i] = array[i + count];
}
array.length = newLength;
},
/**
* A standard Fisher-Yates Array shuffle implementation which modifies the array in place.
*
* @method Phaser.ArrayUtils.shuffle
* @param {any[]} array - The array to shuffle.
* @return {any[]} The original array, now shuffled.
*/
shuffle: function (array)
{
for (var i = array.length - 1; i > 0; i--)
{
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
},
/**
* Transposes the elements of the given matrix (array of arrays).
*
* @method Phaser.ArrayUtils.transposeMatrix
* @param {Array} array - The matrix to transpose.
* @return {Array} A new transposed matrix
*/
transposeMatrix: function (array)
{
var sourceRowCount = array.length;
var sourceColCount = array[0].length;
var result = new Array(sourceColCount);
for (var i = 0; i < sourceColCount; i++)
{
result[i] = new Array(sourceRowCount);
for (var j = sourceRowCount - 1; j > -1; j--)
{
result[i][j] = array[j][i];
}
}
return result;
},
/**
* Rotates the given matrix (array of arrays).
*
* Based on the routine from {@link http://jsfiddle.net/MrPolywhirl/NH42z/}.
*
* @method Phaser.ArrayUtils.rotateMatrix
* @param {Array} matrix - The array to rotate; this matrix _may_ be altered.
* @param {number|string} direction - The amount to rotate: the rotation in degrees (90, -90, 270, -270, 180) or a string command ('rotateLeft', 'rotateRight' or 'rotate180').
* @return {Array} The rotated matrix. The source matrix should be discarded for the returned matrix.
*/
rotateMatrix: function (matrix, direction)
{
if (typeof direction !== 'string')
{
direction = ((direction % 360) + 360) % 360;
}
if (direction === 90 || direction === -270 || direction === 'rotateLeft')
{
matrix = Phaser.ArrayUtils.transposeMatrix(matrix);
matrix = matrix.reverse();
}
else if (direction === -90 || direction === 270 || direction === 'rotateRight')
{
matrix = matrix.reverse();
matrix = Phaser.ArrayUtils.transposeMatrix(matrix);
}
else if (Math.abs(direction) === 180 || direction === 'rotate180')
{
for (var i = 0; i < matrix.length; i++)
{
matrix[i].reverse();
}
matrix = matrix.reverse();
}
return matrix;
},
/**
* Snaps a value to the nearest value in a sorted numeric array.
* The result will always be in the range `[first_value, last_value]`.
*
* @method Phaser.ArrayUtils.findClosest
* @param {number} value - The search value
* @param {number[]} arr - The input array which _must_ be sorted.
* @return {number} The nearest value found.
*/
findClosest: function (value, arr)
{
if (!arr.length)
{
return NaN;
}
else if (arr.length === 1 || value < arr[0])
{
return arr[0];
}
var i = 1;
while (arr[i] < value)
{
i++;
}
var low = arr[i - 1];
var high = (i < arr.length) ? arr[i] : Number.POSITIVE_INFINITY;
return ((high - value) <= (value - low)) ? high : low;
},
/**
* Moves the element from the end of the array to the start, shifting all items in the process.
* The "rotation" happens to the right.
*
* Before: `[ A, B, C, D, E, F ]`
* After: `[ F, A, B, C, D, E ]`
*
* See also Phaser.ArrayUtils.rotateLeft.
*
* @method Phaser.ArrayUtils.rotateRight
* @param {any[]} array - The array to rotate. The array is modified.
* @return {any} The shifted value.
*/
rotateRight: function (array)
{
var s = array.pop();
array.unshift(s);
return s;
},
/**
* Moves the element from the start of the array to the end, shifting all items in the process.
* The "rotation" happens to the left.
*
* Before: `[ A, B, C, D, E, F ]`
* After: `[ B, C, D, E, F, A ]`
*
* See also Phaser.ArrayUtils.rotateRight
*
* @method Phaser.ArrayUtils.rotateLeft
* @param {any[]} array - The array to rotate. The array is modified.
* @return {any} The rotated value.
*/
rotateLeft: function (array)
{
var s = array.shift();
array.push(s);
return s;
},
/**
* Create an array representing the inclusive range of numbers (usually integers) in `[start, end]` (or `[0, start]`, if `end` is omitted).
* This is equivalent to `numberArrayStep(start, 1 + end, 1)`.
*
* When exactly one argument is passed, it's used as `end` and 0 is used as `start`. The length of the result is (1 + end).
*
* ##### Examples
*
* ```javascript
* numberArray(3); // -> [0, 1, 2, 3]
* numberArray(0, 3); // -> [0, 1, 2, 3]
* numberArray(1, 3); // -> [1, 2, 3]
* ```
*
* @method Phaser.ArrayUtils.numberArray
* @param {number} start - The minimum value the array starts with.
* @param {number} [end] - The maximum value the array contains.
* @return {number[]} The array of number values.
*/
numberArray: function (start, end)
{
if (end === undefined || end === null)
{
end = start;
start = 0;
}
var result = [];
for (var i = start; i <= end; i++)
{
result.push(i);
}
return result;
},
/**
* Create an array of numbers (positive and/or negative) progressing from `start`
* up to but not including `end` by advancing by `step`.
*
* If `start` is less than `end` a zero-length range is created unless a negative `step` is specified.
*
* Certain values for `start` and `end` (eg. NaN/undefined/null) are currently coerced to 0;
* for forward compatibility make sure to pass in actual numbers.
*
* @method Phaser.ArrayUtils.numberArrayStep
* @param {number} start - The start of the range.
* @param {number} [end] - The end of the range.
* @param {number} [step=1] - The value to increment or decrement by.
* @returns {Array} Returns the new array of numbers.
* @example
* Phaser.ArrayUtils.numberArrayStep(4);
* // => [0, 1, 2, 3]
*
* Phaser.ArrayUtils.numberArrayStep(1, 5);
* // => [1, 2, 3, 4]
*
* Phaser.ArrayUtils.numberArrayStep(0, 20, 5);
* // => [0, 5, 10, 15]
*
* Phaser.ArrayUtils.numberArrayStep(0, -4, -1);
* // => [0, -1, -2, -3]
*
* Phaser.ArrayUtils.numberArrayStep(1, 4, 0);
* // => [1, 1, 1]
*
* Phaser.ArrayUtils.numberArrayStep(0);
* // => []
*/
numberArrayStep: function (start, end, step)
{
if (start === undefined || start === null) { start = 0; }
if (end === undefined || end === null)
{
end = start;
start = 0;
}
if (step === undefined) { step = 1; }
var result = [];
var total = Math.max(Phaser.Math.roundAwayFromZero((end - start) / (step || 1)), 0);
for (var i = 0; i < total; i++)
{
result.push(start);
start += step;
}
return result;
}
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A basic Linked List data structure.
*
* This implementation _modifies_ the `prev` and `next` properties of each item added:
* - The `prev` and `next` properties must be writable and should not be used for any other purpose.
* - Items _cannot_ be added to multiple LinkedLists at the same time.
* - Only objects can be added.
*
* @class Phaser.LinkedList
* @constructor
*/
Phaser.LinkedList = function ()
{
/**
* Next element in the list.
* @property {object} next
* @default
*/
this.next = null;
/**
* Previous element in the list.
* @property {object} prev
* @default
*/
this.prev = null;
/**
* First element in the list.
* @property {object} first
* @default
*/
this.first = null;
/**
* Last element in the list.
* @property {object} last
* @default
*/
this.last = null;
/**
* Number of elements in the list.
* @property {integer} total
* @default
*/
this.total = 0;
};
Phaser.LinkedList.prototype = {
/**
* Adds a new element to this linked list.
*
* @method Phaser.LinkedList#add
* @param {object} item - The element to add to this list. Can be a Phaser.Sprite or any other object you need to quickly iterate through.
* @return {object} The item that was added.
*/
add: function (item)
{
// If the list is empty
if (this.total === 0 && this.first === null && this.last === null)
{
this.first = item;
this.last = item;
this.next = item;
item.prev = this;
this.total++;
return item;
}
// Gets appended to the end of the list, regardless of anything, and it won't have any children of its own (non-nested list)
this.last.next = item;
item.prev = this.last;
this.last = item;
this.total++;
return item;
},
/**
* Resets the first, last, next and previous node pointers in this list.
*
* @method Phaser.LinkedList#reset
*/
reset: function ()
{
this.first = null;
this.last = null;
this.next = null;
this.prev = null;
this.total = 0;
},
/**
* Removes the given element from this linked list if it exists.
*
* @method Phaser.LinkedList#remove
* @param {object} item - The item to be removed from the list.
*/
remove: function (item)
{
if (this.total === 1)
{
this.reset();
item.next = item.prev = null;
return;
}
if (item === this.first)
{
// It was 'first', make 'first' point to first.next
this.first = this.first.next;
}
else if (item === this.last)
{
// It was 'last', make 'last' point to last.prev
this.last = this.last.prev;
}
if (item.prev)
{
// make item.prev.next point to childs.next instead of item
item.prev.next = item.next;
}
if (item.next)
{
// make item.next.prev point to item.prev instead of item
item.next.prev = item.prev;
}
item.next = item.prev = null;
if (this.first === null)
{
this.last = null;
}
this.total--;
},
/**
* Calls a function on all members of this list, using the member as the context for the callback.
* The function must exist on the member.
*
* @method Phaser.LinkedList#callAll
* @param {function} callback - The function to call.
*/
callAll: function (callback)
{
if (!this.first || !this.last)
{
return;
}
var entity = this.first;
do
{
if (entity && entity[callback])
{
entity[callback].call(entity);
}
entity = entity.next;
}
while (entity !== this.last.next);
}
};
Phaser.LinkedList.prototype.constructor = Phaser.LinkedList;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Phaser.Create class is a collection of smaller helper methods that allow you to generate game content
* quickly and easily, without the need for any external files. You can create textures for sprites and in
* coming releases we'll add dynamic sound effect generation support as well (like sfxr).
*
* Access this via `Game.create` (`this.game.create` from within a State object).
*
* @class Phaser.Create
* @constructor
* @param {Phaser.Game} game - Game reference to the currently running game.
*/
Phaser.Create = function (game)
{
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
/**
* @property {Phaser.BitmapData} bmd - The internal BitmapData Create uses to generate textures from.
*/
this.bmd = null;
/**
* @property {HTMLCanvasElement} canvas - The canvas the BitmapData uses.
*/
this.canvas = null;
/**
* @property {CanvasRenderingContext2D} context - The 2d context of the canvas.
*/
this.ctx = null;
/**
* @property {array} palettes - A range of 16 color palettes for use with sprite generation.
*/
this.palettes = [
{ 0: '#000', 1: '#9D9D9D', 2: '#FFF', 3: '#BE2633', 4: '#E06F8B', 5: '#493C2B', 6: '#A46422', 7: '#EB8931', 8: '#F7E26B', 9: '#2F484E', A: '#44891A', B: '#A3CE27', C: '#1B2632', D: '#005784', E: '#31A2F2', F: '#B2DCEF' },
{ 0: '#000', 1: '#191028', 2: '#46af45', 3: '#a1d685', 4: '#453e78', 5: '#7664fe', 6: '#833129', 7: '#9ec2e8', 8: '#dc534b', 9: '#e18d79', A: '#d6b97b', B: '#e9d8a1', C: '#216c4b', D: '#d365c8', E: '#afaab9', F: '#f5f4eb' },
{ 0: '#000', 1: '#2234d1', 2: '#0c7e45', 3: '#44aacc', 4: '#8a3622', 5: '#5c2e78', 6: '#aa5c3d', 7: '#b5b5b5', 8: '#5e606e', 9: '#4c81fb', A: '#6cd947', B: '#7be2f9', C: '#eb8a60', D: '#e23d69', E: '#ffd93f', F: '#fff' },
{ 0: '#000', 1: '#fff', 2: '#8b4131', 3: '#7bbdc5', 4: '#8b41ac', 5: '#6aac41', 6: '#3931a4', 7: '#d5de73', 8: '#945a20', 9: '#5a4100', A: '#bd736a', B: '#525252', C: '#838383', D: '#acee8b', E: '#7b73de', F: '#acacac' },
{ 0: '#000', 1: '#191028', 2: '#46af45', 3: '#a1d685', 4: '#453e78', 5: '#7664fe', 6: '#833129', 7: '#9ec2e8', 8: '#dc534b', 9: '#e18d79', A: '#d6b97b', B: '#e9d8a1', C: '#216c4b', D: '#d365c8', E: '#afaab9', F: '#fff' }
];
};
/**
* A 16 color palette by [Arne](http://androidarts.com/palette/16pal.htm)
* @constant
* @type {number}
*/
Phaser.Create.PALETTE_ARNE = 0;
/**
* A 16 color JMP inspired palette.
* @constant
* @type {number}
*/
Phaser.Create.PALETTE_JMP = 1;
/**
* A 16 color CGA inspired palette.
* @constant
* @type {number}
*/
Phaser.Create.PALETTE_CGA = 2;
/**
* A 16 color C64 inspired palette.
* @constant
* @type {number}
*/
Phaser.Create.PALETTE_C64 = 3;
/**
* A 16 color palette inspired by Japanese computers like the MSX.
* @constant
* @type {number}
*/
Phaser.Create.PALETTE_JAPANESE_MACHINE = 4;
Phaser.Create.prototype = {
/**
* Generates a new PIXI.Texture from the given data, which can be applied to a Sprite.
*
* This allows you to create game graphics quickly and easily, with no external files but that use actual proper images
* rather than Phaser.Graphics objects, which are expensive to render and limited in scope.
*
* Each element of the array is a string holding the pixel color values, as mapped to one of the Phaser.Create PALETTE consts.
*
* For example:
*
* `var data = [
* ' 333 ',
* ' 777 ',
* 'E333E',
* ' 333 ',
* ' 3 3 '
* ];`
*
* `game.create.texture('bob', data);`
*
* The above will create a new texture called `bob`, which will look like a little man wearing a hat. You can then use it
* for sprites the same way you use any other texture: `game.add.sprite(0, 0, 'bob');`
*
* Use {@link Phaser.Loader#imageFromTexture} to preload an image of the same.
*
* @method Phaser.Create#texture
* @param {string} key - The key used to store this texture in the Phaser Cache.
* @param {array} data - An array of pixel data.
* @param {integer} [pixelWidth=8] - The width of each pixel.
* @param {integer} [pixelHeight=8] - The height of each pixel.
* @param {integer} [palette=0] - The palette to use when rendering the texture. One of the Phaser.Create.PALETTE consts.
* @param {boolean} [generateTexture=true] - When false, a new BitmapData object is returned instead.
* @param {function} [callback] - A function to execute once the texture is generated. It will be passed the newly generated texture.
* @param {any} [callbackContext] - The context in which to invoke the callback.
* @return {?PIXI.Texture|Phaser.BitmapData} The newly generated texture, or a new BitmapData object if `generateTexture` is false, or `null` if a callback was passed and the texture isn't available yet.
*/
texture: function (key, data, pixelWidth, pixelHeight, palette, generateTexture, callback, callbackContext)
{
if (pixelWidth === undefined) { pixelWidth = 8; }
if (pixelHeight === undefined) { pixelHeight = pixelWidth; }
if (palette === undefined) { palette = 0; }
if (generateTexture === undefined) { generateTexture = true; }
var w = data[0].length * pixelWidth;
var h = data.length * pixelHeight;
// No bmd? Let's make one
if (this.bmd === null)
{
this.bmd = this.game.make.bitmapData();
this.canvas = this.bmd.canvas;
this.ctx = this.bmd.context;
}
this.bmd.resize(w, h);
this.bmd.clear();
// Draw it
for (var y = 0; y < data.length; y++)
{
var row = data[y];
for (var x = 0; x < row.length; x++)
{
var d = row[x];
if (d !== '.' && d !== ' ')
{
this.ctx.fillStyle = this.palettes[palette][d];
this.ctx.fillRect(x * pixelWidth, y * pixelHeight, pixelWidth, pixelHeight);
}
}
}
return generateTexture ?
this.bmd.generateTexture(key, callback, callbackContext) :
this.copy();
},
/**
* Creates a grid texture based on the given dimensions.
*
* Use {@link Phaser.Loader#imageFromGrid} to preload an image of the same.
*
* @method Phaser.Create#grid
* @param {string} key - The key used to store this texture in the Phaser Cache.
* @param {integer} width - The width of the grid in pixels.
* @param {integer} height - The height of the grid in pixels.
* @param {integer} cellWidth - The width of the grid cells in pixels.
* @param {integer} cellHeight - The height of the grid cells in pixels.
* @param {string} color - The color to draw the grid lines in. Should be a Canvas supported color string like `#ff5500` or `rgba(200,50,3,0.5)`.
* @param {boolean} [generateTexture=true] - When false, a new BitmapData object is returned instead.
* @param {function} [callback] - A function to execute once the texture is generated. It will be passed the newly generated texture.
* @param {any} [callbackContext] - The context in which to invoke the callback.
* @return {?PIXI.Texture|Phaser.BitmapData} The newly generated texture, or a new BitmapData object if `generateTexture` is false, or `null` if a callback was passed and the texture isn't available yet.
*/
grid: function (key, width, height, cellWidth, cellHeight, color, generateTexture, callback, callbackContext)
{
if (generateTexture === undefined) { generateTexture = true; }
// No bmd? Let's make one
if (this.bmd === null)
{
this.bmd = this.game.make.bitmapData();
this.canvas = this.bmd.canvas;
this.ctx = this.bmd.context;
}
this.bmd.resize(width, height);
this.ctx.fillStyle = color;
for (var y = 0; y < height; y += cellHeight)
{
this.ctx.fillRect(0, y, width, 1);
}
for (var x = 0; x < width; x += cellWidth)
{
this.ctx.fillRect(x, 0, 1, height);
}
return generateTexture ?
this.bmd.generateTexture(key, callback, callbackContext) :
this.copy();
},
/**
* Copies the contents of {@link bmd Create's canvas} to the given BitmapData object, or a new BitmapData object.
*
* @param {Phaser.BitmapData} [dest] - The BitmapData receiving the copied image.
* @param {number} [x=0] - The x coordinate to translate to before drawing.
* @param {number} [y=0] - The y coordinate to translate to before drawing.
* @param {number} [width] - The new width of the Sprite being copied.
* @param {number} [height] - The new height of the Sprite being copied.
* @param {string} [blendMode=null] - The composite blend mode that will be used when drawing. The default is no blend mode at all. This is a Canvas globalCompositeOperation value such as 'lighter' or 'xor'.
* @param {boolean} [roundPx=false] - Should the x and y values be rounded to integers before drawing? This prevents anti-aliasing in some instances.
* @return {Phaser.BitmapData} - The `dest` argument (if passed), or a new BitmapData object
*/
copy: function (dest, x, y, width, height, blendMode, roundPx)
{
if (dest == null) { dest = this.game.make.bitmapData(); }
dest.resize(this.bmd.width, this.bmd.height);
return dest.draw(this.bmd, x, y, width, height, blendMode, roundPx);
}
};
Phaser.Create.prototype.constructor = Phaser.Create;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* WARNING: This is an EXPERIMENTAL class. The API will change significantly in the coming versions and is incomplete.
* Please try to avoid using in production games with a long time to build.
* This is also why the documentation is incomplete.
*
* FlexGrid is a a responsive grid manager that works in conjunction with the ScaleManager RESIZE scaling mode and FlexLayers
* to provide for game object positioning in a responsive manner.
*
* @class Phaser.FlexGrid
* @constructor
* @param {Phaser.ScaleManager} manager - The ScaleManager.
* @param {number} width - The width of the game.
* @param {number} height - The height of the game.
*/
Phaser.FlexGrid = function (manager, width, height)
{
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = manager.game;
/**
* @property {Phaser.ScaleManager} manager - A reference to the ScaleManager.
*/
this.manager = manager;
// The perfect dimensions on which everything else is based
this.width = width;
this.height = height;
this.boundsCustom = new Phaser.Rectangle(0, 0, width, height);
this.boundsFluid = new Phaser.Rectangle(0, 0, width, height);
this.boundsFull = new Phaser.Rectangle(0, 0, width, height);
this.boundsNone = new Phaser.Rectangle(0, 0, width, height);
/**
* @property {Phaser.Point} position -
* @readonly
*/
this.positionCustom = new Phaser.Point(0, 0);
this.positionFluid = new Phaser.Point(0, 0);
this.positionFull = new Phaser.Point(0, 0);
this.positionNone = new Phaser.Point(0, 0);
/**
* @property {Phaser.Point} scaleFactor - The scale factor based on the game dimensions vs. the scaled dimensions.
* @readonly
*/
this.scaleCustom = new Phaser.Point(1, 1);
this.scaleFluid = new Phaser.Point(1, 1);
this.scaleFluidInversed = new Phaser.Point(1, 1);
this.scaleFull = new Phaser.Point(1, 1);
this.scaleNone = new Phaser.Point(1, 1);
this.customWidth = 0;
this.customHeight = 0;
this.customOffsetX = 0;
this.customOffsetY = 0;
this.ratioH = width / height;
this.ratioV = height / width;
this.multiplier = 0;
this.layers = [];
};
Phaser.FlexGrid.prototype = {
/**
* Sets the core game size. This resets the w/h parameters and bounds.
*
* @method Phaser.FlexGrid#setSize
* @param {number} width - The new dimensions.
* @param {number} height - The new dimensions.
*/
setSize: function (width, height)
{
// These are locked and don't change until setSize is called again
this.width = width;
this.height = height;
this.ratioH = width / height;
this.ratioV = height / width;
this.scaleNone = new Phaser.Point(1, 1);
this.boundsNone.width = this.width;
this.boundsNone.height = this.height;
this.refresh();
},
// Need ability to create your own layers with custom scaling, etc.
/**
* A custom layer is centered on the game and maintains its aspect ratio as it scales up and down.
*
* @method Phaser.FlexGrid#createCustomLayer
* @param {number} width - Width of this layer in pixels.
* @param {number} height - Height of this layer in pixels.
* @param {PIXI.DisplayObject[]} [children] - An array of children that are used to populate the FlexLayer.
* @return {Phaser.FlexLayer} The Layer object.
*/
createCustomLayer: function (width, height, children, addToWorld)
{
if (addToWorld === undefined) { addToWorld = true; }
this.customWidth = width;
this.customHeight = height;
this.boundsCustom.width = width;
this.boundsCustom.height = height;
var layer = new Phaser.FlexLayer(this, this.positionCustom, this.boundsCustom, this.scaleCustom);
if (addToWorld)
{
this.game.world.add(layer);
}
this.layers.push(layer);
if (children)
{
layer.addMultiple(children);
}
return layer;
},
/**
* A fluid layer is centered on the game and maintains its aspect ratio as it scales up and down.
*
* @method Phaser.FlexGrid#createFluidLayer
* @param {array} [children] - An array of children that are used to populate the FlexLayer.
* @return {Phaser.FlexLayer} The Layer object.
*/
createFluidLayer: function (children, addToWorld)
{
if (addToWorld === undefined) { addToWorld = true; }
var layer = new Phaser.FlexLayer(this, this.positionFluid, this.boundsFluid, this.scaleFluid);
if (addToWorld)
{
this.game.world.add(layer);
}
this.layers.push(layer);
if (children)
{
layer.addMultiple(children);
}
return layer;
},
/**
* A full layer is placed at 0,0 and extends to the full size of the game. Children are scaled according to the fluid ratios.
*
* @method Phaser.FlexGrid#createFullLayer
* @param {array} [children] - An array of children that are used to populate the FlexLayer.
* @return {Phaser.FlexLayer} The Layer object.
*/
createFullLayer: function (children)
{
var layer = new Phaser.FlexLayer(this, this.positionFull, this.boundsFull, this.scaleFluid);
this.game.world.add(layer);
this.layers.push(layer);
if (typeof children !== 'undefined')
{
layer.addMultiple(children);
}
return layer;
},
/**
* A fixed layer is centered on the game and is the size of the required dimensions and is never scaled.
*
* @method Phaser.FlexGrid#createFixedLayer
* @param {PIXI.DisplayObject[]} [children] - An array of children that are used to populate the FlexLayer.
* @return {Phaser.FlexLayer} The Layer object.
*/
createFixedLayer: function (children)
{
var layer = new Phaser.FlexLayer(this, this.positionNone, this.boundsNone, this.scaleNone);
this.game.world.add(layer);
this.layers.push(layer);
if (typeof children !== 'undefined')
{
layer.addMultiple(children);
}
return layer;
},
/**
* Resets the layer children references
*
* @method Phaser.FlexGrid#reset
*/
reset: function ()
{
var i = this.layers.length;
while (i--)
{
if (!this.layers[i].persist)
{
// Remove references to this class
this.layers[i].position = null;
this.layers[i].scale = null;
this.layers.slice(i, 1);
}
}
},
/**
* Called when the game container changes dimensions.
*
* @method Phaser.FlexGrid#onResize
* @param {number} width - The new width of the game container.
* @param {number} height - The new height of the game container.
*/
onResize: function (width, height)
{
this.ratioH = width / height;
this.ratioV = height / width;
this.refresh(width, height);
},
/**
* Updates all internal vars such as the bounds and scale values.
*
* @method Phaser.FlexGrid#refresh
*/
refresh: function ()
{
this.multiplier = Math.min((this.manager.height / this.height), (this.manager.width / this.width));
this.boundsFluid.width = Math.round(this.width * this.multiplier);
this.boundsFluid.height = Math.round(this.height * this.multiplier);
this.scaleFluid.set(this.boundsFluid.width / this.width, this.boundsFluid.height / this.height);
this.scaleFluidInversed.set(this.width / this.boundsFluid.width, this.height / this.boundsFluid.height);
this.scaleFull.set(this.boundsFull.width / this.width, this.boundsFull.height / this.height);
this.boundsFull.width = Math.round(this.manager.width * this.scaleFluidInversed.x);
this.boundsFull.height = Math.round(this.manager.height * this.scaleFluidInversed.y);
this.boundsFluid.centerOn(this.manager.bounds.centerX, this.manager.bounds.centerY);
this.boundsNone.centerOn(this.manager.bounds.centerX, this.manager.bounds.centerY);
this.positionFluid.set(this.boundsFluid.x, this.boundsFluid.y);
this.positionNone.set(this.boundsNone.x, this.boundsNone.y);
},
/**
* Fits a sprites width to the bounds.
*
* @method Phaser.FlexGrid#fitSprite
* @param {Phaser.Sprite} sprite - The Sprite to fit.
*/
fitSprite: function (sprite)
{
this.manager.scaleSprite(sprite);
sprite.x = this.manager.bounds.centerX;
sprite.y = this.manager.bounds.centerY;
},
/**
* Call in the render function to output the bounds rects.
*
* @method Phaser.FlexGrid#debug
*/
debug: function ()
{
// for (var i = 0; i < this.layers.length; i++)
// {
// this.layers[i].debug();
// }
// this.game.debug.text(this.boundsFull.width + ' x ' + this.boundsFull.height, this.boundsFull.x + 4, this.boundsFull.y + 16);
// this.game.debug.geom(this.boundsFull, 'rgba(0,0,255,0.9', false);
this.game.debug.text(this.boundsFluid.width + ' x ' + this.boundsFluid.height, this.boundsFluid.x + 4, this.boundsFluid.y + 16);
this.game.debug.geom(this.boundsFluid, 'rgba(255,0,0,0.9', false);
// this.game.debug.text(this.boundsNone.width + ' x ' + this.boundsNone.height, this.boundsNone.x + 4, this.boundsNone.y + 16);
// this.game.debug.geom(this.boundsNone, 'rgba(0,255,0,0.9', false);
// this.game.debug.text(this.boundsCustom.width + ' x ' + this.boundsCustom.height, this.boundsCustom.x + 4, this.boundsCustom.y + 16);
// this.game.debug.geom(this.boundsCustom, 'rgba(255,255,0,0.9', false);
}
};
Phaser.FlexGrid.prototype.constructor = Phaser.FlexGrid;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* WARNING: This is an EXPERIMENTAL class. The API will change significantly in the coming versions and is incomplete.
* Please try to avoid using in production games with a long time to build.
* This is also why the documentation is incomplete.
*
* A responsive grid layer.
*
* @class Phaser.FlexLayer
* @extends Phaser.Group
* @constructor
* @param {Phaser.FlexGrid} manager - The FlexGrid that owns this FlexLayer.
* @param {Phaser.Point} position - A reference to the Point object used for positioning.
* @param {Phaser.Rectangle} bounds - A reference to the Rectangle used for the layer bounds.
* @param {Phaser.Point} scale - A reference to the Point object used for layer scaling.
*/
Phaser.FlexLayer = function (manager, position, bounds, scale)
{
Phaser.Group.call(this, manager.game, null, '__flexLayer' + manager.game.rnd.uuid(), false);
/**
* @property {Phaser.ScaleManager} scale - A reference to the ScaleManager.
*/
this.manager = manager.manager;
/**
* @property {Phaser.FlexGrid} grid - A reference to the FlexGrid that owns this layer.
*/
this.grid = manager;
/**
* Should the FlexLayer remain through a State swap?
*
* @type {boolean}
*/
this.persist = false;
/**
* @property {Phaser.Point} position
*/
this.position = position;
/**
* @property {Phaser.Rectangle} bounds
*/
this.bounds = bounds;
/**
* @property {Phaser.Point} scale
*/
this.scale = scale;
/**
* @property {Phaser.Point} topLeft
*/
this.topLeft = bounds.topLeft;
/**
* @property {Phaser.Point} topMiddle
*/
this.topMiddle = new Phaser.Point(bounds.halfWidth, 0);
/**
* @property {Phaser.Point} topRight
*/
this.topRight = bounds.topRight;
/**
* @property {Phaser.Point} bottomLeft
*/
this.bottomLeft = bounds.bottomLeft;
/**
* @property {Phaser.Point} bottomMiddle
*/
this.bottomMiddle = new Phaser.Point(bounds.halfWidth, bounds.bottom);
/**
* @property {Phaser.Point} bottomRight
*/
this.bottomRight = bounds.bottomRight;
};
Phaser.FlexLayer.prototype = Object.create(Phaser.Group.prototype);
Phaser.FlexLayer.prototype.constructor = Phaser.FlexLayer;
/**
* Resize.
*
* @method Phaser.FlexLayer#resize
*/
Phaser.FlexLayer.prototype.resize = function ()
{
};
/**
* Debug.
*
* @method Phaser.FlexLayer#debug
*/
Phaser.FlexLayer.prototype.debug = function ()
{
this.game.debug.text(this.bounds.width + ' x ' + this.bounds.height, this.bounds.x + 4, this.bounds.y + 16);
this.game.debug.geom(this.bounds, 'rgba(0,0,255,0.9', false);
this.game.debug.geom(this.topLeft, 'rgba(255,255,255,0.9');
this.game.debug.geom(this.topMiddle, 'rgba(255,255,255,0.9');
this.game.debug.geom(this.topRight, 'rgba(255,255,255,0.9');
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Phaser.Color class is a set of static methods that assist in color manipulation and conversion.
*
* @class Phaser.Color
*/
Phaser.Color = {
/**
* Red (0xff0000)
*
* @type number
* @constant
* @default
*/
RED: 0xff0000,
/**
* Orange (0xff9900)
*
* @type number
* @constant
* @default
*/
ORANGE: 0xff9900,
/**
* Yellow (0xffff00)
*
* @type number
* @constant
* @default
*/
YELLOW: 0xffff00,
/**
* Green (0x00ff00)
*
* @type number
* @constant
* @default
*/
GREEN: 0x00ff00,
/**
* Aqua (0x00ffff)
*
* @type number
* @constant
* @default
*/
AQUA: 0x00ffff,
/**
* Blue (0x0000ff)
*
* @type number
* @constant
* @default
*/
BLUE: 0x0000ff,
/**
* Violet/purple (0xff00ff)
*
* @type number
* @constant
* @default
*/
VIOLET: 0xff00ff,
/**
* White (0xffffff)
*
* @type number
* @constant
* @default
*/
WHITE: 0xffffff,
/**
* Black (0x000000)
*
* @type number
* @constant
* @default
*/
BLACK: 0,
/**
* Gray (0x666666)
*
* @type number
* @constant
* @default
*/
GRAY: 0x666666,
/**
* Packs the r, g, b, a components into a single integer, for use with Int32Array.
* If device is little endian then ABGR order is used. Otherwise RGBA order is used.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.Color.packPixel
* @static
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @param {number} a - The alpha color component, in the range 0 - 255.
* @return {number} The packed color as uint32
*/
packPixel: function (r, g, b, a)
{
if (Phaser.Device.LITTLE_ENDIAN)
{
return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0;
}
else
{
return ((r << 24) | (g << 16) | (b << 8) | a) >>> 0;
}
},
/**
* Unpacks the r, g, b, a components into the specified color object, or a new
* object, for use with Int32Array. If little endian, then ABGR order is used when
* unpacking, otherwise, RGBA order is used. The resulting color object has the
* `r, g, b, a` properties which are unrelated to endianness.
*
* Note that the integer is assumed to be packed in the correct endianness. On little-endian
* the format is 0xAABBGGRR and on big-endian the format is 0xRRGGBBAA. If you want a
* endian-independent method, use fromRGBA(rgba) and toRGBA(r, g, b, a).
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.Color.unpackPixel
* @static
* @param {number} rgba - The integer, packed in endian order by packPixel.
* @param {object} [out] - An object into which 3 properties will be created: r, g and b. If not provided a new object will be created.
* @param {boolean} [hsl=false] - Also convert the rgb values into hsl?
* @param {boolean} [hsv=false] - Also convert the rgb values into hsv?
* @return {object} An object with the red, green and blue values set in the r, g and b properties.
*/
unpackPixel: function (rgba, out, hsl, hsv)
{
if (out === undefined || out === null) { out = Phaser.Color.createColor(); }
if (hsl === undefined || hsl === null) { hsl = false; }
if (hsv === undefined || hsv === null) { hsv = false; }
if (Phaser.Device.LITTLE_ENDIAN)
{
out.a = ((rgba & 0xff000000) >>> 24);
out.b = ((rgba & 0x00ff0000) >>> 16);
out.g = ((rgba & 0x0000ff00) >>> 8);
out.r = ((rgba & 0x000000ff));
}
else
{
out.r = ((rgba & 0xff000000) >>> 24);
out.g = ((rgba & 0x00ff0000) >>> 16);
out.b = ((rgba & 0x0000ff00) >>> 8);
out.a = ((rgba & 0x000000ff));
}
out.color = rgba;
out.rgba = 'rgba(' + out.r + ',' + out.g + ',' + out.b + ',' + (out.a / 255) + ')';
if (hsl)
{
Phaser.Color.RGBtoHSL(out.r, out.g, out.b, out);
}
if (hsv)
{
Phaser.Color.RGBtoHSV(out.r, out.g, out.b, out);
}
return out;
},
/**
* A utility to convert an integer in 0xRRGGBBAA format to a color object.
* This does not rely on endianness.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.Color.fromRGBA
* @static
* @param {number} rgba - An RGBA hex
* @param {object} [out] - The object to use, optional.
* @return {object} A color object.
*/
fromRGBA: function (rgba, out)
{
if (!out)
{
out = Phaser.Color.createColor();
}
out.r = ((rgba & 0xff000000) >>> 24);
out.g = ((rgba & 0x00ff0000) >>> 16);
out.b = ((rgba & 0x0000ff00) >>> 8);
out.a = ((rgba & 0x000000ff));
out.rgba = 'rgba(' + out.r + ',' + out.g + ',' + out.b + ',' + out.a + ')';
return out;
},
/**
* A utility to convert RGBA components to a 32 bit integer in RRGGBBAA format.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.Color.toRGBA
* @static
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @param {number} a - The alpha color component, in the range 0 - 255.
* @return {number} A RGBA-packed 32 bit integer
*/
toRGBA: function (r, g, b, a)
{
return (r << 24) | (g << 16) | (b << 8) | a;
},
/**
* Converts RGBA components to a 32 bit integer in AABBGGRR format.
*
* @method Phaser.Color.toABGR
* @static
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @param {number} a - The alpha color component, in the range 0 - 255.
* @return {number} A RGBA-packed 32 bit integer
*/
toABGR: function (r, g, b, a)
{
return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0;
},
/**
* Converts a hex color value to an [R, G, B] array.
*
* @static
* @method Phaser.Color.hexToRGBArray
* @param {number} color - The color to convert to an RGB array. In the format 0xRRGGBB.
* @return {array} An array with element 0 containing the Red value, 1 containing Green, and 2 containing Blue.
*/
hexToRGBArray: function (color)
{
return [
(color >> 16 & 0xFF) / 255,
(color >> 8 & 0xFF) / 255,
(color & 0xFF) / 255
];
},
/**
* Converts an RGB color array, in the format: [R, G, B], to a hex color value.
*
* @static
* @method Phaser.Color.RGBArrayToHex
* @param {array} rgb - An array with element 0 containing the Red value, 1 containing Green, and 2 containing Blue.
* @return {number} The color value, in the format 0xRRGGBB.
*/
RGBArrayToHex: function (rgb)
{
return ((rgb[0] * 255 << 16) + (rgb[1] * 255 << 8) + rgb[2] * 255);
},
/**
* Converts an RGB color value to HSL (hue, saturation and lightness).
* Conversion forumla from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes RGB values are contained in the set [0, 255] and returns h, s and l in the set [0, 1].
* Based on code by Michael Jackson (https://github.com/mjijackson)
*
* @method Phaser.Color.RGBtoHSL
* @static
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @param {object} [out] - An object into which 3 properties will be created, h, s and l. If not provided a new object will be created.
* @return {object} An object with the hue, saturation and lightness values set in the h, s and l properties.
*/
RGBtoHSL: function (r, g, b, out)
{
if (!out)
{
out = Phaser.Color.createColor(r, g, b, 1);
}
r /= 255;
g /= 255;
b /= 255;
var min = Math.min(r, g, b);
var max = Math.max(r, g, b);
// achromatic by default
out.h = 0;
out.s = 0;
out.l = (max + min) / 2;
if (max !== min)
{
var d = max - min;
out.s = out.l > 0.5 ? d / (2 - max - min) : d / (max + min);
if (max === r)
{
out.h = (g - b) / d + (g < b ? 6 : 0);
}
else if (max === g)
{
out.h = (b - r) / d + 2;
}
else if (max === b)
{
out.h = (r - g) / d + 4;
}
out.h /= 6;
}
return out;
},
/**
* Converts an HSL (hue, saturation and lightness) color value to RGB.
* Conversion forumla from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes HSL values are contained in the set [0, 1] and returns r, g and b values in the set [0, 255].
* Based on code by Michael Jackson (https://github.com/mjijackson)
*
* @method Phaser.Color.HSLtoRGB
* @static
* @param {number} h - The hue, in the range 0 - 1.
* @param {number} s - The saturation, in the range 0 - 1.
* @param {number} l - The lightness, in the range 0 - 1.
* @param {object} [out] - An object into which 3 properties will be created: r, g and b. If not provided a new object will be created.
* @return {object} An object with the red, green and blue values set in the r, g and b properties.
*/
HSLtoRGB: function (h, s, l, out)
{
if (!out)
{
out = Phaser.Color.createColor(l, l, l);
}
else
{
// achromatic by default
out.r = l;
out.g = l;
out.b = l;
}
if (s !== 0)
{
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
out.r = Phaser.Color.hueToColor(p, q, h + 1 / 3);
out.g = Phaser.Color.hueToColor(p, q, h);
out.b = Phaser.Color.hueToColor(p, q, h - 1 / 3);
}
// out.r = (out.r * 255 | 0);
// out.g = (out.g * 255 | 0);
// out.b = (out.b * 255 | 0);
out.r = Math.floor((out.r * 255 | 0));
out.g = Math.floor((out.g * 255 | 0));
out.b = Math.floor((out.b * 255 | 0));
Phaser.Color.updateColor(out);
return out;
},
/**
* Converts an RGB color value to HSV (hue, saturation and value).
* Conversion forumla from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes RGB values are contained in the set [0, 255] and returns h, s and v in the set [0, 1].
* Based on code by Michael Jackson (https://github.com/mjijackson)
*
* @method Phaser.Color.RGBtoHSV
* @static
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @param {object} [out] - An object into which 3 properties will be created, h, s and v. If not provided a new object will be created.
* @return {object} An object with the hue, saturation and value set in the h, s and v properties.
*/
RGBtoHSV: function (r, g, b, out)
{
if (!out)
{
out = Phaser.Color.createColor(r, g, b, 255);
}
r /= 255;
g /= 255;
b /= 255;
var min = Math.min(r, g, b);
var max = Math.max(r, g, b);
var d = max - min;
// achromatic by default
out.h = 0;
out.s = max === 0 ? 0 : d / max;
out.v = max;
if (max !== min)
{
if (max === r)
{
out.h = (g - b) / d + (g < b ? 6 : 0);
}
else if (max === g)
{
out.h = (b - r) / d + 2;
}
else if (max === b)
{
out.h = (r - g) / d + 4;
}
out.h /= 6;
}
return out;
},
/**
* Converts an HSV (hue, saturation and value) color value to RGB.
* Conversion forumla from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes HSV values are contained in the set [0, 1] and returns r, g and b values in the set [0, 255].
* Based on code by Michael Jackson (https://github.com/mjijackson)
*
* @method Phaser.Color.HSVtoRGB
* @static
* @param {number} h - The hue, in the range 0 - 1.
* @param {number} s - The saturation, in the range 0 - 1.
* @param {number} v - The value, in the range 0 - 1.
* @param {object} [out] - An object into which 3 properties will be created: r, g and b. If not provided a new object will be created.
* @return {object} An object with the red, green and blue values set in the r, g and b properties.
*/
HSVtoRGB: function (h, s, v, out)
{
if (out === undefined) { out = Phaser.Color.createColor(0, 0, 0, 1, h, s, 0, v); }
var r, g, b;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch (i % 6)
{
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
out.r = Math.floor(r * 255);
out.g = Math.floor(g * 255);
out.b = Math.floor(b * 255);
Phaser.Color.updateColor(out);
return out;
},
/**
* Converts a hue to an RGB color.
* Based on code by Michael Jackson (https://github.com/mjijackson)
*
* @method Phaser.Color.hueToColor
* @static
* @param {number} p
* @param {number} q
* @param {number} t
* @return {number} The color component value.
*/
hueToColor: function (p, q, t)
{
if (t < 0)
{
t += 1;
}
if (t > 1)
{
t -= 1;
}
if (t < 1 / 6)
{
return p + (q - p) * 6 * t;
}
if (t < 1 / 2)
{
return q;
}
if (t < 2 / 3)
{
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
},
/**
* A utility function to create a lightweight 'color' object with the default components.
* Any components that are not specified will default to zero.
*
* This is useful when you want to use a shared color object for the getPixel and getPixelAt methods.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.Color.createColor
* @static
* @param {number} [r=0] - The red color component, in the range 0 - 255.
* @param {number} [g=0] - The green color component, in the range 0 - 255.
* @param {number} [b=0] - The blue color component, in the range 0 - 255.
* @param {number} [a=1] - The alpha color component, in the range 0 - 1.
* @param {number} [h=0] - The hue, in the range 0 - 1.
* @param {number} [s=0] - The saturation, in the range 0 - 1.
* @param {number} [l=0] - The lightness, in the range 0 - 1.
* @param {number} [v=0] - The value, in the range 0 - 1.
* @return {object} The resulting object with r, g, b, a properties and h, s, l and v.
*/
createColor: function (r, g, b, a, h, s, l, v)
{
var out = { r: r || 0, g: g || 0, b: b || 0, a: a || 1, h: h || 0, s: s || 0, l: l || 0, v: v || 0, color: 0, color32: 0, rgba: '' };
return Phaser.Color.updateColor(out);
},
/**
* Takes a color object and updates the rgba, color and color32 properties.
*
* @method Phaser.Color.updateColor
* @static
* @param {object} out - The color object to update.
* @returns {number} A native color value integer (format: 0xAARRGGBB).
*/
updateColor: function (out)
{
out.rgba = 'rgba(' + out.r.toFixed() + ',' + out.g.toFixed() + ',' + out.b.toFixed() + ',' + out.a.toString() + ')';
out.color = Phaser.Color.getColor(out.r, out.g, out.b);
out.color32 = Phaser.Color.getColor32(out.a * 255, out.r, out.g, out.b);
return out;
},
/**
* Given an alpha and 3 color values this will return an integer representation of it.
*
* @method Phaser.Color.getColor32
* @static
* @param {number} a - The alpha color component, in the range 0 - 255.
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @returns {number} A native color value integer (format: 0xAARRGGBB).
*/
getColor32: function (a, r, g, b)
{
return a << 24 | r << 16 | g << 8 | b;
},
/**
* Given 3 color values this will return an integer representation of it.
*
* @method Phaser.Color.getColor
* @static
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @returns {number} A native color value integer (format: 0xRRGGBB).
*/
getColor: function (r, g, b)
{
return r << 16 | g << 8 | b;
},
/**
* Converts the given color values into a string.
* If prefix was '#' it will be in the format `#RRGGBB` otherwise `0xAARRGGBB`.
*
* @method Phaser.Color.RGBtoString
* @static
* @param {number} r - The red color component, in the range 0 - 255.
* @param {number} g - The green color component, in the range 0 - 255.
* @param {number} b - The blue color component, in the range 0 - 255.
* @param {number} [a=255] - The alpha color component, in the range 0 - 255.
* @param {string} [prefix='#'] - The prefix used in the return string. If '#' it will return `#RRGGBB`, else `0xAARRGGBB`.
* @return {string} A string containing the color values. If prefix was '#' it will be in the format `#RRGGBB` otherwise `0xAARRGGBB`.
*/
RGBtoString: function (r, g, b, a, prefix)
{
if (a === undefined) { a = 255; }
if (prefix === undefined) { prefix = '#'; }
if (prefix === '#')
{
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
else
{
return '0x' + Phaser.Color.componentToHex(a) + Phaser.Color.componentToHex(r) + Phaser.Color.componentToHex(g) + Phaser.Color.componentToHex(b);
}
},
/**
* Converts a hex string into an integer color value.
*
* @method Phaser.Color.hexToRGB
* @static
* @param {string} hex - The hex string to convert. Can be in the short-hand format `#03f` or `#0033ff`.
* @return {number} The rgb color value in the format 0xAARRGGBB.
*/
hexToRGB: function (hex)
{
var rgb = Phaser.Color.hexToColor(hex);
if (rgb)
{
return Phaser.Color.getColor32(rgb.a, rgb.r, rgb.g, rgb.b);
}
},
/**
* Converts a hex string into a Phaser Color object.
*
* The hex string can supplied as `'#0033ff'` or the short-hand format of `'#03f'`; it can begin with an optional "#" or "0x", or be unprefixed.
*
* An alpha channel is _not_ supported.
*
* @method Phaser.Color.hexToColor
* @static
* @param {string} hex - The color string in a hex format.
* @param {object} [out] - An object into which 3 properties will be created or set: r, g and b. If not provided a new object will be created.
* @return {object} An object with the red, green and blue values set in the r, g and b properties.
*/
hexToColor: function (hex, out)
{
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
hex = hex.replace(/^(?:#|0x)?([a-f\d])([a-f\d])([a-f\d])$/i, function (m, r, g, b)
{
return r + r + g + g + b + b;
});
var result = (/^(?:#|0x)?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i).exec(hex);
if (result)
{
var r = parseInt(result[1], 16);
var g = parseInt(result[2], 16);
var b = parseInt(result[3], 16);
if (!out)
{
out = Phaser.Color.createColor(r, g, b);
}
else
{
out.r = r;
out.g = g;
out.b = b;
}
}
return out;
},
/**
* Converts a CSS 'web' string into a Phaser Color object.
*
* The web string can be in the format `'rgb(r,g,b)'` or `'rgba(r,g,b,a)'` where r/g/b are in the range [0..255] and a is in the range [0..1].
*
* @method Phaser.Color.webToColor
* @static
* @param {string} web - The color string in CSS 'web' format.
* @param {object} [out] - An object into which 4 properties will be created: r, g, b and a. If not provided a new object will be created.
* @return {object} An object with the red, green, blue and alpha values set in the r, g, b and a properties.
*/
webToColor: function (web, out)
{
if (!out)
{
out = Phaser.Color.createColor();
}
var result = (/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d+(?:\.\d+)?))?\s*\)$/).exec(web);
if (result)
{
out.r = ~~Number(result[1]);
out.g = ~~Number(result[2]);
out.b = ~~Number(result[3]);
out.a = result[4] !== undefined ? Number(result[4]) : 1;
Phaser.Color.updateColor(out);
}
return out;
},
/**
* Converts a value - a "hex" string, a "CSS 'web' string", or a number - into red, green, blue, and alpha components.
*
* The value can be a string (see `hexToColor` and `webToColor` for the supported formats) or a packed integer (see `getRGB`).
*
* An alpha channel is _not_ supported when specifying a hex string.
*
* @method Phaser.Color.valueToColor
* @static
* @param {string|number} value - The color expressed as a recognized string format or a packed integer.
* @param {object} [out] - The object to use for the output. If not provided a new object will be created.
* @return {object} The (`out`) object with the red, green, blue, and alpha values set as the r/g/b/a properties.
*/
valueToColor: function (value, out)
{
// The behavior is not consistent between hexToColor/webToColor on invalid input.
// This unifies both by returning a new object, but returning null may be better.
if (!out)
{
out = Phaser.Color.createColor();
}
if (typeof value === 'string')
{
if (value.indexOf('rgb') === 0)
{
return Phaser.Color.webToColor(value, out);
}
else
{
// `hexToColor` does not support alpha; match `createColor`.
out.a = 1;
return Phaser.Color.hexToColor(value, out);
}
}
else if (typeof value === 'number')
{
// `getRGB` does not take optional object to modify;
// alpha is also adjusted to match `createColor`.
var tempColor = Phaser.Color.getRGB(value);
out.r = tempColor.r;
out.g = tempColor.g;
out.b = tempColor.b;
out.a = tempColor.a / 255;
return out;
}
else
{
return out;
}
},
/**
* Return a string containing a hex representation of the given color component.
*
* @method Phaser.Color.componentToHex
* @static
* @param {number} color - The color channel to get the hex value for, must be a value between 0 and 255.
* @returns {string} A string of length 2 characters, i.e. 255 = ff, 100 = 64.
*/
componentToHex: function (color)
{
var hex = color.toString(16);
return (hex.length === 1) ? '0' + hex : hex;
},
/**
* Get HSV color wheel values in an array which will be 360 elements in size.
*
* @method Phaser.Color.HSVColorWheel
* @static
* @param {number} [s=1] - The saturation, in the range 0 - 1.
* @param {number} [v=1] - The value, in the range 0 - 1.
* @return {array} An array containing 360 elements corresponding to the HSV color wheel.
*/
HSVColorWheel: function (s, v)
{
if (s === undefined) { s = 1.0; }
if (v === undefined) { v = 1.0; }
var colors = [];
for (var c = 0; c <= 359; c++)
{
colors.push(Phaser.Color.HSVtoRGB(c / 359, s, v));
}
return colors;
},
/**
* Get HSL color wheel values in an array which will be 360 elements in size.
*
* @method Phaser.Color.HSLColorWheel
* @static
* @param {number} [s=0.5] - The saturation, in the range 0 - 1.
* @param {number} [l=0.5] - The lightness, in the range 0 - 1.
* @return {array} An array containing 360 elements corresponding to the HSL color wheel.
*/
HSLColorWheel: function (s, l)
{
if (s === undefined) { s = 0.5; }
if (l === undefined) { l = 0.5; }
var colors = [];
for (var c = 0; c <= 359; c++)
{
colors.push(Phaser.Color.HSLtoRGB(c / 359, s, l));
}
return colors;
},
/**
* Interpolates the two given colours based on the supplied step and currentStep properties.
*
* @method Phaser.Color.interpolateColor
* @static
* @param {number} color1 - The first color value.
* @param {number} color2 - The second color value.
* @param {number} steps - The number of steps to run the interpolation over.
* @param {number} currentStep - The currentStep value. If the interpolation will take 100 steps, a currentStep value of 50 would be half-way between the two.
* @param {number} [alpha] - The alpha of the returned color.
* @param {number} [colorSpace=0] - The color space to interpolate in. 0 = RGB, 1 = HSV.
* @returns {number} The interpolated color value.
*/
interpolateColor: function (color1, color2, steps, currentStep, alpha, colorSpace)
{
if (alpha === undefined) { alpha = 255; }
if (colorSpace === undefined) { colorSpace = 0; }
var src1 = Phaser.Color.getRGB(color1);
var src2 = Phaser.Color.getRGB(color2);
if (colorSpace === 0)
{
var r = (((src2.red - src1.red) * currentStep) / steps) + src1.red;
var g = (((src2.green - src1.green) * currentStep) / steps) + src1.green;
var b = (((src2.blue - src1.blue) * currentStep) / steps) + src1.blue;
}
if (colorSpace === 1)
{
var hsv1 = Phaser.Color.RGBtoHSV(src1.r, src1.g, src1.b);
var hsv2 = Phaser.Color.RGBtoHSV(src2.r, src2.g, src2.b);
var dh = hsv2.h - hsv1.h;
var h;
if (hsv1.h > hsv2.h)
{
var h3 = hsv2.h;
hsv2.h = hsv1.h;
hsv1.h = h3;
dh = -dh;
currentStep = steps - currentStep;
}
if (dh > 0.5)
{
hsv1.h = hsv1.h + 1;
h = (((hsv2.h - hsv1.h) * currentStep / steps) + hsv1.h) % 1;
}
if (dh <= 0.5)
{
h = ((hsv2.h - hsv1.h) * currentStep / steps) + hsv1.h;
}
var s = (((hsv2.s - hsv1.s) * currentStep) / steps) + hsv1.s;
var v = (((hsv2.v - hsv1.v) * currentStep) / steps) + hsv1.v;
var rgb = Phaser.Color.HSVtoRGB(h, s, v, rgb);
var r = rgb.r;
var g = rgb.g;
var b = rgb.b;
}
return Phaser.Color.getColor32(alpha, r, g, b);
},
/**
* Interpolates the two given colours based on the supplied step and currentStep properties.
*
* @method Phaser.Color.interpolateColorWithRGB
* @static
* @param {number} color - The first color value.
* @param {number} r - The red color value, between 0 and 0xFF (255).
* @param {number} g - The green color value, between 0 and 0xFF (255).
* @param {number} b - The blue color value, between 0 and 0xFF (255).
* @param {number} steps - The number of steps to run the interpolation over.
* @param {number} currentStep - The currentStep value. If the interpolation will take 100 steps, a currentStep value of 50 would be half-way between the two.
* @returns {number} The interpolated color value.
*/
interpolateColorWithRGB: function (color, r, g, b, steps, currentStep)
{
var src = Phaser.Color.getRGB(color);
var or = (((r - src.red) * currentStep) / steps) + src.red;
var og = (((g - src.green) * currentStep) / steps) + src.green;
var ob = (((b - src.blue) * currentStep) / steps) + src.blue;
return Phaser.Color.getColor(or, og, ob);
},
/**
* Interpolates the two given colours based on the supplied step and currentStep properties.
* @method Phaser.Color.interpolateRGB
* @static
* @param {number} r1 - The red color value, between 0 and 0xFF (255).
* @param {number} g1 - The green color value, between 0 and 0xFF (255).
* @param {number} b1 - The blue color value, between 0 and 0xFF (255).
* @param {number} r2 - The red color value, between 0 and 0xFF (255).
* @param {number} g2 - The green color value, between 0 and 0xFF (255).
* @param {number} b2 - The blue color value, between 0 and 0xFF (255).
* @param {number} steps - The number of steps to run the interpolation over.
* @param {number} currentStep - The currentStep value. If the interpolation will take 100 steps, a currentStep value of 50 would be half-way between the two.
* @returns {number} The interpolated color value.
*/
interpolateRGB: function (r1, g1, b1, r2, g2, b2, steps, currentStep)
{
var r = (((r2 - r1) * currentStep) / steps) + r1;
var g = (((g2 - g1) * currentStep) / steps) + g1;
var b = (((b2 - b1) * currentStep) / steps) + b1;
return Phaser.Color.getColor(r, g, b);
},
/**
* Calculates a linear (interpolation) value of two colors over t.
*
* This is a slightly simpler interface to {@link Phaser.Color.interpolateColor}.
*
* The arguments are similar to {@link Phaser.Math.linear}.
*
* @method Phaser.Color.linear
* @param {number} color1 - The first color value.
* @param {number} color2 - The second color value.
* @param {number} t - A value between 0 and 1.
* @return {number} The interpolated color value.
*/
linear: function (color1, color2, t)
{
return this.interpolateColor(color1, color2, 1, t);
},
/**
* Calculates a linear (interpolation) value of an array of colors over t.
*
* The arguments are similar to {@link Phaser.Math.linearInterpolation}.
*
* This can be used as a {@link Phaser.TweenData#interpolationFunction}.
*
* @method Phaser.Color.linearInterpolation
* @param {number[]} colors - The input array of color values to interpolate between.
* @param {number} t - The amount of interpolation, between 0 (start) and 1 (end).
* @return {number} The interpolated color value.
*/
linearInterpolation: function (colors, t)
{
var k = Phaser.Math.linear(0, colors.length - 1, t);
var color1 = colors[Math.floor(k)];
var color2 = colors[Math.ceil(k)];
return this.linear(color1, color2, k % 1);
},
/**
* Returns a random color value between black and white
* Set the min value to start each channel from the given offset.
* Set the max value to restrict the maximum color used per channel.
*
* @method Phaser.Color.getRandomColor
* @static
* @param {number} [min=0] - The lowest value to use for the color.
* @param {number} [max=255] - The highest value to use for the color.
* @param {number} [alpha=255] - The alpha value of the returning color (default 255 = fully opaque).
* @returns {number} 32-bit color value with alpha.
*/
getRandomColor: function (min, max, alpha)
{
if (min === undefined) { min = 0; }
if (max === undefined) { max = 255; }
if (alpha === undefined) { alpha = 255; }
// Sanity checks
if (max > 255 || min > max)
{
return Phaser.Color.getColor(255, 255, 255);
}
var red = min + Math.round(Math.random() * (max - min));
var green = min + Math.round(Math.random() * (max - min));
var blue = min + Math.round(Math.random() * (max - min));
return Phaser.Color.getColor32(alpha, red, green, blue);
},
/**
* Return the component parts of a color as an Object with the properties alpha, red, green, blue.
*
* Alpha will only be set if it exist in the given color (0xAARRGGBB)
*
* @method Phaser.Color.getRGB
* @static
* @param {number} color - Color in RGB (0xRRGGBB) or ARGB format (0xAARRGGBB).
* @returns {object} An Object with properties: alpha, red, green, blue (also r, g, b and a). Alpha will only be present if a color value > 16777215 was given.
*/
getRGB: function (color)
{
if (color > 16777215)
{
// The color value has an alpha component
return {
alpha: color >>> 24,
red: color >> 16 & 0xFF,
green: color >> 8 & 0xFF,
blue: color & 0xFF,
a: color >>> 24,
r: color >> 16 & 0xFF,
g: color >> 8 & 0xFF,
b: color & 0xFF
};
}
else
{
return {
alpha: 255,
red: color >> 16 & 0xFF,
green: color >> 8 & 0xFF,
blue: color & 0xFF,
a: 255,
r: color >> 16 & 0xFF,
g: color >> 8 & 0xFF,
b: color & 0xFF
};
}
},
/**
* Returns a CSS friendly string value from the given color.
*
* @method Phaser.Color.getWebRGB
* @static
* @param {number|Object} color - Color in RGB (0xRRGGBB), ARGB format (0xAARRGGBB) or an Object with r, g, b, a properties.
* @returns {string} A string in the format: 'rgba(r,g,b,a)'
*/
getWebRGB: function (color)
{
if (typeof color === 'object')
{
return 'rgba(' + color.r.toString() + ',' + color.g.toString() + ',' + color.b.toString() + ',' + (color.a / 255).toString() + ')';
}
else
{
var rgb = Phaser.Color.getRGB(color);
return 'rgba(' + rgb.r.toString() + ',' + rgb.g.toString() + ',' + rgb.b.toString() + ',' + (rgb.a / 255).toString() + ')';
}
},
/**
* Given a native color value (in the format 0xAARRGGBB) this will return the Alpha component, as a value between 0 and 255.
*
* @method Phaser.Color.getAlpha
* @static
* @param {number} color - In the format 0xAARRGGBB.
* @returns {number} The Alpha component of the color, will be between 0 and 1 (0 being no Alpha (opaque), 1 full Alpha (transparent)).
*/
getAlpha: function (color)
{
return color >>> 24;
},
/**
* Given a native color value (in the format 0xAARRGGBB) this will return the Alpha component as a value between 0 and 1.
*
* @method Phaser.Color.getAlphaFloat
* @static
* @param {number} color - In the format 0xAARRGGBB.
* @returns {number} The Alpha component of the color, will be between 0 and 1 (0 being no Alpha (opaque), 1 full Alpha (transparent)).
*/
getAlphaFloat: function (color)
{
return (color >>> 24) / 255;
},
/**
* Given a native color value (in the format 0xAARRGGBB) this will return the Red component, as a value between 0 and 255.
*
* @method Phaser.Color.getRed
* @static
* @param {number} color In the format 0xAARRGGBB.
* @returns {number} The Red component of the color, will be between 0 and 255 (0 being no color, 255 full Red).
*/
getRed: function (color)
{
return color >> 16 & 0xFF;
},
/**
* Given a native color value (in the format 0xAARRGGBB) this will return the Green component, as a value between 0 and 255.
*
* @method Phaser.Color.getGreen
* @static
* @param {number} color - In the format 0xAARRGGBB.
* @returns {number} The Green component of the color, will be between 0 and 255 (0 being no color, 255 full Green).
*/
getGreen: function (color)
{
return color >> 8 & 0xFF;
},
/**
* Given a native color value (in the format 0xAARRGGBB) this will return the Blue component, as a value between 0 and 255.
*
* @method Phaser.Color.getBlue
* @static
* @param {number} color - In the format 0xAARRGGBB.
* @returns {number} The Blue component of the color, will be between 0 and 255 (0 being no color, 255 full Blue).
*/
getBlue: function (color)
{
return color & 0xFF;
},
/**
* Blends the source color, ignoring the backdrop.
*
* @method Phaser.Color.blendNormal
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendNormal: function (a)
{
return a;
},
/**
* Selects the lighter of the backdrop and source colors.
*
* @method Phaser.Color.blendLighten
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendLighten: function (a, b)
{
return (b > a) ? b : a;
},
/**
* Selects the darker of the backdrop and source colors.
*
* @method Phaser.Color.blendDarken
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendDarken: function (a, b)
{
return (b > a) ? a : b;
},
/**
* Multiplies the backdrop and source color values.
* The result color is always at least as dark as either of the two constituent
* colors. Multiplying any color with black produces black;
* multiplying with white leaves the original color unchanged.
*
* @method Phaser.Color.blendMultiply
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendMultiply: function (a, b)
{
return (a * b) / 255;
},
/**
* Takes the average of the source and backdrop colors.
*
* @method Phaser.Color.blendAverage
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendAverage: function (a, b)
{
return (a + b) / 2;
},
/**
* Adds the source and backdrop colors together and returns the value, up to a maximum of 255.
*
* @method Phaser.Color.blendAdd
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendAdd: function (a, b)
{
return Math.min(255, a + b);
},
/**
* Combines the source and backdrop colors and returns their value minus 255.
*
* @method Phaser.Color.blendSubtract
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendSubtract: function (a, b)
{
return Math.max(0, a + b - 255);
},
/**
* Subtracts the darker of the two constituent colors from the lighter.
*
* Painting with white inverts the backdrop color; painting with black produces no change.
*
* @method Phaser.Color.blendDifference
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendDifference: function (a, b)
{
return Math.abs(a - b);
},
/**
* Negation blend mode.
*
* @method Phaser.Color.blendNegation
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendNegation: function (a, b)
{
return 255 - Math.abs(255 - a - b);
},
/**
* Multiplies the complements of the backdrop and source color values, then complements the result.
* The result color is always at least as light as either of the two constituent colors.
* Screening any color with white produces white; screening with black leaves the original color unchanged.
*
* @method Phaser.Color.blendScreen
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendScreen: function (a, b)
{
return 255 - (((255 - a) * (255 - b)) >> 8);
},
/**
* Produces an effect similar to that of the Difference mode, but lower in contrast.
* Painting with white inverts the backdrop color; painting with black produces no change.
*
* @method Phaser.Color.blendExclusion
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendExclusion: function (a, b)
{
return a + b - 2 * a * b / 255;
},
/**
* Multiplies or screens the colors, depending on the backdrop color.
* Source colors overlay the backdrop while preserving its highlights and shadows.
* The backdrop color is not replaced, but is mixed with the source color to reflect the lightness or darkness of the backdrop.
*
* @method Phaser.Color.blendOverlay
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendOverlay: function (a, b)
{
return b < 128 ? (2 * a * b / 255) : (255 - 2 * (255 - a) * (255 - b) / 255);
},
/**
* Darkens or lightens the colors, depending on the source color value.
*
* If the source color is lighter than 0.5, the backdrop is lightened, as if it were dodged;
* this is useful for adding highlights to a scene.
*
* If the source color is darker than 0.5, the backdrop is darkened, as if it were burned in.
* The degree of lightening or darkening is proportional to the difference between the source color and 0.5;
* if it is equal to 0.5, the backdrop is unchanged.
*
* Painting with pure black or white produces a distinctly darker or lighter area, but does not result in pure black or white.
* The effect is similar to shining a diffused spotlight on the backdrop.
*
* @method Phaser.Color.blendSoftLight
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendSoftLight: function (a, b)
{
return b < 128 ? (2 * ((a >> 1) + 64)) * (b / 255) : 255 - (2 * (255 - ((a >> 1) + 64)) * (255 - b) / 255);
},
/**
* Multiplies or screens the colors, depending on the source color value.
*
* If the source color is lighter than 0.5, the backdrop is lightened, as if it were screened;
* this is useful for adding highlights to a scene.
*
* If the source color is darker than 0.5, the backdrop is darkened, as if it were multiplied;
* this is useful for adding shadows to a scene.
*
* The degree of lightening or darkening is proportional to the difference between the source color and 0.5;
* if it is equal to 0.5, the backdrop is unchanged.
*
* Painting with pure black or white produces pure black or white. The effect is similar to shining a harsh spotlight on the backdrop.
*
* @method Phaser.Color.blendHardLight
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendHardLight: function (a, b)
{
return Phaser.Color.blendOverlay(b, a);
},
/**
* Brightens the backdrop color to reflect the source color.
* Painting with black produces no change.
*
* @method Phaser.Color.blendColorDodge
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendColorDodge: function (a, b)
{
return b === 255 ? b : Math.min(255, ((a << 8) / (255 - b)));
},
/**
* Darkens the backdrop color to reflect the source color.
* Painting with white produces no change.
*
* @method Phaser.Color.blendColorBurn
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendColorBurn: function (a, b)
{
return b === 0 ? b : Math.max(0, (255 - ((255 - a) << 8) / b));
},
/**
* An alias for blendAdd, it simply sums the values of the two colors.
*
* @method Phaser.Color.blendLinearDodge
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendLinearDodge: function (a, b)
{
return Phaser.Color.blendAdd(a, b);
},
/**
* An alias for blendSubtract, it simply sums the values of the two colors and subtracts 255.
*
* @method Phaser.Color.blendLinearBurn
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendLinearBurn: function (a, b)
{
return Phaser.Color.blendSubtract(a, b);
},
/**
* This blend mode combines Linear Dodge and Linear Burn (rescaled so that neutral colors become middle gray).
* Dodge applies to values of top layer lighter than middle gray, and burn to darker values.
* The calculation simplifies to the sum of bottom layer and twice the top layer, subtract 128. The contrast decreases.
*
* @method Phaser.Color.blendLinearLight
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendLinearLight: function (a, b)
{
return b < 128 ? Phaser.Color.blendLinearBurn(a, 2 * b) : Phaser.Color.blendLinearDodge(a, (2 * (b - 128)));
},
/**
* This blend mode combines Color Dodge and Color Burn (rescaled so that neutral colors become middle gray).
* Dodge applies when values in the top layer are lighter than middle gray, and burn to darker values.
* The middle gray is the neutral color. When color is lighter than this, this effectively moves the white point of the bottom
* layer down by twice the difference; when it is darker, the black point is moved up by twice the difference. The perceived contrast increases.
*
* @method Phaser.Color.blendVividLight
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendVividLight: function (a, b)
{
return b < 128 ? Phaser.Color.blendColorBurn(a, 2 * b) : Phaser.Color.blendColorDodge(a, (2 * (b - 128)));
},
/**
* If the backdrop color (light source) is lighter than 50%, the blendDarken mode is used, and colors lighter than the backdrop color do not change.
* If the backdrop color is darker than 50% gray, colors lighter than the blend color are replaced, and colors darker than the blend color do not change.
*
* @method Phaser.Color.blendPinLight
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendPinLight: function (a, b)
{
return b < 128 ? Phaser.Color.blendDarken(a, 2 * b) : Phaser.Color.blendLighten(a, (2 * (b - 128)));
},
/**
* Runs blendVividLight on the source and backdrop colors.
* If the resulting color is 128 or more, it receives a value of 255; if less than 128, a value of 0.
* Therefore, all blended pixels have red, green, and blue channel values of either 0 or 255.
* This changes all pixels to primary additive colors (red, green, or blue), white, or black.
*
* @method Phaser.Color.blendHardMix
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendHardMix: function (a, b)
{
return Phaser.Color.blendVividLight(a, b) < 128 ? 0 : 255;
},
/**
* Reflect blend mode. This mode is useful when adding shining objects or light zones to images.
*
* @method Phaser.Color.blendReflect
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendReflect: function (a, b)
{
return b === 255 ? b : Math.min(255, (a * a / (255 - b)));
},
/**
* Glow blend mode. This mode is a variation of reflect mode with the source and backdrop colors swapped.
*
* @method Phaser.Color.blendGlow
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendGlow: function (a, b)
{
return Phaser.Color.blendReflect(b, a);
},
/**
* Phoenix blend mode. This subtracts the lighter color from the darker color, and adds 255, giving a bright result.
*
* @method Phaser.Color.blendPhoenix
* @static
* @param {integer} a - The source color to blend, in the range 1 to 255.
* @param {integer} b - The backdrop color to blend, in the range 1 to 255.
* @returns {integer} The blended color value, in the range 1 to 255.
*/
blendPhoenix: function (a, b)
{
return Math.min(a, b) - Math.max(a, b) + 255;
}
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Physics Manager is responsible for looking after all of the running physics systems.
* Phaser supports 4 physics systems: Arcade Physics, P2, Ninja Physics and Box2D via a commercial plugin.
*
* Game Objects (such as Sprites) can only belong to 1 physics system, but you can have multiple systems active in a single game.
*
* For example you could have P2 managing a polygon-built terrain landscape that an vehicle drives over, while it could be firing bullets that use the
* faster (due to being much simpler) Arcade Physics system.
*
* @class Phaser.Physics
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {object} [physicsConfig=null] - A physics configuration object to pass to the Physics world on creation.
*/
Phaser.Physics = function (game, config)
{
config = config || {};
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = game;
/**
* @property {object} config - The physics configuration object as passed to the game on creation.
*/
this.config = config;
/**
* @property {Phaser.Physics.Arcade} arcade - The Arcade Physics system.
*/
this.arcade = null;
/**
* @property {Phaser.Physics.P2} p2 - The P2.JS Physics system.
*/
this.p2 = null;
/**
* @property {Phaser.Physics.Ninja} ninja - The N+ Ninja Physics system.
*/
this.ninja = null;
/**
* @property {Phaser.Physics.Box2D} box2d - The Box2D Physics system.
*/
this.box2d = null;
/**
* @property {Phaser.Physics.Chipmunk} chipmunk - The Chipmunk Physics system (to be done).
*/
this.chipmunk = null;
/**
* @property {Phaser.Physics.Matter} matter - The MatterJS Physics system (coming soon).
*/
this.matter = null;
this.parseConfig();
};
/**
* @const
* @type {number}
*/
Phaser.Physics.ARCADE = 0;
/**
* @const
* @type {number}
*/
Phaser.Physics.P2JS = 1;
/**
* @const
* @type {number}
*/
Phaser.Physics.NINJA = 2;
/**
* @const
* @type {number}
*/
Phaser.Physics.BOX2D = 3;
/**
* @const
* @type {number}
*/
Phaser.Physics.CHIPMUNK = 4;
/**
* @const
* @type {number}
*/
Phaser.Physics.MATTERJS = 5;
Phaser.Physics.prototype = {
/**
* Parses the Physics Configuration object passed to the Game constructor and starts any physics systems specified within.
*
* @method Phaser.Physics#parseConfig
*/
parseConfig: function ()
{
if ((!this.config.hasOwnProperty('arcade') || this.config.arcade === true) && Phaser.Physics.hasOwnProperty('Arcade'))
{
// If Arcade isn't specified, we create it automatically if we can
this.arcade = new Phaser.Physics.Arcade(this.game);
}
if (this.config.hasOwnProperty('ninja') && this.config.ninja === true && Phaser.Physics.hasOwnProperty('Ninja'))
{
this.ninja = new Phaser.Physics.Ninja(this.game);
}
if (this.config.hasOwnProperty('p2') && this.config.p2 === true && Phaser.Physics.hasOwnProperty('P2'))
{
this.p2 = new Phaser.Physics.P2(this.game, this.config);
}
if (this.config.hasOwnProperty('box2d') && this.config.box2d === true && Phaser.Physics.hasOwnProperty('BOX2D'))
{
this.box2d = new Phaser.Physics.Box2D(this.game, this.config);
}
if (this.config.hasOwnProperty('matter') && this.config.matter === true && Phaser.Physics.hasOwnProperty('Matter'))
{
this.matter = new Phaser.Physics.Matter(this.game, this.config);
}
},
/**
* This will create an instance of the requested physics simulation.
* Phaser.Physics.Arcade is running by default, but all others need activating directly.
*
* You can start the following physics systems:
*
* Phaser.Physics.P2JS - A full-body advanced physics system by Stefan Hedman.
* Phaser.Physics.NINJA - A port of Metanet Softwares N+ physics system.
* Phaser.Physics.BOX2D - A commercial Phaser Plugin (see http://phaser.io)
*
* Both Ninja Physics and Box2D require their respective plugins to be loaded before you can start them.
* They are not bundled into the core Phaser library.
*
* If the physics world has already been created (i.e. in another state in your game) then
* calling startSystem will reset the physics world, not re-create it. If you need to start them again from their constructors
* then set Phaser.Physics.p2 (or whichever system you want to recreate) to `null` before calling `startSystem`.
*
* @method Phaser.Physics#startSystem
* @param {number} system - The physics system to start: Phaser.Physics.ARCADE, Phaser.Physics.P2JS, Phaser.Physics.NINJA or Phaser.Physics.BOX2D.
*/
startSystem: function (system)
{
if (system === Phaser.Physics.ARCADE)
{
this.arcade = new Phaser.Physics.Arcade(this.game);
}
else if (system === Phaser.Physics.P2JS)
{
if (this.p2 === null)
{
this.p2 = new Phaser.Physics.P2(this.game, this.config);
}
else
{
this.p2.reset();
}
}
else if (system === Phaser.Physics.NINJA)
{
this.ninja = new Phaser.Physics.Ninja(this.game);
}
else if (system === Phaser.Physics.BOX2D)
{
if (this.box2d === null)
{
this.box2d = new Phaser.Physics.Box2D(this.game, this.config);
}
else
{
this.box2d.reset();
}
}
else if (system === Phaser.Physics.MATTERJS)
{
if (this.matter === null)
{
this.matter = new Phaser.Physics.Matter(this.game, this.config);
}
else
{
this.matter.reset();
}
}
},
/**
* This will create a default physics body on the given game object or array of objects.
* A game object can only have 1 physics body active at any one time, and it can't be changed until the object is destroyed.
* It can be for any of the physics systems that have been started:
*
* Phaser.Physics.Arcade - A light weight AABB based collision system with basic separation.
* Phaser.Physics.P2JS - A full-body advanced physics system supporting multiple object shapes, polygon loading, contact materials, springs and constraints.
* Phaser.Physics.NINJA - A port of Metanet Softwares N+ physics system. Advanced AABB and Circle vs. Tile collision.
* Phaser.Physics.BOX2D - A port of https://code.google.com/p/box2d-html5
* Phaser.Physics.MATTER - A full-body and light-weight advanced physics system (still in development)
* Phaser.Physics.CHIPMUNK is still in development.
*
* If you require more control over what type of body is created, for example to create a Ninja Physics Circle instead of the default AABB, then see the
* individual physics systems `enable` methods instead of using this generic one.
*
* @method Phaser.Physics#enable
* @param {object|array} object - The game object to create the physics body on. Can also be an array of objects, a body will be created on every object in the array.
* @param {number} [system=Phaser.Physics.ARCADE] - The physics system that will be used to create the body. Defaults to Arcade Physics.
* @param {boolean} [debug=false] - Enable the debug drawing for this body. Defaults to false.
*/
enable: function (object, system, debug)
{
if (system === undefined) { system = Phaser.Physics.ARCADE; }
if (debug === undefined) { debug = false; }
if (system === Phaser.Physics.ARCADE)
{
this.arcade.enable(object);
}
else if (system === Phaser.Physics.P2JS && this.p2)
{
this.p2.enable(object, debug);
}
else if (system === Phaser.Physics.NINJA && this.ninja)
{
this.ninja.enableAABB(object);
}
else if (system === Phaser.Physics.BOX2D && this.box2d)
{
this.box2d.enable(object);
}
else if (system === Phaser.Physics.MATTERJS && this.matter)
{
this.matter.enable(object);
}
else
{
console.warn(object.key + ' is attempting to enable a physics body using an unknown physics system.');
}
},
/**
* preUpdate checks.
*
* @method Phaser.Physics#preUpdate
* @protected
*/
preUpdate: function ()
{
// ArcadePhysics / Ninja don't have a core to preUpdate
if (this.p2)
{
this.p2.preUpdate();
}
if (this.box2d)
{
this.box2d.preUpdate();
}
if (this.matter)
{
this.matter.preUpdate();
}
},
/**
* Updates all running physics systems.
*
* @method Phaser.Physics#update
* @protected
*/
update: function ()
{
// ArcadePhysics / Ninja don't have a core to update
if (this.p2)
{
this.p2.update();
}
if (this.box2d)
{
this.box2d.update();
}
if (this.matter)
{
this.matter.update();
}
},
/**
* Updates the physics bounds to match the world dimensions.
*
* @method Phaser.Physics#setBoundsToWorld
* @protected
*/
setBoundsToWorld: function ()
{
if (this.arcade)
{
this.arcade.setBoundsToWorld();
}
if (this.ninja)
{
this.ninja.setBoundsToWorld();
}
if (this.p2)
{
this.p2.setBoundsToWorld();
}
if (this.box2d)
{
this.box2d.setBoundsToWorld();
}
if (this.matter)
{
this.matter.setBoundsToWorld();
}
},
/**
* Clears down all active physics systems. This doesn't destroy them, it just clears them of objects and is called when the State changes.
*
* @method Phaser.Physics#clear
* @protected
*/
clear: function ()
{
if (this.p2)
{
this.p2.clear();
}
if (this.box2d)
{
this.box2d.clear();
}
if (this.matter)
{
this.matter.clear();
}
},
/**
* Resets the active physics system. Called automatically on a Phaser.State swap.
*
* @method Phaser.Physics#reset
* @protected
*/
reset: function ()
{
if (this.p2)
{
this.p2.reset();
}
if (this.box2d)
{
this.box2d.reset();
}
if (this.matter)
{
this.matter.reset();
}
},
/**
* Destroys all active physics systems. Usually only called on a Game Shutdown, not on a State swap.
*
* @method Phaser.Physics#destroy
*/
destroy: function ()
{
if (this.p2)
{
this.p2.destroy();
}
if (this.box2d)
{
this.box2d.destroy();
}
if (this.matter)
{
this.matter.destroy();
}
this.arcade = null;
this.ninja = null;
this.p2 = null;
this.box2d = null;
this.matter = null;
}
};
Phaser.Physics.prototype.constructor = Phaser.Physics;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Arcade Physics world. Contains Arcade Physics related collision, overlap and motion methods.
*
* @class Phaser.Physics.Arcade
* @constructor
* @param {Phaser.Game} game - reference to the current game instance.
*/
Phaser.Physics.Arcade = function (game)
{
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = game;
/**
* @property {Phaser.Point} gravity - The World gravity setting. Defaults to x: 0, y: 0, or no gravity.
*/
this.gravity = new Phaser.Point();
/**
* @property {Phaser.Rectangle} bounds - The bounds inside of which the physics world exists. Defaults to match the world bounds.
*/
this.bounds = new Phaser.Rectangle(0, 0, game.world.width, game.world.height);
/**
* Which edges of the World bounds Bodies can collide against when `collideWorldBounds` is `true`.
* For example checkCollision.down = false means Bodies cannot collide with the World.bounds.bottom.
* @property {object} checkCollision - An object containing allowed collision flags (up, down, left, right).
*/
this.checkCollision = { up: true, down: true, left: true, right: true };
/**
* @property {number} maxObjects - Used by the QuadTree to set the maximum number of objects per quad.
*/
this.maxObjects = 10;
/**
* @property {number} maxLevels - Used by the QuadTree to set the maximum number of iteration levels.
*/
this.maxLevels = 4;
/**
* @property {number} OVERLAP_BIAS - A value added to the delta values during collision checks. Increase it to prevent sprite tunneling.
* @default
*/
this.OVERLAP_BIAS = 4;
/**
* @property {boolean} forceX - If true World.separate will always separate on the X axis before Y. Otherwise it will check gravity totals first.
*/
this.forceX = false;
/**
* @property {number} sortDirection - Used when colliding a Sprite vs. a Group, or a Group vs. a Group, this defines the direction the sort is based on. Default is Phaser.Physics.Arcade.LEFT_RIGHT.
* @default
*/
this.sortDirection = Phaser.Physics.Arcade.LEFT_RIGHT;
/**
* @property {boolean} skipQuadTree - If true the QuadTree will not be used for any collision. QuadTrees are great if objects are well spread out in your game, otherwise they are a performance hit. If you enable this you can disable on a per body basis via `Body.skipQuadTree`.
*/
this.skipQuadTree = true;
/**
* @property {boolean} isPaused - If `true` the `Body.preUpdate` method will be skipped, halting all motion for all bodies. Note that other methods such as `collide` will still work, so be careful not to call them on paused bodies.
*/
this.isPaused = false;
/**
* @property {Phaser.QuadTree} quadTree - The world QuadTree.
*/
this.quadTree = new Phaser.QuadTree(this.game.world.bounds.x, this.game.world.bounds.y, this.game.world.bounds.width, this.game.world.bounds.height, this.maxObjects, this.maxLevels);
/**
* @property {number} _total - Internal cache var.
* @private
*/
this._total = 0;
// By default we want the bounds the same size as the world bounds
this.setBoundsToWorld();
};
Phaser.Physics.Arcade.prototype.constructor = Phaser.Physics.Arcade;
/**
* A constant used for the sortDirection value.
* Use this if you don't wish to perform any pre-collision sorting at all, or will manually sort your Groups.
* @constant
* @type {number}
*/
Phaser.Physics.Arcade.SORT_NONE = 0;
/**
* A constant used for the sortDirection value.
* Use this if your game world is wide but short and scrolls from the left to the right (i.e. Mario)
* @constant
* @type {number}
*/
Phaser.Physics.Arcade.LEFT_RIGHT = 1;
/**
* A constant used for the sortDirection value.
* Use this if your game world is wide but short and scrolls from the right to the left (i.e. Mario backwards)
* @constant
* @type {number}
*/
Phaser.Physics.Arcade.RIGHT_LEFT = 2;
/**
* A constant used for the sortDirection value.
* Use this if your game world is narrow but tall and scrolls from the top to the bottom (i.e. Dig Dug)
* @constant
* @type {number}
*/
Phaser.Physics.Arcade.TOP_BOTTOM = 3;
/**
* A constant used for the sortDirection value.
* Use this if your game world is narrow but tall and scrolls from the bottom to the top (i.e. Commando or a vertically scrolling shoot-em-up)
* @constant
* @type {number}
*/
Phaser.Physics.Arcade.BOTTOM_TOP = 4;
Phaser.Physics.Arcade.prototype = {
/**
* Updates the size of this physics world.
*
* @method Phaser.Physics.Arcade#setBounds
* @param {number} x - Top left most corner of the world.
* @param {number} y - Top left most corner of the world.
* @param {number} width - New width of the world. Can never be smaller than the Game.width.
* @param {number} height - New height of the world. Can never be smaller than the Game.height.
*/
setBounds: function (x, y, width, height)
{
this.bounds.setTo(x, y, width, height);
},
/**
* Updates the size of this physics world to match the size of the game world.
*
* @method Phaser.Physics.Arcade#setBoundsToWorld
*/
setBoundsToWorld: function ()
{
this.bounds.copyFrom(this.game.world.bounds);
},
/**
* This will create an Arcade Physics body on the given game object or array of game objects.
* A game object can only have 1 physics body active at any one time, and it can't be changed until the object is destroyed.
*
* @method Phaser.Physics.Arcade#enable
* @param {object|array|Phaser.Group} object - The game object to create the physics body on. Can also be an array or Group of objects, a body will be created on every child that has a `body` property.
* @param {boolean} [children=true] - Should a body be created on all children of this object? If true it will recurse down the display list as far as it can go.
*/
enable: function (object, children)
{
if (children === undefined) { children = true; }
var i = 1;
if (Array.isArray(object))
{
i = object.length;
while (i--)
{
if (object[i] instanceof Phaser.Group)
{
// If it's a Group then we do it on the children regardless
this.enable(object[i].children, children);
}
else
{
this.enableBody(object[i]);
if (children && object[i].hasOwnProperty('children') && object[i].children.length > 0)
{
this.enable(object[i], true);
}
}
}
}
else
if (object instanceof Phaser.Group)
{
// If it's a Group then we do it on the children regardless
this.enable(object.children, children);
}
else
{
this.enableBody(object);
if (children && object.hasOwnProperty('children') && object.children.length > 0)
{
this.enable(object.children, true);
}
}
},
/**
* Creates an Arcade Physics body on the given game object.
*
* A game object can only have 1 physics body active at any one time, and it can't be changed until the body is nulled.
*
* When you add an Arcade Physics body to an object it will automatically add the object into its parent Groups hash array.
*
* @method Phaser.Physics.Arcade#enableBody
* @param {object} object - The game object to create the physics body on. A body will only be created if this object has a null `body` property.
*/
enableBody: function (object)
{
if (object.hasOwnProperty('body') && object.body === null)
{
object.body = new Phaser.Physics.Arcade.Body(object);
if (object.parent && object.parent instanceof Phaser.Group)
{
object.parent.addToHash(object);
}
}
},
/**
* Called automatically by a Physics body, it updates all motion related values on the Body unless `World.isPaused` is `true`.
*
* @method Phaser.Physics.Arcade#updateMotion
* @param {Phaser.Physics.Arcade.Body} The Body object to be updated.
*/
updateMotion: function (body)
{
if (body.allowRotation)
{
var velocityDelta = this.computeVelocity(0, body, body.angularVelocity, body.angularAcceleration, body.angularDrag, body.maxAngular) - body.angularVelocity;
body.angularVelocity += velocityDelta;
body.rotation += (body.angularVelocity * this.game.time.physicsElapsed);
}
body.velocity.x = this.computeVelocity(1, body, body.velocity.x, body.acceleration.x, body.drag.x, body.maxVelocity.x);
body.velocity.y = this.computeVelocity(2, body, body.velocity.y, body.acceleration.y, body.drag.y, body.maxVelocity.y);
},
/**
* A tween-like function that takes a starting velocity and some other factors and returns an altered velocity.
* Based on a function in Flixel by @ADAMATOMIC
*
* @method Phaser.Physics.Arcade#computeVelocity
* @param {number} axis - 0 for nothing, 1 for horizontal, 2 for vertical.
* @param {Phaser.Physics.Arcade.Body} body - The Body object to be updated.
* @param {number} velocity - Any component of velocity (e.g. 20).
* @param {number} acceleration - Rate at which the velocity is changing.
* @param {number} drag - Really kind of a deceleration, this is how much the velocity changes if Acceleration is not set.
* @param {number} [max=10000] - An absolute value cap for the velocity.
* @return {number} The altered Velocity value.
*/
computeVelocity: function (axis, body, velocity, acceleration, drag, max)
{
if (max === undefined) { max = 10000; }
if (axis === 1 && body.allowGravity)
{
velocity += (this.gravity.x + body.gravity.x) * this.game.time.physicsElapsed;
}
else if (axis === 2 && body.allowGravity)
{
velocity += (this.gravity.y + body.gravity.y) * this.game.time.physicsElapsed;
}
if (acceleration)
{
velocity += acceleration * this.game.time.physicsElapsed;
}
else if (drag && body.allowDrag)
{
drag *= this.game.time.physicsElapsed;
if (velocity - drag > 0)
{
velocity -= drag;
}
else if (velocity + drag < 0)
{
velocity += drag;
}
else
{
velocity = 0;
}
}
if (velocity > max)
{
velocity = max;
}
else if (velocity < -max)
{
velocity = -max;
}
return velocity;
},
/**
* Checks for overlaps between two game objects. The objects can be Sprites, Groups or Emitters.
*
* Unlike {@link #collide} the objects are NOT automatically separated or have any physics applied, they merely test for overlap results.
*
* You can perform Sprite vs. Sprite, Sprite vs. Group and Group vs. Group overlap checks.
* Both the first and second parameter can be arrays of objects, of differing types.
* If two arrays are passed, the contents of the first parameter will be tested against all contents of the 2nd parameter.
*
* **This function is not recursive**, and will not test against children of objects passed (i.e. Groups within Groups).
*
* ##### Tilemaps
*
* Any overlapping tiles, including blank/null tiles, will give a positive result. Tiles marked via {@link Phaser.Tilemap#setCollision} (and similar methods) have no special status, and callbacks added via {@link Phaser.Tilemap#setTileIndexCallback} or {@link Phaser.Tilemap#setTileLocationCallback} are not invoked. So calling this method without any callbacks isn't very useful.
*
* If you're interested only in whether an object overlaps a certain tile or class of tiles, filter the tiles with `processCallback` and then use the result returned by this method. Blank/null tiles can be excluded by their {@link Phaser.Tile#index index} (-1).
*
* If you want to take action on certain overlaps, examine the tiles in `collideCallback` and then handle as you like.
*
* @method Phaser.Physics.Arcade#overlap
* @param {Phaser.Sprite|Phaser.Group|Phaser.Particles.Emitter|array} object1 - The first object or array of objects to check. Can be Phaser.Sprite, Phaser.Group or Phaser.Particles.Emitter.
* @param {Phaser.Sprite|Phaser.Group|Phaser.Particles.Emitter|array} object2 - The second object or array of objects to check. Can be Phaser.Sprite, Phaser.Group or Phaser.Particles.Emitter.
* @param {function} [overlapCallback=null] - An optional callback function that is called if the objects overlap. The two objects will be passed to this function in the same order in which you specified them, unless you are checking Group vs. Sprite, in which case Sprite will always be the first parameter.
* @param {function} [processCallback=null] - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then `overlapCallback` will only be called if this callback returns `true`.
* @param {object} [callbackContext] - The context in which to run the callbacks.
* @return {boolean} True if an overlap occurred otherwise false.
*/
overlap: function (object1, object2, overlapCallback, processCallback, callbackContext)
{
overlapCallback = overlapCallback || null;
processCallback = processCallback || null;
callbackContext = callbackContext || overlapCallback;
this._total = 0;
this.collideObjects(object1, object2, overlapCallback, processCallback, callbackContext, true);
return (this._total > 0);
},
/**
* Checks for collision between two game objects and separates them if colliding ({@link https://gist.github.com/samme/cbb81dd19f564dcfe2232761e575063d details}). If you don't require separation then use {@link #overlap} instead.
*
* You can perform Sprite vs. Sprite, Sprite vs. Group, Group vs. Group, Sprite vs. Tilemap Layer or Group vs. Tilemap Layer collisions.
* Both the `object1` and `object2` can be arrays of objects, of differing types.
*
* If two Groups or arrays are passed, each member of one will be tested against each member of the other.
*
* If one Group **only** is passed (as `object1`), each member of the Group will be collided against the other members.
*
* If either object is `null` the collision test will fail.
*
* Bodies with `enable = false` and Sprites with `exists = false` are skipped (ignored).
*
* An optional processCallback can be provided. If given this function will be called when two sprites are found to be colliding. It is called before any separation takes place, giving you the chance to perform additional checks. If the function returns true then the collision and separation is carried out. If it returns false it is skipped.
*
* The collideCallback is an optional function that is only called if two sprites collide. If a processCallback has been set then it needs to return true for collideCallback to be called.
*
* **This function is not recursive**, and will not test against children of objects passed (i.e. Groups or Tilemaps within other Groups).
*
* ##### Examples
*
* ```javascript
* collide(group);
* collide(group, undefined); // equivalent
*
* collide(sprite1, sprite2);
*
* collide(sprite, group);
*
* collide(group1, group2);
*
* collide([sprite1, sprite2], [sprite3, sprite4]); // 1 vs. 3, 1 vs. 4, 2 vs. 3, 2 vs. 4
* ```
*
* ##### Tilemaps
*
* Tiles marked via {@link Phaser.Tilemap#setCollision} (and similar methods) are "solid". If a Sprite collides with one of these tiles, the two are separated by moving the Sprite outside the tile's edges. Enable {@link Phaser.TilemapLayer#debug} to see the colliding edges of the Tilemap.
*
* Tiles with a callback attached via {@link Phaser.Tilemap#setTileIndexCallback} or {@link Phaser.Tilemap#setTileLocationCallback} invoke the callback if a Sprite collides with them. If a tile has a callback attached via both methods, only the location callback is invoked. The colliding Sprite is separated from the tile only if the callback returns `true`.
*
* @method Phaser.Physics.Arcade#collide
* @param {Phaser.Sprite|Phaser.Group|Phaser.Particles.Emitter|Phaser.TilemapLayer|array} object1 - The first object or array of objects to check. Can be Phaser.Sprite, Phaser.Group, Phaser.Particles.Emitter, or Phaser.TilemapLayer.
* @param {Phaser.Sprite|Phaser.Group|Phaser.Particles.Emitter|Phaser.TilemapLayer|array} object2 - The second object or array of objects to check. Can be Phaser.Sprite, Phaser.Group, Phaser.Particles.Emitter or Phaser.TilemapLayer.
* @param {function} [collideCallback=null] - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them, unless you are colliding Group vs. Sprite, in which case Sprite will always be the first parameter.
* @param {function} [processCallback=null] - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them, unless you are colliding Group vs. Sprite, in which case Sprite will always be the first parameter.
* @param {object} [callbackContext] - The context in which to run the callbacks.
* @return {boolean} True if a collision occurred otherwise false.
*/
collide: function (object1, object2, collideCallback, processCallback, callbackContext)
{
collideCallback = collideCallback || null;
processCallback = processCallback || null;
callbackContext = callbackContext || collideCallback;
this._total = 0;
this.collideObjects(object1, object2, collideCallback, processCallback, callbackContext, false);
return (this._total > 0);
},
/**
* A Sort function for sorting two bodies based on a LEFT to RIGHT sort direction.
*
* This is called automatically by World.sort
*
* @method Phaser.Physics.Arcade#sortLeftRight
* @param {Phaser.Sprite} a - The first Sprite to test. The Sprite must have an Arcade Physics Body.
* @param {Phaser.Sprite} b - The second Sprite to test. The Sprite must have an Arcade Physics Body.
* @return {integer} A negative value if `a > b`, a positive value if `a < b` or 0 if `a === b` or the bodies are invalid.
*/
sortLeftRight: function (a, b)
{
if (!a.body || !b.body)
{
return 0;
}
return a.body.x - b.body.x;
},
/**
* A Sort function for sorting two bodies based on a RIGHT to LEFT sort direction.
*
* This is called automatically by World.sort
*
* @method Phaser.Physics.Arcade#sortRightLeft
* @param {Phaser.Sprite} a - The first Sprite to test. The Sprite must have an Arcade Physics Body.
* @param {Phaser.Sprite} b - The second Sprite to test. The Sprite must have an Arcade Physics Body.
* @return {integer} A negative value if `a > b`, a positive value if `a < b` or 0 if `a === b` or the bodies are invalid.
*/
sortRightLeft: function (a, b)
{
if (!a.body || !b.body)
{
return 0;
}
return b.body.x - a.body.x;
},
/**
* A Sort function for sorting two bodies based on a TOP to BOTTOM sort direction.
*
* This is called automatically by World.sort
*
* @method Phaser.Physics.Arcade#sortTopBottom
* @param {Phaser.Sprite} a - The first Sprite to test. The Sprite must have an Arcade Physics Body.
* @param {Phaser.Sprite} b - The second Sprite to test. The Sprite must have an Arcade Physics Body.
* @return {integer} A negative value if `a > b`, a positive value if `a < b` or 0 if `a === b` or the bodies are invalid.
*/
sortTopBottom: function (a, b)
{
if (!a.body || !b.body)
{
return 0;
}
return a.body.y - b.body.y;
},
/**
* A Sort function for sorting two bodies based on a BOTTOM to TOP sort direction.
*
* This is called automatically by World.sort
*
* @method Phaser.Physics.Arcade#sortBottomTop
* @param {Phaser.Sprite} a - The first Sprite to test. The Sprite must have an Arcade Physics Body.
* @param {Phaser.Sprite} b - The second Sprite to test. The Sprite must have an Arcade Physics Body.
* @return {integer} A negative value if `a > b`, a positive value if `a < b` or 0 if `a === b` or the bodies are invalid.
*/
sortBottomTop: function (a, b)
{
if (!a.body || !b.body)
{
return 0;
}
return b.body.y - a.body.y;
},
/**
* This method will sort a Groups hash array.
*
* If the Group has `physicsSortDirection` set it will use the sort direction defined.
*
* Otherwise if the sortDirection parameter is undefined, or Group.physicsSortDirection is null, it will use Phaser.Physics.Arcade.sortDirection.
*
* By changing Group.physicsSortDirection you can customise each Group to sort in a different order.
*
* @method Phaser.Physics.Arcade#sort
* @param {Phaser.Group} group - The Group to sort.
* @param {integer} [sortDirection] - The sort direction used to sort this Group.
*/
sort: function (group, sortDirection)
{
if (group.physicsSortDirection !== null)
{
sortDirection = group.physicsSortDirection;
}
else
if (sortDirection === undefined) { sortDirection = this.sortDirection; }
if (sortDirection === Phaser.Physics.Arcade.LEFT_RIGHT)
{
// Game world is say 2000x600 and you start at 0
group.hash.sort(this.sortLeftRight);
}
else if (sortDirection === Phaser.Physics.Arcade.RIGHT_LEFT)
{
// Game world is say 2000x600 and you start at 2000
group.hash.sort(this.sortRightLeft);
}
else if (sortDirection === Phaser.Physics.Arcade.TOP_BOTTOM)
{
// Game world is say 800x2000 and you start at 0
group.hash.sort(this.sortTopBottom);
}
else if (sortDirection === Phaser.Physics.Arcade.BOTTOM_TOP)
{
// Game world is say 800x2000 and you start at 2000
group.hash.sort(this.sortBottomTop);
}
},
/**
* Internal collision handler. Extracts objects for {@link #collideHandler}.
*
* @method Phaser.Physics.Arcade#collideObjects
* @private
*/
collideObjects: function (object1, object2, collideCallback, processCallback, callbackContext, overlapOnly)
{
if (!Array.isArray(object1) && Array.isArray(object2))
{
for (var i = 0; i < object2.length; i++)
{
if (!object2[i]) { continue; }
this.collideHandler(object1, object2[i], collideCallback, processCallback, callbackContext, overlapOnly);
}
}
else if (Array.isArray(object1) && !Array.isArray(object2))
{
for (var i = 0; i < object1.length; i++)
{
if (!object1[i]) { continue; }
this.collideHandler(object1[i], object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
else if (Array.isArray(object1) && Array.isArray(object2))
{
for (var i = 0; i < object1.length; i++)
{
if (!object1[i]) { continue; }
for (var j = 0; j < object2.length; j++)
{
if (!object2[j]) { continue; }
this.collideHandler(object1[i], object2[j], collideCallback, processCallback, callbackContext, overlapOnly);
}
}
}
else
{
this.collideHandler(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
},
/**
* Internal collision handler. Matches arguments to other handlers.
*
* @method Phaser.Physics.Arcade#collideHandler
* @private
* @param {Phaser.Sprite|Phaser.Group|Phaser.Particles.Emitter|Phaser.TilemapLayer} object1 - The first object to check. Can be an instance of Phaser.Sprite, Phaser.Group, Phaser.Particles.Emitter, or Phaser.TilemapLayer.
* @param {Phaser.Sprite|Phaser.Group|Phaser.Particles.Emitter|Phaser.TilemapLayer} object2 - The second object to check. Can be an instance of Phaser.Sprite, Phaser.Group, Phaser.Particles.Emitter or Phaser.TilemapLayer. Can also be an array of objects to check.
* @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
* @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
*/
collideHandler: function (object1, object2, collideCallback, processCallback, callbackContext, overlapOnly)
{
// Only collide valid objects
if (object2 === undefined && object1.physicsType === Phaser.GROUP)
{
this.sort(object1);
this.collideGroupVsSelf(object1, collideCallback, processCallback, callbackContext, overlapOnly);
return;
}
// If neither of the objects are set or exist then bail out
if (!object1 || !object2 || !object1.exists || !object2.exists)
{
return;
}
// Groups? Sort them
if (this.sortDirection !== Phaser.Physics.Arcade.SORT_NONE)
{
if (object1.physicsType === Phaser.GROUP)
{
this.sort(object1);
}
if (object2.physicsType === Phaser.GROUP)
{
this.sort(object2);
}
}
// SPRITES
if (object1.physicsType === Phaser.SPRITE)
{
if (object2.physicsType === Phaser.SPRITE)
{
this.collideSpriteVsSprite(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
else if (object2.physicsType === Phaser.GROUP)
{
this.collideSpriteVsGroup(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
else if (object2.physicsType === Phaser.TILEMAPLAYER)
{
this.collideSpriteVsTilemapLayer(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
// GROUPS
else if (object1.physicsType === Phaser.GROUP)
{
if (object2.physicsType === Phaser.SPRITE)
{
this.collideSpriteVsGroup(object2, object1, collideCallback, processCallback, callbackContext, overlapOnly);
}
else if (object2.physicsType === Phaser.GROUP)
{
this.collideGroupVsGroup(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
else if (object2.physicsType === Phaser.TILEMAPLAYER)
{
this.collideGroupVsTilemapLayer(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
// TILEMAP LAYERS
else if (object1.physicsType === Phaser.TILEMAPLAYER)
{
if (object2.physicsType === Phaser.SPRITE)
{
this.collideSpriteVsTilemapLayer(object2, object1, collideCallback, processCallback, callbackContext, overlapOnly);
}
else if (object2.physicsType === Phaser.GROUP)
{
this.collideGroupVsTilemapLayer(object2, object1, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
},
/**
* An internal function. Use Phaser.Physics.Arcade.collide instead.
*
* @method Phaser.Physics.Arcade#collideSpriteVsSprite
* @private
* @param {Phaser.Sprite} sprite1 - The first sprite to check.
* @param {Phaser.Sprite} sprite2 - The second sprite to check.
* @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
* @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
* @return {boolean} True if there was a collision, otherwise false.
*/
collideSpriteVsSprite: function (sprite1, sprite2, collideCallback, processCallback, callbackContext, overlapOnly)
{
if (!sprite1.body || !sprite2.body)
{
return false;
}
if (this.separate(sprite1.body, sprite2.body, processCallback, callbackContext, overlapOnly))
{
if (collideCallback)
{
collideCallback.call(callbackContext, sprite1, sprite2);
}
this._total++;
}
return true;
},
/**
* An internal function. Use Phaser.Physics.Arcade.collide instead.
*
* @method Phaser.Physics.Arcade#collideSpriteVsGroup
* @private
* @param {Phaser.Sprite} sprite - The sprite to check.
* @param {Phaser.Group} group - The Group to check.
* @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
* @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
*/
collideSpriteVsGroup: function (sprite, group, collideCallback, processCallback, callbackContext, overlapOnly)
{
if (group.length === 0 || !sprite.body)
{
return;
}
if (this.skipQuadTree || sprite.body.skipQuadTree)
{
var bounds = {};
for (var i = 0; i < group.hash.length; i++)
{
var object1 = group.hash[i];
// Skip duff entries - we can't check a non-existent sprite or one with no body
if (!object1 || !object1.exists || !object1.body)
{
continue;
}
// Inject the Body bounds data into the bounds object
bounds = object1.body.getBounds(bounds);
// Skip items either side of the sprite
if (this.sortDirection === Phaser.Physics.Arcade.LEFT_RIGHT)
{
if (sprite.body.right < bounds.x)
{
break;
}
else if (bounds.right < sprite.body.x)
{
continue;
}
}
else if (this.sortDirection === Phaser.Physics.Arcade.RIGHT_LEFT)
{
if (sprite.body.x > bounds.right)
{
break;
}
else if (bounds.x > sprite.body.right)
{
continue;
}
}
else if (this.sortDirection === Phaser.Physics.Arcade.TOP_BOTTOM)
{
if (sprite.body.bottom < bounds.y)
{
break;
}
else if (bounds.bottom < sprite.body.y)
{
continue;
}
}
else if (this.sortDirection === Phaser.Physics.Arcade.BOTTOM_TOP)
{
if (sprite.body.y > bounds.bottom)
{
break;
}
else if (bounds.y > sprite.body.bottom)
{
continue;
}
}
this.collideSpriteVsSprite(sprite, object1, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
else
{
// What is the sprite colliding with in the quadtree?
this.quadTree.clear();
this.quadTree.reset(this.game.world.bounds.x, this.game.world.bounds.y, this.game.world.bounds.width, this.game.world.bounds.height, this.maxObjects, this.maxLevels);
this.quadTree.populate(group);
var items = this.quadTree.retrieve(sprite);
for (var i = 0; i < items.length; i++)
{
// We have our potential suspects, are they in this group?
if (this.separate(sprite.body, items[i], processCallback, callbackContext, overlapOnly))
{
if (collideCallback)
{
collideCallback.call(callbackContext, sprite, items[i].sprite);
}
this._total++;
}
}
}
},
/**
* An internal function. Use Phaser.Physics.Arcade.collide instead.
*
* @method Phaser.Physics.Arcade#collideGroupVsSelf
* @private
* @param {Phaser.Group} group - The Group to check.
* @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
* @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
* @return {boolean} True if there was a collision, otherwise false.
*/
collideGroupVsSelf: function (group, collideCallback, processCallback, callbackContext, overlapOnly)
{
if (group.length === 0)
{
return;
}
for (var i = 0; i < group.hash.length; i++)
{
var bounds1 = {};
var object1 = group.hash[i];
// Skip duff entries - we can't check a non-existent sprite or one with no body
if (!object1 || !object1.exists || !object1.body)
{
continue;
}
// Inject the Body bounds data into the bounds1 object
bounds1 = object1.body.getBounds(bounds1);
for (var j = i + 1; j < group.hash.length; j++)
{
var bounds2 = {};
var object2 = group.hash[j];
// Skip duff entries - we can't check a non-existent sprite or one with no body
if (!object2 || !object2.exists || !object2.body)
{
continue;
}
// Inject the Body bounds data into the bounds2 object
bounds2 = object2.body.getBounds(bounds2);
// Skip items either side of the sprite
if (this.sortDirection === Phaser.Physics.Arcade.LEFT_RIGHT)
{
if (bounds1.right < bounds2.x)
{
break;
}
else if (bounds2.right < bounds1.x)
{
continue;
}
}
else if (this.sortDirection === Phaser.Physics.Arcade.RIGHT_LEFT)
{
if (bounds1.x > bounds2.right)
{
continue;
}
else if (bounds2.x > bounds1.right)
{
break;
}
}
else if (this.sortDirection === Phaser.Physics.Arcade.TOP_BOTTOM)
{
if (bounds1.bottom < bounds2.y)
{
continue;
}
else if (bounds2.bottom < bounds1.y)
{
break;
}
}
else if (this.sortDirection === Phaser.Physics.Arcade.BOTTOM_TOP)
{
if (bounds1.y > bounds2.bottom)
{
continue;
}
else if (bounds2.y > object1.body.bottom)
{
break;
}
}
this.collideSpriteVsSprite(object1, object2, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
},
/**
* An internal function. Use Phaser.Physics.Arcade.collide instead.
*
* @method Phaser.Physics.Arcade#collideGroupVsGroup
* @private
* @param {Phaser.Group} group1 - The first Group to check.
* @param {Phaser.Group} group2 - The second Group to check.
* @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
* @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
*/
collideGroupVsGroup: function (group1, group2, collideCallback, processCallback, callbackContext, overlapOnly)
{
if (group1.length === 0 || group2.length === 0)
{
return;
}
for (var i = 0; i < group1.children.length; i++)
{
if (group1.children[i].exists)
{
if (group1.children[i].physicsType === Phaser.GROUP)
{
this.collideGroupVsGroup(group1.children[i], group2, collideCallback, processCallback, callbackContext, overlapOnly);
}
else
{
this.collideSpriteVsGroup(group1.children[i], group2, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
}
},
/**
* The core separation function to separate two physics bodies.
*
* @private
* @method Phaser.Physics.Arcade#separate
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body object to separate.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body object to separate.
* @param {function} [processCallback=null] - A callback function that lets you perform additional checks against the two objects if they overlap. If this function is set then the sprites will only be collided if it returns true.
* @param {object} [callbackContext] - The context in which to run the process callback.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
* @return {boolean} Returns true if the bodies collided, otherwise false.
*/
separate: function (body1, body2, processCallback, callbackContext, overlapOnly)
{
if (
!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.sprite, body2.sprite) === 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)
{
var bodyRect = (body1.isCircle) ? body2 : body1;
var bodyCircle = (body1.isCircle) ? body1 : body2;
var rect = {
x: bodyRect.x,
y: bodyRect.y,
right: bodyRect.right,
bottom: bodyRect.bottom
};
var 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);
}
}
}
var resultX = false;
var resultY = false;
// Do we separate on x or y first?
if (this.forceX || Math.abs(this.gravity.y + body1.gravity.y) < Math.abs(this.gravity.x + body1.gravity.x))
{
resultX = this.separateX(body1, body2, overlapOnly);
// Are they still intersecting? Let's do the other axis then
if (this.intersects(body1, body2))
{
resultY = this.separateY(body1, body2, overlapOnly);
}
}
else
{
resultY = this.separateY(body1, body2, overlapOnly);
// Are they still intersecting? Let's do the other axis then
if (this.intersects(body1, body2))
{
resultX = this.separateX(body1, body2, overlapOnly);
}
}
var result = (resultX || resultY);
if (result)
{
if (overlapOnly)
{
if (body1.onOverlap)
{
body1.onOverlap.dispatch(body1.sprite, body2.sprite);
}
if (body2.onOverlap)
{
body2.onOverlap.dispatch(body2.sprite, body1.sprite);
}
}
else
{
if (body1.onCollide)
{
body1.onCollide.dispatch(body1.sprite, body2.sprite);
}
if (body2.onCollide)
{
body2.onCollide.dispatch(body2.sprite, body1.sprite);
}
}
}
return result;
},
/**
* Check for intersection against two bodies.
*
* @method Phaser.Physics.Arcade#intersects
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body object to check.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body object to check.
* @return {boolean} True if they intersect, otherwise false.
*/
intersects: function (body1, body2)
{
if (body1 === body2)
{
return false;
}
if (body1.isCircle)
{
if (body2.isCircle)
{
// Circle vs. Circle
return Phaser.Math.distance(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
if (body2.isCircle)
{
// Rect vs. Circle
return this.circleBodyIntersects(body2, body1);
}
else
{
// Rect vs. Rect
if (body1.right <= body2.position.x)
{
return false;
}
if (body1.bottom <= body2.position.y)
{
return false;
}
if (body1.position.x >= body2.right)
{
return false;
}
if (body1.position.y >= body2.bottom)
{
return false;
}
return true;
}
},
/**
* Checks to see if a circular Body intersects with a Rectangular Body.
*
* @method Phaser.Physics.Arcade#circleBodyIntersects
* @param {Phaser.Physics.Arcade.Body} circle - The Body with `isCircle` set.
* @param {Phaser.Physics.Arcade.Body} body - The Body with `isCircle` not set (i.e. uses Rectangle shape)
* @return {boolean} Returns true if the bodies intersect, otherwise false.
*/
circleBodyIntersects: function (circle, body)
{
var x = Phaser.Math.clamp(circle.center.x, body.left, body.right);
var y = Phaser.Math.clamp(circle.center.y, body.top, body.bottom);
var dx = (circle.center.x - x) * (circle.center.x - x);
var dy = (circle.center.y - y) * (circle.center.y - y);
return (dx + dy) <= (circle.halfWidth * circle.halfWidth);
},
/**
* The core separation function to separate two circular physics bodies.
*
* @method Phaser.Physics.Arcade#separateCircle
* @private
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body to separate. Must have `Body.isCircle` true and a positive `radius`.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body to separate. Must have `Body.isCircle` true and a positive `radius`.
* @param {boolean} overlapOnly - If true the bodies will only have their overlap data set, no separation or exchange of velocity will take place.
* @return {boolean} Returns true if the bodies were separated or overlap, otherwise false.
*/
separateCircle: function (body1, body2, overlapOnly)
{
// Set the bounding box overlap values
this.getOverlapX(body1, body2);
this.getOverlapY(body1, body2);
var dx = body2.center.x - body1.center.x;
var dy = body2.center.y - body1.center.y;
var angleCollision = Math.atan2(dy, dx);
var overlap = 0;
if (body1.isCircle !== body2.isCircle)
{
var 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
};
var 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 = Phaser.Math.distance(circle.x, circle.y, rect.x, rect.y) - circle.radius;
}
else if (circle.x > rect.right)
{
overlap = Phaser.Math.distance(circle.x, circle.y, rect.right, rect.y) - circle.radius;
}
}
else if (circle.y > rect.bottom)
{
if (circle.x < rect.x)
{
overlap = Phaser.Math.distance(circle.x, circle.y, rect.x, rect.bottom) - circle.radius;
}
else if (circle.x > rect.right)
{
overlap = Phaser.Math.distance(circle.x, circle.y, rect.right, rect.bottom) - circle.radius;
}
}
overlap *= -1;
}
else
{
overlap = (body1.halfWidth + body2.halfWidth) - Phaser.Math.distance(body1.center.x, body1.center.y, body2.center.x, body2.center.y);
}
// 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)
{
if (body1.onOverlap)
{
body1.onOverlap.dispatch(body1.sprite, body2.sprite);
}
if (body2.onOverlap)
{
body2.onOverlap.dispatch(body2.sprite, body1.sprite);
}
}
// return true if there was some overlap, otherwise false
return (overlap !== 0);
}
// Transform the velocity vector to the coordinate system oriented along the direction of impact.
// This is done to eliminate the vertical component of the velocity
var v1 = {
x: body1.velocity.x * Math.cos(angleCollision) + body1.velocity.y * Math.sin(angleCollision),
y: -body1.velocity.x * Math.sin(angleCollision) + body1.velocity.y * Math.cos(angleCollision)
};
var v2 = {
x: body2.velocity.x * Math.cos(angleCollision) + body2.velocity.y * Math.sin(angleCollision),
y: -body2.velocity.x * Math.sin(angleCollision) + body2.velocity.y * Math.cos(angleCollision)
};
// We expect the new velocity after impact
var tempVel1 = ((body1.mass - body2.mass) * v1.x + 2 * body2.mass * v2.x) / (body1.mass + body2.mass);
var tempVel2 = (2 * body1.mass * v1.x + (body2.mass - body1.mass) * v2.x) / (body1.mass + body2.mass);
// We convert the vector to the original coordinate system and multiplied by factor of rebound
if (!body1.immovable)
{
body1.velocity.x = (tempVel1 * Math.cos(angleCollision) - v1.y * Math.sin(angleCollision)) * body1.bounce.x;
body1.velocity.y = (v1.y * Math.cos(angleCollision) + tempVel1 * Math.sin(angleCollision)) * body1.bounce.y;
}
if (!body2.immovable)
{
body2.velocity.x = (tempVel2 * Math.cos(angleCollision) - v2.y * Math.sin(angleCollision)) * body2.bounce.x;
body2.velocity.y = (v2.y * Math.cos(angleCollision) + tempVel2 * Math.sin(angleCollision)) * body2.bounce.y;
}
// When the collision angle is almost perpendicular to the total initial velocity vector
// (collision on a tangent) vector direction can be determined incorrectly.
// This code fixes the problem
if (Math.abs(angleCollision) < Math.PI / 2)
{
if ((body1.velocity.x > 0) && !body1.immovable && (body2.velocity.x > body1.velocity.x))
{
body1.velocity.x *= -1;
}
else if ((body2.velocity.x < 0) && !body2.immovable && (body1.velocity.x < body2.velocity.x))
{
body2.velocity.x *= -1;
}
else if ((body1.velocity.y > 0) && !body1.immovable && (body2.velocity.y > body1.velocity.y))
{
body1.velocity.y *= -1;
}
else if ((body2.velocity.y < 0) && !body2.immovable && (body1.velocity.y < body2.velocity.y))
{
body2.velocity.y *= -1;
}
}
else if (Math.abs(angleCollision) > Math.PI / 2)
{
if ((body1.velocity.x < 0) && !body1.immovable && (body2.velocity.x < body1.velocity.x))
{
body1.velocity.x *= -1;
}
else if ((body2.velocity.x > 0) && !body2.immovable && (body1.velocity.x > body2.velocity.x))
{
body2.velocity.x *= -1;
}
else if ((body1.velocity.y < 0) && !body1.immovable && (body2.velocity.y < body1.velocity.y))
{
body1.velocity.y *= -1;
}
else if ((body2.velocity.y > 0) && !body2.immovable && (body1.velocity.x > body2.velocity.y))
{
body2.velocity.y *= -1;
}
}
if (!body1.immovable)
{
body1.x += (body1.velocity.x * this.game.time.physicsElapsed) - overlap * Math.cos(angleCollision);
body1.y += (body1.velocity.y * this.game.time.physicsElapsed) - overlap * Math.sin(angleCollision);
}
if (!body2.immovable)
{
body2.x += (body2.velocity.x * this.game.time.physicsElapsed) + overlap * Math.cos(angleCollision);
body2.y += (body2.velocity.y * this.game.time.physicsElapsed) + overlap * Math.sin(angleCollision);
}
if (body1.onCollide)
{
body1.onCollide.dispatch(body1.sprite, body2.sprite);
}
if (body2.onCollide)
{
body2.onCollide.dispatch(body2.sprite, body1.sprite);
}
return true;
},
/**
* Calculates the horizontal overlap between two Bodies and sets their properties accordingly, including:
* `touching.left`, `touching.right` and `overlapX`.
*
* @method Phaser.Physics.Arcade#getOverlapX
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body to separate.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body to separate.
* @param {boolean} overlapOnly - Is this an overlap only check, or part of separation?
* @return {float} Returns the amount of horizontal overlap between the two bodies.
*/
getOverlapX: function (body1, body2, overlapOnly)
{
var overlap = 0;
var maxOverlap = body1.deltaAbsX() + body2.deltaAbsX() + this.OVERLAP_BIAS;
if (body1.deltaX() === 0 && body2.deltaX() === 0)
{
// They overlap but neither of them are moving
body1.embedded = true;
body2.embedded = true;
}
else if (body1.deltaX() > body2.deltaX())
{
// Body1 is moving right and / or Body2 is moving left
overlap = body1.right - body2.x;
if ((overlap > maxOverlap && !overlapOnly) || body1.checkCollision.right === false || body2.checkCollision.left === false)
{
overlap = 0;
}
else
{
body1.touching.none = false;
body1.touching.right = true;
body2.touching.none = false;
body2.touching.left = true;
}
}
else if (body1.deltaX() < body2.deltaX())
{
// Body1 is moving left and/or Body2 is moving right
overlap = body1.x - body2.width - body2.x;
if ((-overlap > maxOverlap && !overlapOnly) || body1.checkCollision.left === false || body2.checkCollision.right === false)
{
overlap = 0;
}
else
{
body1.touching.none = false;
body1.touching.left = true;
body2.touching.none = false;
body2.touching.right = true;
}
}
// Resets the overlapX to zero if there is no overlap, or to the actual pixel value if there is
body1.overlapX = overlap;
body2.overlapX = overlap;
return overlap;
},
/**
* Calculates the vertical overlap between two Bodies and sets their properties accordingly, including:
* `touching.up`, `touching.down` and `overlapY`.
*
* @method Phaser.Physics.Arcade#getOverlapY
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body to separate.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body to separate.
* @param {boolean} overlapOnly - Is this an overlap only check, or part of separation?
* @return {float} Returns the amount of vertical overlap between the two bodies.
*/
getOverlapY: function (body1, body2, overlapOnly)
{
var overlap = 0;
var maxOverlap = body1.deltaAbsY() + body2.deltaAbsY() + this.OVERLAP_BIAS;
if (body1.deltaY() === 0 && body2.deltaY() === 0)
{
// They overlap but neither of them are moving
body1.embedded = true;
body2.embedded = true;
}
else if (body1.deltaY() > body2.deltaY())
{
// Body1 is moving down and/or Body2 is moving up
overlap = body1.bottom - body2.y;
if ((overlap > maxOverlap && !overlapOnly) || body1.checkCollision.down === false || body2.checkCollision.up === false)
{
overlap = 0;
}
else
{
body1.touching.none = false;
body1.touching.down = true;
body2.touching.none = false;
body2.touching.up = true;
}
}
else if (body1.deltaY() < body2.deltaY())
{
// Body1 is moving up and/or Body2 is moving down
overlap = body1.y - body2.bottom;
if ((-overlap > maxOverlap && !overlapOnly) || body1.checkCollision.up === false || body2.checkCollision.down === false)
{
overlap = 0;
}
else
{
body1.touching.none = false;
body1.touching.up = true;
body2.touching.none = false;
body2.touching.down = true;
}
}
// Resets the overlapY to zero if there is no overlap, or to the actual pixel value if there is
body1.overlapY = overlap;
body2.overlapY = overlap;
return overlap;
},
/**
* The core separation function to separate two physics bodies on the x axis.
*
* @method Phaser.Physics.Arcade#separateX
* @private
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body to separate.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body to separate.
* @param {boolean} overlapOnly - If true the bodies will only have their overlap data set, no separation or exchange of velocity will take place.
* @return {boolean} Returns true if the bodies were separated or overlap, otherwise false.
*/
separateX: function (body1, body2, overlapOnly)
{
var overlap = this.getOverlapX(body1, body2, overlapOnly);
// 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)
{
// return true if there was some overlap, otherwise false
return (overlap !== 0) || (body1.embedded && body2.embedded);
}
// Adjust their positions and velocities accordingly (if there was any overlap)
var v1 = body1.velocity.x;
var v2 = body2.velocity.x;
if (!body1.immovable && !body2.immovable)
{
overlap *= 0.5;
body1.x -= overlap;
body2.x += overlap;
var nv1 = Math.sqrt((v2 * v2 * body2.mass) / body1.mass) * ((v2 > 0) ? 1 : -1);
var nv2 = Math.sqrt((v1 * v1 * body1.mass) / body2.mass) * ((v1 > 0) ? 1 : -1);
var avg = (nv1 + nv2) * 0.5;
nv1 -= avg;
nv2 -= avg;
body1.velocity.x = avg + nv1 * body1.bounce.x;
body2.velocity.x = avg + nv2 * body2.bounce.x;
}
else if (!body1.immovable)
{
body1.x -= overlap;
body1.velocity.x = v2 - v1 * body1.bounce.x;
// This is special case code that handles things like vertically moving platforms you can ride
if (body2.moves)
{
body1.y += (body2.y - body2.prev.y) * body2.friction.y;
}
}
else
{
body2.x += overlap;
body2.velocity.x = v1 - v2 * body2.bounce.x;
// This is special case code that handles things like vertically moving platforms you can ride
if (body1.moves)
{
body2.y += (body1.y - body1.prev.y) * body1.friction.y;
}
}
// If we got this far then there WAS overlap, and separation is complete, so return true
return true;
},
/**
* The core separation function to separate two physics bodies on the y axis.
*
* @private
* @method Phaser.Physics.Arcade#separateY
* @param {Phaser.Physics.Arcade.Body} body1 - The first Body to separate.
* @param {Phaser.Physics.Arcade.Body} body2 - The second Body to separate.
* @param {boolean} overlapOnly - If true the bodies will only have their overlap data set, no separation or exchange of velocity will take place.
* @return {boolean} Returns true if the bodies were separated or overlap, otherwise false.
*/
separateY: function (body1, body2, overlapOnly)
{
var overlap = this.getOverlapY(body1, body2, overlapOnly);
// Can't separate two immovable bodies, or a body with its own custom separation logic
if (overlapOnly || overlap === 0 || (body1.immovable && body2.immovable) || body1.customSeparateY || body2.customSeparateY)
{
// return true if there was some overlap, otherwise false
return (overlap !== 0) || (body1.embedded && body2.embedded);
}
// Adjust their positions and velocities accordingly (if there was any overlap)
var v1 = body1.velocity.y;
var v2 = body2.velocity.y;
if (!body1.immovable && !body2.immovable)
{
overlap *= 0.5;
body1.y -= overlap;
body2.y += overlap;
var nv1 = Math.sqrt((v2 * v2 * body2.mass) / body1.mass) * ((v2 > 0) ? 1 : -1);
var nv2 = Math.sqrt((v1 * v1 * body1.mass) / body2.mass) * ((v1 > 0) ? 1 : -1);
var avg = (nv1 + nv2) * 0.5;
nv1 -= avg;
nv2 -= avg;
body1.velocity.y = avg + nv1 * body1.bounce.y;
body2.velocity.y = avg + nv2 * body2.bounce.y;
}
else if (!body1.immovable)
{
body1.y -= overlap;
body1.velocity.y = v2 - v1 * body1.bounce.y;
// This is special case code that handles things like horizontal moving platforms you can ride
if (body2.moves)
{
body1.x += (body2.x - body2.prev.x) * body2.friction.x;
}
}
else
{
body2.y += overlap;
body2.velocity.y = v1 - v2 * body2.bounce.y;
// This is special case code that handles things like horizontal moving platforms you can ride
if (body1.moves)
{
body2.x += (body1.x - body1.prev.x) * body1.friction.x;
}
}
// If we got this far then there WAS overlap, and separation is complete, so return true
return true;
},
/**
* Given a Group and a Pointer this will check to see which Group children overlap with the Pointer coordinates.
* Each child will be sent to the given callback for further processing.
* Note that the children are not checked for depth order, but simply if they overlap the Pointer or not.
*
* @method Phaser.Physics.Arcade#getObjectsUnderPointer
* @param {Phaser.Pointer} pointer - The Pointer to check.
* @param {Phaser.Group} group - The Group to check.
* @param {function} [callback] - A callback function that is called if the object overlaps with the Pointer. The callback will be sent two parameters: the Pointer and the Object that overlapped with it.
* @param {object} [callbackContext] - The context in which to run the callback.
* @return {PIXI.DisplayObject[]} An array of the Sprites from the Group that overlapped the Pointer coordinates.
*/
getObjectsUnderPointer: function (pointer, group, callback, callbackContext)
{
if (group.length === 0 || !pointer.exists)
{
return;
}
return this.getObjectsAtLocation(pointer.x, pointer.y, group, callback, callbackContext, pointer);
},
/**
* Given a Group and a location this will check to see which Group children overlap with the coordinates.
* Each child will be sent to the given callback for further processing.
* Note that the children are not checked for depth order, but simply if they overlap the coordinate or not.
*
* @method Phaser.Physics.Arcade#getObjectsAtLocation
* @param {number} x - The x coordinate to check.
* @param {number} y - The y coordinate to check.
* @param {Phaser.Group} group - The Group to check.
* @param {function} [callback] - A callback function that is called if the object overlaps the coordinates. The callback will be sent two parameters: the callbackArg and the Object that overlapped the location.
* @param {object} [callbackContext] - The context in which to run the callback.
* @param {object} [callbackArg] - An argument to pass to the callback.
* @return {PIXI.DisplayObject[]} An array of the Sprites from the Group that overlapped the coordinates.
*/
getObjectsAtLocation: function (x, y, group, callback, callbackContext, callbackArg)
{
this.quadTree.clear();
this.quadTree.reset(this.game.world.bounds.x, this.game.world.bounds.y, this.game.world.bounds.width, this.game.world.bounds.height, this.maxObjects, this.maxLevels);
this.quadTree.populate(group);
var rect = new Phaser.Rectangle(x, y, 1, 1);
var output = [];
var items = this.quadTree.retrieve(rect);
for (var i = 0; i < items.length; i++)
{
if (items[i].hitTest(x, y))
{
if (callback)
{
callback.call(callbackContext, callbackArg, items[i].sprite);
}
output.push(items[i].sprite);
}
}
return output;
},
/**
* Move the given display object towards the destination object at a steady velocity.
* If you specify a maxTime then it will adjust the speed (overwriting what you set) so it arrives at the destination in that number of seconds.
* Timings are approximate due to the way browser timers work. Allow for a variance of +- 50ms.
* Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
* Note: The display object doesn't stop moving once it reaches the destination coordinates.
* Note: Doesn't take into account acceleration, maxVelocity or drag (if you've set drag or acceleration too high this object may not move at all)
*
* @method Phaser.Physics.Arcade#moveToObject
* @param {any} displayObject - The display object to move.
* @param {any} destination - The display object to move towards. Can be any object but must have visible x/y properties.
* @param {number} [speed=60] - The speed it will move, in pixels per second (default is 60 pixels/sec)
* @param {number} [maxTime=0] - Time given in milliseconds (1000 = 1 sec). If set the speed is adjusted so the object will arrive at destination in the given number of ms.
* @return {number} The angle (in radians) that the object should be visually set to in order to match its new velocity.
*/
moveToObject: function (displayObject, destination, speed, maxTime)
{
if (speed === undefined) { speed = 60; }
if (maxTime === undefined) { maxTime = 0; }
var angle = Phaser.Point.angle(destination, displayObject);
if (maxTime > 0)
{
// We know how many pixels we need to move, but how fast?
speed = this.distanceBetween(displayObject, destination) / (maxTime / 1000);
}
displayObject.body.velocity.setToPolar(angle, speed);
return angle;
},
/**
* Move the given display object towards the pointer at a steady velocity. If no pointer is given it will use Phaser.Input.activePointer.
* If you specify a maxTime then it will adjust the speed (over-writing what you set) so it arrives at the destination in that number of seconds.
* Timings are approximate due to the way browser timers work. Allow for a variance of +- 50ms.
* Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
* Note: The display object doesn't stop moving once it reaches the destination coordinates.
*
* @method Phaser.Physics.Arcade#moveToPointer
* @param {any} displayObject - The display object to move.
* @param {number} [speed=60] - The speed it will move, in pixels per second (default is 60 pixels/sec)
* @param {Phaser.Pointer} [pointer] - The pointer to move towards. Defaults to Phaser.Input.activePointer.
* @param {number} [maxTime=0] - Time given in milliseconds (1000 = 1 sec). If set the speed is adjusted so the object will arrive at destination in the given number of ms.
* @return {number} The angle (in radians) that the object should be visually set to in order to match its new velocity.
*/
moveToPointer: function (displayObject, speed, pointer, maxTime)
{
if (speed === undefined) { speed = 60; }
pointer = pointer || this.game.input.activePointer;
if (maxTime === undefined) { maxTime = 0; }
var angle = this.angleToPointer(displayObject, pointer);
if (maxTime > 0)
{
// We know how many pixels we need to move, but how fast?
speed = this.distanceToPointer(displayObject, pointer) / (maxTime / 1000);
}
displayObject.body.velocity.setToPolar(angle, speed);
return angle;
},
/**
* Move the given display object towards the x/y coordinates at a steady velocity.
* If you specify a maxTime then it will adjust the speed (over-writing what you set) so it arrives at the destination in that number of seconds.
* Timings are approximate due to the way browser timers work. Allow for a variance of +- 50ms.
* Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
* Note: The display object doesn't stop moving once it reaches the destination coordinates.
* Note: Doesn't take into account acceleration, maxVelocity or drag (if you've set drag or acceleration too high this object may not move at all)
*
* @method Phaser.Physics.Arcade#moveToXY
* @param {any} displayObject - The display object to move.
* @param {number} x - The x coordinate to move towards.
* @param {number} y - The y coordinate to move towards.
* @param {number} [speed=60] - The speed it will move, in pixels per second (default is 60 pixels/sec)
* @param {number} [maxTime=0] - Time given in milliseconds (1000 = 1 sec). If set the speed is adjusted so the object will arrive at destination in the given number of ms.
* @return {number} The angle (in radians) that the object should be visually set to in order to match its new velocity.
*/
moveToXY: function (displayObject, x, y, speed, maxTime)
{
if (speed === undefined) { speed = 60; }
if (maxTime === undefined) { maxTime = 0; }
var angle = Math.atan2(y - displayObject.y, x - displayObject.x);
if (maxTime > 0)
{
// We know how many pixels we need to move, but how fast?
speed = this.distanceToXY(displayObject, x, y) / (maxTime / 1000);
}
displayObject.body.velocity.setToPolar(angle, speed);
return angle;
},
/**
* Given the angle (in degrees) and speed calculate the velocity and return it as a Point object, or set it to the given point object.
* One way to use this is: velocityFromAngle(angle, 200, sprite.velocity) which will set the values directly to the sprites velocity and not create a new Point object.
*
* @method Phaser.Physics.Arcade#velocityFromAngle
* @param {number} angle - The angle in degrees calculated in clockwise positive direction (down = 90 degrees positive, right = 0 degrees positive, up = 90 degrees negative)
* @param {number} [speed=60] - The speed it will move, in pixels per second sq.
* @param {Phaser.Point|object} [point] - The Point object in which the x and y properties will be set to the calculated velocity.
* @return {Phaser.Point} - A Point where point.x contains the velocity x value and point.y contains the velocity y value.
*/
velocityFromAngle: function (angle, speed, point)
{
if (speed === undefined) { speed = 60; }
point = point || new Phaser.Point();
return point.setToPolar(angle, speed, true);
},
/**
* Given the rotation (in radians) and speed calculate the velocity and return it as a Point object, or set it to the given point object.
* One way to use this is: velocityFromRotation(rotation, 200, sprite.velocity) which will set the values directly to the sprites velocity and not create a new Point object.
*
* @method Phaser.Physics.Arcade#velocityFromRotation
* @param {number} rotation - The angle in radians.
* @param {number} [speed=60] - The speed it will move, in pixels per second sq.
* @param {Phaser.Point|object} [point] - The Point object in which the x and y properties will be set to the calculated velocity.
* @return {Phaser.Point} - A Point where point.x contains the velocity x value and point.y contains the velocity y value.
*/
velocityFromRotation: function (rotation, speed, point)
{
if (speed === undefined) { speed = 60; }
point = point || new Phaser.Point();
return point.setToPolar(rotation, speed);
},
/**
* Given the rotation (in radians) and speed calculate the acceleration and return it as a Point object, or set it to the given point object.
* One way to use this is: accelerationFromRotation(rotation, 200, sprite.acceleration) which will set the values directly to the sprites acceleration and not create a new Point object.
*
* @method Phaser.Physics.Arcade#accelerationFromRotation
* @param {number} rotation - The angle in radians.
* @param {number} [speed=60] - The speed it will move, in pixels per second sq.
* @param {Phaser.Point|object} [point] - The Point object in which the x and y properties will be set to the calculated acceleration.
* @return {Phaser.Point} - A Point where point.x contains the acceleration x value and point.y contains the acceleration y value.
*/
accelerationFromRotation: function (rotation, speed, point)
{
if (speed === undefined) { speed = 60; }
point = point || new Phaser.Point();
return point.setToPolar(rotation, speed);
},
/**
* Sets the acceleration.x/y property on the display object so it will move towards the target at the given speed (in pixels per second sq.)
* You must give a maximum speed value, beyond which the display object won't go any faster.
* Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
* Note: The display object doesn't stop moving once it reaches the destination coordinates.
*
* @method Phaser.Physics.Arcade#accelerateToObject
* @param {any} displayObject - The display object to move.
* @param {any} destination - The display object to move towards. Can be any object but must have visible x/y properties.
* @param {number} [speed=60] - The speed it will accelerate in pixels per second.
* @param {number} [xSpeedMax=500] - The maximum x velocity the display object can reach.
* @param {number} [ySpeedMax=500] - The maximum y velocity the display object can reach.
* @return {number} The angle (in radians) that the object should be visually set to in order to match its new trajectory.
*/
accelerateToObject: function (displayObject, destination, speed, xSpeedMax, ySpeedMax)
{
if (speed === undefined) { speed = 60; }
if (xSpeedMax === undefined) { xSpeedMax = 1000; }
if (ySpeedMax === undefined) { ySpeedMax = 1000; }
var angle = this.angleBetween(displayObject, destination);
displayObject.body.acceleration.setToPolar(angle, speed);
displayObject.body.maxVelocity.setTo(xSpeedMax, ySpeedMax);
return angle;
},
/**
* Sets the acceleration.x/y property on the display object so it will move towards the target at the given speed (in pixels per second sq.)
* You must give a maximum speed value, beyond which the display object won't go any faster.
* Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
* Note: The display object doesn't stop moving once it reaches the destination coordinates.
*
* @method Phaser.Physics.Arcade#accelerateToPointer
* @param {any} displayObject - The display object to move.
* @param {Phaser.Pointer} [pointer] - The pointer to move towards. Defaults to Phaser.Input.activePointer.
* @param {number} [speed=60] - The speed it will accelerate in pixels per second.
* @param {number} [xSpeedMax=500] - The maximum x velocity the display object can reach.
* @param {number} [ySpeedMax=500] - The maximum y velocity the display object can reach.
* @return {number} The angle (in radians) that the object should be visually set to in order to match its new trajectory.
*/
accelerateToPointer: function (displayObject, pointer, speed, xSpeedMax, ySpeedMax)
{
if (speed === undefined) { speed = 60; }
if (pointer === undefined) { pointer = this.game.input.activePointer; }
if (xSpeedMax === undefined) { xSpeedMax = 1000; }
if (ySpeedMax === undefined) { ySpeedMax = 1000; }
var angle = this.angleToPointer(displayObject, pointer);
displayObject.body.acceleration.setToPolar(angle, speed);
displayObject.body.maxVelocity.setTo(xSpeedMax, ySpeedMax);
return angle;
},
/**
* Sets the acceleration.x/y property on the display object so it will move towards the x/y coordinates at the given speed (in pixels per second sq.)
* You must give a maximum speed value, beyond which the display object won't go any faster.
* Note: The display object does not continuously track the target. If the target changes location during transit the display object will not modify its course.
* Note: The display object doesn't stop moving once it reaches the destination coordinates.
*
* @method Phaser.Physics.Arcade#accelerateToXY
* @param {any} displayObject - The display object to move.
* @param {number} x - The x coordinate to accelerate towards.
* @param {number} y - The y coordinate to accelerate towards.
* @param {number} [speed=60] - The speed it will accelerate in pixels per second.
* @param {number} [xSpeedMax=500] - The maximum x velocity the display object can reach.
* @param {number} [ySpeedMax=500] - The maximum y velocity the display object can reach.
* @return {number} The angle (in radians) that the object should be visually set to in order to match its new trajectory.
*/
accelerateToXY: function (displayObject, x, y, speed, xSpeedMax, ySpeedMax)
{
if (speed === undefined) { speed = 60; }
if (xSpeedMax === undefined) { xSpeedMax = 1000; }
if (ySpeedMax === undefined) { ySpeedMax = 1000; }
var angle = this.angleToXY(displayObject, x, y);
displayObject.body.acceleration.setTo(angle, speed);
displayObject.body.maxVelocity.setTo(xSpeedMax, ySpeedMax);
return angle;
},
/**
* Find the distance between two display objects (like Sprites).
*
* The optional `world` argument allows you to return the result based on the Game Objects `world` property,
* instead of its `x` and `y` values. This is useful of the object has been nested inside an offset Group,
* or parent Game Object.
*
* If you have nested objects and need to calculate the distance between their centers in World coordinates,
* set their anchors to (0.5, 0.5) and use the `world` argument.
*
* If objects aren't nested or they share a parent's offset, you can calculate the distance between their
* centers with the `useCenter` argument, regardless of their anchor values.
*
* @method Phaser.Physics.Arcade#distanceBetween
* @param {any} source - The Display Object to test from.
* @param {any} target - The Display Object to test to.
* @param {boolean} [world=false] - Calculate the distance using World coordinates (true), or Object coordinates (false, the default). If `useCenter` is true, this value is ignored.
* @param {boolean} [useCenter=false] - Calculate the distance using the {@link Phaser.Sprite#centerX} and {@link Phaser.Sprite#centerY} coordinates. If true, this value overrides the `world` argument.
* @return {number} The distance between the source and target objects.
*/
distanceBetween: function (source, target, world, useCenter)
{
if (world === undefined) { world = false; }
var dx;
var dy;
if (useCenter)
{
dx = source.centerX - target.centerX;
dy = source.centerY - target.centerY;
}
else if (world)
{
dx = source.world.x - target.world.x;
dy = source.world.y - target.world.y;
}
else
{
dx = source.x - target.x;
dy = source.y - target.y;
}
return Math.sqrt(dx * dx + dy * dy);
},
/**
* Find the distance between a display object (like a Sprite) and the given x/y coordinates.
* The calculation is made from the display objects x/y coordinate. This may be the top-left if its anchor hasn't been changed.
* If you need to calculate from the center of a display object instead use {@link #distanceBetween} with the `useCenter` argument.
*
* The optional `world` argument allows you to return the result based on the Game Objects `world` property,
* instead of its `x` and `y` values. This is useful of the object has been nested inside an offset Group,
* or parent Game Object.
*
* @method Phaser.Physics.Arcade#distanceToXY
* @param {any} displayObject - The Display Object to test from.
* @param {number} x - The x coordinate to move towards.
* @param {number} y - The y coordinate to move towards.
* @param {boolean} [world=false] - Calculate the distance using World coordinates (true), or Object coordinates (false, the default)
* @return {number} The distance between the object and the x/y coordinates.
*/
distanceToXY: function (displayObject, x, y, world)
{
if (world === undefined) { world = false; }
var dx = (world) ? displayObject.world.x - x : displayObject.x - x;
var dy = (world) ? displayObject.world.y - y : displayObject.y - y;
return Math.sqrt(dx * dx + dy * dy);
},
/**
* Find the distance between a display object (like a Sprite) and a Pointer. If no Pointer is given the Input.activePointer is used.
* The calculation is made from the display objects x/y coordinate. This may be the top-left if its anchor hasn't been changed.
* If you need to calculate from the center of a display object instead use {@link #distanceBetween} with the `useCenter` argument.
*
* The optional `world` argument allows you to return the result based on the Game Objects `world` property,
* instead of its `x` and `y` values. This is useful of the object has been nested inside an offset Group,
* or parent Game Object.
*
* @method Phaser.Physics.Arcade#distanceToPointer
* @param {any} displayObject - The Display Object to test from.
* @param {Phaser.Pointer} [pointer] - The Phaser.Pointer to test to. If none is given then Input.activePointer is used.
* @param {boolean} [world=false] - Calculate the distance using World coordinates (true), or Object coordinates (false, the default)
* @return {number} The distance between the object and the Pointer.
*/
distanceToPointer: function (displayObject, pointer, world)
{
if (pointer === undefined) { pointer = this.game.input.activePointer; }
if (world === undefined) { world = false; }
var dx = (world) ? displayObject.world.x - pointer.worldX : displayObject.x - pointer.worldX;
var dy = (world) ? displayObject.world.y - pointer.worldY : displayObject.y - pointer.worldY;
return Math.sqrt(dx * dx + dy * dy);
},
/**
* From a set of points or display objects, find the one closest to a source point or object.
*
* @method Phaser.Physics.Arcade#closest
* @param {any} source - The {@link Phaser.Point Point} or Display Object distances will be measured from.
* @param {any[]} targets - The {@link Phaser.Point Points} or Display Objects whose distances to the source will be compared.
* @param {boolean} [world=false] - Calculate the distance using World coordinates (true), or Object coordinates (false, the default). If `useCenter` is true, this value is ignored.
* @param {boolean} [useCenter=false] - Calculate the distance using the {@link Phaser.Sprite#centerX} and {@link Phaser.Sprite#centerY} coordinates. If true, this value overrides the `world` argument.
* @return {any} - The first target closest to the origin.
*/
closest: function (source, targets, world, useCenter)
{
var min = Infinity;
var closest = null;
for (var i = 0, len = targets.length; i < len; i++)
{
var target = targets[i];
var distance = this.distanceBetween(source, target, world, useCenter);
if (distance < min)
{
closest = target;
min = distance;
}
}
return closest;
},
/**
* From a set of points or display objects, find the one farthest from a source point or object.
*
* @method Phaser.Physics.Arcade#farthest
* @param {any} source - The {@link Phaser.Point Point} or Display Object distances will be measured from.
* @param {any[]} targets - The {@link Phaser.Point Points} or Display Objects whose distances to the source will be compared.
* @param {boolean} [world=false] - Calculate the distance using World coordinates (true), or Object coordinates (false, the default). If `useCenter` is true, this value is ignored.
* @param {boolean} [useCenter=false] - Calculate the distance using the {@link Phaser.Sprite#centerX} and {@link Phaser.Sprite#centerY} coordinates. If true, this value overrides the `world` argument.
* @return {any} - The target closest to the origin.
*/
farthest: function (source, targets, world, useCenter)
{
var max = -1;
var farthest = null;
for (var i = 0, len = targets.length; i < len; i++)
{
var target = targets[i];
var distance = this.distanceBetween(source, target, world, useCenter);
if (distance > max)
{
farthest = target;
max = distance;
}
}
return farthest;
},
/**
* Find the angle in radians between two display objects (like Sprites).
*
* The optional `world` argument allows you to return the result based on the Game Objects `world` property,
* instead of its `x` and `y` values. This is useful of the object has been nested inside an offset Group,
* or parent Game Object.
*
* @method Phaser.Physics.Arcade#angleBetween
* @param {any} source - The Display Object to test from.
* @param {any} target - The Display Object to test to.
* @param {boolean} [world=false] - Calculate the angle using World coordinates (true), or Object coordinates (false, the default)
* @return {number} The angle in radians between the source and target display objects.
*/
angleBetween: function (source, target, world)
{
if (world === undefined) { world = false; }
if (world)
{
return Phaser.Point.angle(target.world, source.world);
}
else
{
return Phaser.Point.angle(target, source);
}
},
/**
* Find the angle in radians between centers of two display objects (like Sprites).
*
* @method Phaser.Physics.Arcade#angleBetweenCenters
* @param {any} source - The Display Object to test from.
* @param {any} target - The Display Object to test to.
* @return {number} The angle in radians between the source and target display objects.
*/
angleBetweenCenters: function (source, target)
{
var dx = target.centerX - source.centerX;
var dy = target.centerY - source.centerY;
return Math.atan2(dy, dx);
},
/**
* Find the angle in radians between a display object (like a Sprite) and the given x/y coordinate.
*
* The optional `world` argument allows you to return the result based on the Game Objects `world` property,
* instead of its `x` and `y` values. This is useful of the object has been nested inside an offset Group,
* or parent Game Object.
*
* @method Phaser.Physics.Arcade#angleToXY
* @param {any} displayObject - The Display Object to test from.
* @param {number} x - The x coordinate to get the angle to.
* @param {number} y - The y coordinate to get the angle to.
* @param {boolean} [world=false] - Calculate the angle using World coordinates (true), or Object coordinates (false, the default)
* @return {number} The angle in radians between displayObject.x/y to Pointer.x/y
*/
angleToXY: function (displayObject, x, y, world)
{
if (world === undefined) { world = false; }
if (world)
{
return Math.atan2(y - displayObject.world.y, x - displayObject.world.x);
}
else
{
return Math.atan2(y - displayObject.y, x - displayObject.x);
}
},
/**
* Find the angle in radians between a display object (like a Sprite) and a Pointer, taking their x/y and center into account.
*
* The optional `world` argument allows you to return the result based on the Game Objects `world` property,
* instead of its `x` and `y` values. This is useful of the object has been nested inside an offset Group,
* or parent Game Object.
*
* @method Phaser.Physics.Arcade#angleToPointer
* @param {any} displayObject - The Display Object to test from.
* @param {Phaser.Pointer} [pointer] - The Phaser.Pointer to test to. If none is given then Input.activePointer is used.
* @param {boolean} [world=false] - Calculate the angle using World coordinates (true), or Object coordinates (false, the default)
* @return {number} The angle in radians between displayObject.x/y to Pointer.x/y
*/
angleToPointer: function (displayObject, pointer, world)
{
if (pointer === undefined) { pointer = this.game.input.activePointer; }
if (world === undefined) { world = false; }
if (world)
{
return Math.atan2(pointer.worldY - displayObject.world.y, pointer.worldX - displayObject.world.x);
}
else
{
return Math.atan2(pointer.worldY - displayObject.y, pointer.worldX - displayObject.x);
}
},
/**
* Find the angle in radians between a display object (like a Sprite) and a Pointer,
* taking their x/y and center into account relative to the world.
*
* @method Phaser.Physics.Arcade#worldAngleToPointer
* @param {any} displayObject - The DisplayObjerct to test from.
* @param {Phaser.Pointer} [pointer] - The Phaser.Pointer to test to. If none is given then Input.activePointer is used.
* @return {number} The angle in radians between displayObject.world.x/y to Pointer.worldX / worldY
*/
worldAngleToPointer: function (displayObject, pointer)
{
return this.angleToPointer(displayObject, pointer, true);
}
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Physics Body is linked to a single Sprite. All physics operations should be performed against the body rather than
* the Sprite itself. For example you can set the velocity, acceleration, bounce values etc all on the Body.
*
* @class Phaser.Physics.Arcade.Body
* @constructor
* @param {Phaser.Sprite} sprite - The Sprite object this physics body belongs to.
*/
Phaser.Physics.Arcade.Body = function (sprite)
{
/**
* @property {Phaser.Sprite} sprite - Reference to the parent Sprite.
*/
this.sprite = sprite;
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = sprite.game;
/**
* @property {number} type - The type of physics system this body belongs to.
*/
this.type = Phaser.Physics.ARCADE;
/**
* @property {boolean} enable - A disabled body won't be checked for any form of collision or overlap or have its pre/post updates run.
* @default
*/
this.enable = true;
/**
* If `true` this Body is using circular collision detection. If `false` it is using rectangular.
* Use `Body.setCircle` to control the collision shape this Body uses.
* @property {boolean} isCircle
* @default
* @readOnly
*/
this.isCircle = false;
/**
* The radius of the circular collision shape this Body is using if Body.setCircle has been enabled, relative to the Sprite's _texture_.
* If you wish to change the radius then call {@link #setCircle} again with the new value.
* If you wish to stop the Body using a circle then call {@link #setCircle} with a radius of zero (or undefined).
* The actual radius of the Body (at any Sprite scale) is equal to {@link #halfWidth} and the diameter is equal to {@link #width}.
* @property {number} radius
* @default
* @readOnly
*/
this.radius = 0;
/**
* @property {Phaser.Point} offset - The offset of the Physics Body from the Sprite's texture.
*/
this.offset = new Phaser.Point();
/**
* @property {Phaser.Point} position - The position of the physics body, equivalent to ({@link #left}, {@link #top}).
* @readonly
*/
this.position = new Phaser.Point(sprite.x, sprite.y);
/**
* @property {Phaser.Point} prev - The previous position of the physics body.
* @readonly
*/
this.prev = new Phaser.Point(this.position.x, this.position.y);
/**
* @property {boolean} allowRotation - Allow this Body to be rotated? (via angularVelocity, etc)
* @default
*/
this.allowRotation = true;
/**
* The Body's rotation in degrees, as calculated by its angularVelocity and angularAcceleration. Please understand that the collision Body
* itself never rotates, it is always axis-aligned. However these values are passed up to the parent Sprite and updates its rotation.
* @property {number} rotation
*/
this.rotation = sprite.angle;
/**
* @property {number} preRotation - The previous rotation of the physics body, in degrees.
* @readonly
*/
this.preRotation = sprite.angle;
/**
* @property {number} width - The calculated width of the physics body.
* @readonly
*/
this.width = sprite.width;
/**
* @property {number} height - The calculated height of the physics body.
* @readonly
*/
this.height = sprite.height;
/**
* @property {number} sourceWidth - The un-scaled original size.
* @readonly
*/
this.sourceWidth = sprite.width;
/**
* @property {number} sourceHeight - The un-scaled original size.
* @readonly
*/
this.sourceHeight = sprite.height;
if (sprite.texture)
{
this.sourceWidth = sprite.texture.frame.width;
this.sourceHeight = sprite.texture.frame.height;
}
/**
* @property {number} halfWidth - The calculated width / 2 of the physics body.
* @readonly
*/
this.halfWidth = Math.abs(sprite.width / 2);
/**
* @property {number} halfHeight - The calculated height / 2 of the physics body.
* @readonly
*/
this.halfHeight = Math.abs(sprite.height / 2);
/**
* @property {Phaser.Point} center - The center coordinate of the Physics Body.
* @readonly
*/
this.center = new Phaser.Point(sprite.x + this.halfWidth, sprite.y + this.halfHeight);
/**
* @property {Phaser.Point} velocity - The velocity, or rate of change the Body's position. Measured in pixels per second.
*/
this.velocity = new Phaser.Point();
/**
* @property {Phaser.Point} newVelocity - The distanced traveled during the last update, equal to `velocity * physicsElapsed`. Calculated during the Body.preUpdate and applied to its position.
* @readonly
*/
this.newVelocity = new Phaser.Point();
/**
* @property {Phaser.Point} deltaMax - The Sprite position is updated based on the delta x/y values. You can set a cap on those (both +-) using deltaMax.
*/
this.deltaMax = new Phaser.Point();
/**
* @property {Phaser.Point} acceleration - The acceleration is the rate of change of the velocity. Measured in pixels per second squared.
*/
this.acceleration = new Phaser.Point();
/**
* @property {boolean} allowDrag - Allow this Body to be influenced by {@link #drag}?
* @default
*/
this.allowDrag = true;
/**
* @property {Phaser.Point} drag - The drag applied to the motion of the Body (when {@link #allowDrag} is enabled). Measured in pixels per second squared.
*/
this.drag = new Phaser.Point();
/**
* @property {boolean} allowGravity - Allow this Body to be influenced by gravity? Either world or local.
* @default
*/
this.allowGravity = true;
/**
* @property {Phaser.Point} gravity - This Body's local gravity, **added** to any world gravity, unless Body.allowGravity is set to false.
*/
this.gravity = new Phaser.Point();
/**
* @property {Phaser.Point} bounce - The elasticity of the Body when colliding. bounce.x/y = 1 means full rebound, bounce.x/y = 0.5 means 50% rebound velocity.
*/
this.bounce = new Phaser.Point();
/**
* The elasticity of the Body when colliding with the World bounds.
* By default this property is `null`, in which case `Body.bounce` is used instead. Set this property
* to a Phaser.Point object in order to enable a World bounds specific bounce value.
* @property {Phaser.Point} worldBounce
*/
this.worldBounce = null;
/**
* A Signal that is dispatched when this Body collides with the world bounds.
* Due to the potentially high volume of signals this could create it is disabled by default.
* To use this feature set this property to a Phaser.Signal: `sprite.body.onWorldBounds = new Phaser.Signal()`
* and it will be called when a collision happens, passing five arguments:
* `onWorldBounds(sprite, up, down, left, right)`
* where the Sprite is a reference to the Sprite that owns this Body, and the other arguments are booleans
* indicating on which side of the world the Body collided.
* @property {Phaser.Signal} onWorldBounds
*/
this.onWorldBounds = null;
/**
* A Signal that is dispatched when this Body collides with another Body.
*
* You still need to call `game.physics.arcade.collide` in your `update` method in order
* for this signal to be dispatched.
*
* Usually you'd pass a callback to the `collide` method, but this signal provides for
* a different level of notification.
*
* Due to the potentially high volume of signals this could create it is disabled by default.
*
* To use this feature set this property to a Phaser.Signal: `sprite.body.onCollide = new Phaser.Signal()`
* and it will be called when a collision happens, passing two arguments: the sprites which collided.
* The first sprite in the argument is always the owner of this Body.
*
* If two Bodies with this Signal set collide, both will dispatch the Signal.
* @property {Phaser.Signal} onCollide
*/
this.onCollide = null;
/**
* A Signal that is dispatched when this Body overlaps with another Body.
*
* You still need to call `game.physics.arcade.overlap` in your `update` method in order
* for this signal to be dispatched.
*
* Usually you'd pass a callback to the `overlap` method, but this signal provides for
* a different level of notification.
*
* Due to the potentially high volume of signals this could create it is disabled by default.
*
* To use this feature set this property to a Phaser.Signal: `sprite.body.onOverlap = new Phaser.Signal()`
* and it will be called when a collision happens, passing two arguments: the sprites which collided.
* The first sprite in the argument is always the owner of this Body.
*
* If two Bodies with this Signal set collide, both will dispatch the Signal.
* @property {Phaser.Signal} onOverlap
*/
this.onOverlap = null;
/**
* @property {Phaser.Point} maxVelocity - The maximum velocity (in pixels per second squared) that the Body can reach.
* @default
*/
this.maxVelocity = new Phaser.Point(10000, 10000);
/**
* @property {Phaser.Point} friction - If this Body is {@link #immovable} and moving, and another Body is 'riding' this one, this is the amount of motion the riding Body receives on each axis.
*/
this.friction = new Phaser.Point(1, 0);
/**
* @property {number} angularVelocity - The angular velocity is the rate of change of the Body's rotation. It is measured in degrees per second.
* @default
*/
this.angularVelocity = 0;
/**
* @property {number} angularAcceleration - The angular acceleration is the rate of change of the angular velocity. Measured in degrees per second squared.
* @default
*/
this.angularAcceleration = 0;
/**
* @property {number} angularDrag - The drag applied during the rotation of the Body. Measured in degrees per second squared.
* @default
*/
this.angularDrag = 0;
/**
* @property {number} maxAngular - The maximum angular velocity in degrees per second that the Body can reach.
* @default
*/
this.maxAngular = 1000;
/**
* @property {number} mass - The mass of the Body. When two bodies collide their mass is used in the calculation to determine the exchange of velocity.
* @default
*/
this.mass = 1;
/**
* @property {number} angle - The angle of the Body's **velocity** in radians.
* @readonly
*/
this.angle = 0;
/**
* @property {number} speed - The speed of the Body in pixels per second, equal to the magnitude of the velocity.
* @readonly
*/
this.speed = 0;
/**
* @property {number} facing - A const reference to the direction the Body is traveling or facing: Phaser.NONE, Phaser.LEFT, Phaser.RIGHT, Phaser.UP, or Phaser.DOWN. If the Body is moving on both axes, UP and DOWN take precedence.
* @default
*/
this.facing = Phaser.NONE;
/**
* @property {boolean} immovable - An immovable Body will not receive any impacts from other bodies. **Two** immovable Bodies can't separate or exchange momentum and will pass through each other.
* @default
*/
this.immovable = false;
/**
* Whether the physics system should update the Body's position and rotation based on its velocity, acceleration, drag, and gravity.
*
* If you have a Body that is being moved around the world via a tween or a Group motion, but its local x/y position never
* actually changes, then you should set Body.moves = false. Otherwise it will most likely fly off the screen.
* If you want the physics system to move the body around, then set moves to true.
*
* A Body with moves = false can still be moved slightly (but not accelerated) during collision separation unless you set {@link #immovable} as well.
*
* @property {boolean} moves - Set to true to allow the Physics system to move this Body, otherwise false to move it manually.
* @default
*/
this.moves = true;
/**
* This flag allows you to disable the custom x separation that takes place by Physics.Arcade.separate.
* Used in combination with your own collision processHandler you can create whatever type of collision response you need.
* @property {boolean} customSeparateX - Use a custom separation system or the built-in one?
* @default
*/
this.customSeparateX = false;
/**
* This flag allows you to disable the custom y separation that takes place by Physics.Arcade.separate.
* Used in combination with your own collision processHandler you can create whatever type of collision response you need.
* @property {boolean} customSeparateY - Use a custom separation system or the built-in one?
* @default
*/
this.customSeparateY = false;
/**
* When this body collides with another, the amount of overlap is stored here.
* @property {number} overlapX - The amount of horizontal overlap during the collision.
*/
this.overlapX = 0;
/**
* When this body collides with another, the amount of overlap is stored here.
* @property {number} overlapY - The amount of vertical overlap during the collision.
*/
this.overlapY = 0;
/**
* If `Body.isCircle` is true, and this body collides with another circular body, the amount of overlap is stored here.
* @property {number} overlapR - The amount of overlap during the collision.
*/
this.overlapR = 0;
/**
* If a body is overlapping with another body, but neither of them are moving (maybe they spawned on-top of each other?) this is set to true.
* @property {boolean} embedded - Body embed value.
*/
this.embedded = false;
/**
* A Body can be set to collide against the World bounds automatically and rebound back into the World if this is set to true. Otherwise it will leave the World.
* @property {boolean} collideWorldBounds - Should the Body collide with the World bounds?
*/
this.collideWorldBounds = false;
/**
* Set the checkCollision properties to control which directions collision is processed for this Body.
* For example checkCollision.up = false means it won't collide when the collision happened while moving up.
* If you need to disable a Body entirely, use `body.enable = false`, this will also disable motion.
* If you need to disable just collision and/or overlap checks, but retain motion, set `checkCollision.none = true`.
* @property {object} checkCollision - An object containing allowed collision (none, up, down, left, right).
*/
this.checkCollision = { none: false, up: true, down: true, left: true, right: true };
/**
* This object is populated with boolean values when the Body collides with another.
* touching.up = true means the collision happened to the top of this Body for example.
* @property {object} touching - An object containing touching results (none, up, down, left, right).
*/
this.touching = { none: true, up: false, down: false, left: false, right: false };
/**
* This object is populated with previous touching values from the bodies previous collision.
* @property {object} wasTouching - An object containing previous touching results (none, up, down, left, right).
*/
this.wasTouching = { none: true, up: false, down: false, left: false, right: false };
/**
* This object is populated with boolean values when the Body collides with the World bounds or a Tile.
* For example if blocked.up is true then the Body cannot move up.
* @property {object} blocked - An object containing on which faces this Body is blocked from moving, if any (none, up, down, left, right).
*/
this.blocked = { none: true, up: false, down: false, left: false, right: false };
/**
* If this is an especially small or fast moving object then it can sometimes skip over tilemap collisions if it moves through a tile in a step.
* Set this padding value to add extra padding to its bounds. tilePadding.x applied to its width, y to its height.
* @property {Phaser.Point} tilePadding - Extra padding to be added to this sprite's dimensions when checking for tile collision.
*/
this.tilePadding = new Phaser.Point();
/**
* @property {boolean} dirty - If this Body in a preUpdate (true) or postUpdate (false) state?
*/
this.dirty = false;
/**
* @property {boolean} skipQuadTree - If true and you collide this Sprite against a Group, it will disable the collision check from using a QuadTree.
*/
this.skipQuadTree = false;
/**
* If true the Body will check itself against the Sprite.getBounds() dimensions and adjust its width and height accordingly.
* If false it will compare its dimensions against the Sprite scale instead, and adjust its width height if the scale has changed.
* Typically you would need to enable syncBounds if your sprite is the child of a responsive display object such as a FlexLayer,
* or in any situation where the Sprite scale doesn't change, but its parents scale is effecting the dimensions regardless.
* @property {boolean} syncBounds
* @default
*/
this.syncBounds = false;
/**
* @property {boolean} isMoving - Set by the `moveTo` and `moveFrom` methods.
*/
this.isMoving = false;
/**
* @property {boolean} stopVelocityOnCollide - Set by the `moveTo` and `moveFrom` methods.
*/
this.stopVelocityOnCollide = true;
/**
* @property {integer} moveTimer - Internal time used by the `moveTo` and `moveFrom` methods.
* @private
*/
this.moveTimer = 0;
/**
* @property {integer} moveDistance - Internal distance value, used by the `moveTo` and `moveFrom` methods.
* @private
*/
this.moveDistance = 0;
/**
* @property {integer} moveDuration - Internal duration value, used by the `moveTo` and `moveFrom` methods.
* @private
*/
this.moveDuration = 0;
/**
* @property {Phaser.Line} moveTarget - Set by the `moveTo` method, and updated each frame.
* @private
*/
this.moveTarget = null;
/**
* @property {Phaser.Point} moveEnd - Set by the `moveTo` method, and updated each frame.
* @private
*/
this.moveEnd = null;
/**
* @property {Phaser.Signal} onMoveComplete - Listen for the completion of `moveTo` or `moveFrom` events.
*/
this.onMoveComplete = new Phaser.Signal();
/**
* @property {function} movementCallback - Optional callback. If set, invoked during the running of `moveTo` or `moveFrom` events.
*/
this.movementCallback = null;
/**
* @property {object} movementCallbackContext - Context in which to call the movementCallback.
*/
this.movementCallbackContext = null;
/**
* @property {boolean} _reset - Internal cache var.
* @private
*/
this._reset = true;
/**
* @property {number} _sx - Internal cache var.
* @private
*/
this._sx = sprite.scale.x;
/**
* @property {number} _sy - Internal cache var.
* @private
*/
this._sy = sprite.scale.y;
/**
* @property {number} _dx - Internal cache var.
* @private
*/
this._dx = 0;
/**
* @property {number} _dy - Internal cache var.
* @private
*/
this._dy = 0;
};
Phaser.Physics.Arcade.Body.prototype = {
/**
* Internal method.
*
* @method Phaser.Physics.Arcade.Body#updateBounds
* @protected
*/
updateBounds: function ()
{
if (this.syncBounds)
{
var b = this.sprite.getBounds();
b.ceilAll();
if (b.width !== this.width || b.height !== this.height)
{
this.width = b.width;
this.height = b.height;
this._reset = true;
}
}
else
{
var asx = Math.abs(this.sprite.scale.x);
var asy = Math.abs(this.sprite.scale.y);
if (asx !== this._sx || asy !== this._sy)
{
this.width = this.sourceWidth * asx;
this.height = this.sourceHeight * asy;
this._sx = asx;
this._sy = asy;
this._reset = true;
}
}
if (this._reset)
{
this.halfWidth = Math.floor(this.width / 2);
this.halfHeight = Math.floor(this.height / 2);
this.updateCenter();
}
},
/**
* Update the Body's center from its position.
*
* @method Phaser.Physics.Arcade.Body#updateCenter
* @protected
*/
updateCenter: function ()
{
this.center.setTo(this.position.x + this.halfWidth, this.position.y + this.halfHeight);
},
/**
* Internal method.
*
* @method Phaser.Physics.Arcade.Body#preUpdate
* @protected
*/
preUpdate: function ()
{
if (!this.enable || this.game.physics.arcade.isPaused)
{
return;
}
this.dirty = true;
// Store and reset collision flags
this.wasTouching.none = this.touching.none;
this.wasTouching.up = this.touching.up;
this.wasTouching.down = this.touching.down;
this.wasTouching.left = this.touching.left;
this.wasTouching.right = this.touching.right;
this.touching.none = true;
this.touching.up = false;
this.touching.down = false;
this.touching.left = false;
this.touching.right = false;
this.blocked.none = true;
this.blocked.up = false;
this.blocked.down = false;
this.blocked.left = false;
this.blocked.right = false;
this.overlapR = 0;
this.overlapX = 0;
this.overlapY = 0;
this.embedded = false;
this.updateBounds();
this.position.x = (this.sprite.world.x - (this.sprite.anchor.x * this.sprite.width)) + this.sprite.scale.x * this.offset.x;
this.position.x -= this.sprite.scale.x < 0 ? this.width : 0;
this.position.y = (this.sprite.world.y - (this.sprite.anchor.y * this.sprite.height)) + this.sprite.scale.y * this.offset.y;
this.position.y -= this.sprite.scale.y < 0 ? this.height : 0;
this.updateCenter();
this.rotation = this.sprite.angle;
this.preRotation = this.rotation;
if (this._reset || this.sprite.fresh)
{
this.prev.x = this.position.x;
this.prev.y = this.position.y;
}
if (this.moves)
{
this.game.physics.arcade.updateMotion(this);
this.newVelocity.set(this.velocity.x * this.game.time.physicsElapsed, this.velocity.y * this.game.time.physicsElapsed);
this.position.x += this.newVelocity.x;
this.position.y += this.newVelocity.y;
this.updateCenter();
if (this.position.x !== this.prev.x || this.position.y !== this.prev.y)
{
this.angle = this.velocity.atan();
}
this.speed = Math.sqrt(this.velocity.x * this.velocity.x + this.velocity.y * this.velocity.y);
// Now the State update will throw collision checks at the Body
// And finally we'll integrate the new position back to the Sprite in postUpdate
if (this.collideWorldBounds)
{
if (this.checkWorldBounds() && this.onWorldBounds)
{
this.onWorldBounds.dispatch(this.sprite, this.blocked.up, this.blocked.down, this.blocked.left, this.blocked.right);
}
}
}
this._dx = this.deltaX();
this._dy = this.deltaY();
this._reset = false;
},
/**
* Internal method.
*
* @method Phaser.Physics.Arcade.Body#updateMovement
* @protected
*/
updateMovement: function ()
{
var percent = 0;
var collided = (this.overlapX !== 0 || this.overlapY !== 0);
// Duration or Distance based?
if (this.moveDuration > 0)
{
this.moveTimer += this.game.time.elapsedMS;
percent = this.moveTimer / this.moveDuration;
}
else
{
this.moveTarget.end.set(this.position.x, this.position.y);
percent = this.moveTarget.length / this.moveDistance;
}
if (this.movementCallback)
{
var result = this.movementCallback.call(this.movementCallbackContext, this, this.velocity, percent);
}
if (collided || percent >= 1 || (result !== undefined && result !== true))
{
this.stopMovement((percent >= 1) || (this.stopVelocityOnCollide && collided));
return false;
}
return true;
},
/**
* If this Body is moving as a result of a call to `moveTo` or `moveFrom` (i.e. it
* has Body.isMoving true), then calling this method will stop the movement before
* either the duration or distance counters expire.
*
* The `onMoveComplete` signal is dispatched.
*
* @method Phaser.Physics.Arcade.Body#stopMovement
* @param {boolean} [stopVelocity] - Should the Body.velocity be set to zero?
*/
stopMovement: function (stopVelocity)
{
if (this.isMoving)
{
this.isMoving = false;
if (stopVelocity)
{
this.velocity.set(0);
}
// Send the Sprite this Body belongs to
// and a boolean indicating if it stopped because of a collision or not
this.onMoveComplete.dispatch(this.sprite, (this.overlapX !== 0 || this.overlapY !== 0));
}
},
/**
* Internal method.
*
* @method Phaser.Physics.Arcade.Body#postUpdate
* @protected
*/
postUpdate: function ()
{
// Only allow postUpdate to be called once per frame
if (!this.enable || !this.dirty)
{
return;
}
// Moving?
if (this.isMoving)
{
this.updateMovement();
}
this.dirty = false;
if (this.deltaX() < 0)
{
this.facing = Phaser.LEFT;
}
else if (this.deltaX() > 0)
{
this.facing = Phaser.RIGHT;
}
if (this.deltaY() < 0)
{
this.facing = Phaser.UP;
}
else if (this.deltaY() > 0)
{
this.facing = Phaser.DOWN;
}
if (this.moves)
{
this._dx = this.deltaX();
this._dy = this.deltaY();
if (this.deltaMax.x !== 0 && this._dx !== 0)
{
if (this._dx < 0 && this._dx < -this.deltaMax.x)
{
this._dx = -this.deltaMax.x;
}
else if (this._dx > 0 && this._dx > this.deltaMax.x)
{
this._dx = this.deltaMax.x;
}
}
if (this.deltaMax.y !== 0 && this._dy !== 0)
{
if (this._dy < 0 && this._dy < -this.deltaMax.y)
{
this._dy = -this.deltaMax.y;
}
else if (this._dy > 0 && this._dy > this.deltaMax.y)
{
this._dy = this.deltaMax.y;
}
}
this.sprite.position.x += this._dx;
this.sprite.position.y += this._dy;
this._reset = true;
}
this.updateCenter();
if (this.allowRotation)
{
this.sprite.angle += this.deltaZ();
}
this.prev.x = this.position.x;
this.prev.y = this.position.y;
},
/**
* Internal method.
*
* @method Phaser.Physics.Arcade.Body#checkWorldBounds
* @protected
* @return {boolean} True if the Body collided with the world bounds, otherwise false.
*/
checkWorldBounds: function ()
{
var pos = this.position;
var bounds = this.game.physics.arcade.bounds;
var check = this.game.physics.arcade.checkCollision;
var bx = (this.worldBounce) ? -this.worldBounce.x : -this.bounce.x;
var by = (this.worldBounce) ? -this.worldBounce.y : -this.bounce.y;
if (pos.x < bounds.x && check.left)
{
pos.x = bounds.x;
this.velocity.x *= bx;
this.blocked.left = true;
this.blocked.none = false;
}
else if (this.right > bounds.right && check.right)
{
pos.x = bounds.right - this.width;
this.velocity.x *= bx;
this.blocked.right = true;
this.blocked.none = false;
}
if (pos.y < bounds.y && check.up)
{
pos.y = bounds.y;
this.velocity.y *= by;
this.blocked.up = true;
this.blocked.none = false;
}
else if (this.bottom > bounds.bottom && check.down)
{
pos.y = bounds.bottom - this.height;
this.velocity.y *= by;
this.blocked.down = true;
this.blocked.none = false;
}
return !this.blocked.none;
},
/**
* Note: This method is experimental, and may be changed or removed in a future release.
*
* This method moves the Body in the given direction, for the duration specified.
* It works by setting the velocity on the Body, and an internal timer, and then
* monitoring the duration each frame. When the duration is up the movement is
* stopped and the `Body.onMoveComplete` signal is dispatched.
*
* Movement also stops if the Body collides or overlaps with any other Body.
*
* You can control if the velocity should be reset to zero on collision, by using
* the property `Body.stopVelocityOnCollide`.
*
* Stop the movement at any time by calling `Body.stopMovement`.
*
* You can optionally set a speed in pixels per second. If not specified it
* will use the current `Body.speed` value. If this is zero, the function will return false.
*
* Please note that due to browser timings you should allow for a variance in
* when the duration will actually expire. Depending on system it may be as much as
* +- 50ms. Also this method doesn't take into consideration any other forces acting
* on the Body, such as Gravity, drag or maxVelocity, all of which may impact the
* movement.
*
* @method Phaser.Physics.Arcade.Body#moveFrom
* @param {integer} duration - The duration of the movement, in ms.
* @param {integer} [speed] - The speed of the movement, in pixels per second. If not provided `Body.speed` is used.
* @param {integer} [direction] - The angle of movement. If not provided `Body.angle` is used.
* @return {boolean} True if the movement successfully started, otherwise false.
*/
moveFrom: function (duration, speed, direction)
{
if (speed === undefined) { speed = this.speed; }
if (speed === 0)
{
return false;
}
var angle;
if (direction === undefined)
{
angle = this.angle;
direction = this.game.math.radToDeg(angle);
}
else
{
angle = this.game.math.degToRad(direction);
}
this.moveTimer = 0;
this.moveDuration = duration;
// Avoid sin/cos
if (direction === 0 || direction === 180)
{
this.velocity.set(Math.cos(angle) * speed, 0);
}
else if (direction === 90 || direction === 270)
{
this.velocity.set(0, Math.sin(angle) * speed);
}
else
{
this.velocity.setToPolar(angle, speed);
}
this.isMoving = true;
return true;
},
/**
* Note: This method is experimental, and may be changed or removed in a future release.
*
* This method moves the Body in the given direction, for the duration specified.
* It works by setting the velocity on the Body, and an internal distance counter.
* The distance is monitored each frame. When the distance equals the distance
* specified in this call, the movement is stopped, and the `Body.onMoveComplete`
* signal is dispatched.
*
* Movement also stops if the Body collides or overlaps with any other Body.
*
* You can control if the velocity should be reset to zero on collision, by using
* the property `Body.stopVelocityOnCollide`.
*
* Stop the movement at any time by calling `Body.stopMovement`.
*
* Please note that due to browser timings you should allow for a variance in
* when the distance will actually expire.
*
* Note: This method doesn't take into consideration any other forces acting
* on the Body, such as Gravity, drag or maxVelocity, all of which may impact the
* movement.
*
* @method Phaser.Physics.Arcade.Body#moveTo
* @param {integer} duration - The duration of the movement, in ms.
* @param {integer} distance - The distance, in pixels, the Body will move.
* @param {integer} [direction] - The angle of movement. If not provided `Body.angle` is used.
* @return {boolean} True if the movement successfully started, otherwise false.
*/
moveTo: function (duration, distance, direction)
{
var speed = distance / (duration / 1000);
if (speed === 0)
{
return false;
}
var angle;
if (direction === undefined)
{
angle = this.angle;
direction = this.game.math.radToDeg(angle);
}
else
{
angle = this.game.math.degToRad(direction);
}
distance = Math.abs(distance);
this.moveDuration = 0;
this.moveDistance = distance;
if (this.moveTarget === null)
{
this.moveTarget = new Phaser.Line();
this.moveEnd = new Phaser.Point();
}
this.moveTarget.fromAngle(this.x, this.y, angle, distance);
this.moveEnd.set(this.moveTarget.end.x, this.moveTarget.end.y);
this.moveTarget.setTo(this.x, this.y, this.x, this.y);
// Avoid sin/cos
if (direction === 0 || direction === 180)
{
this.velocity.set(Math.cos(angle) * speed, 0);
}
else if (direction === 90 || direction === 270)
{
this.velocity.set(0, Math.sin(angle) * speed);
}
else
{
this.velocity.setToPolar(angle, speed);
}
this.isMoving = true;
return true;
},
/**
* You can modify the size of the physics Body to be any dimension you need.
* This allows you to make it smaller, or larger, than the parent Sprite. You
* can also control the x and y offset of the Body.
*
* The width, height, and offset arguments are relative to the Sprite
* _texture_ and are scaled with the Sprite's {@link Phaser.Sprite#scale}
* (but **not** the scale of any ancestors or the {@link Phaser.Camera#scale
* Camera scale}).
*
* For example: If you have a Sprite with a texture that is 80x100 in size,
* and you want the physics body to be 32x32 pixels in the middle of the
* texture, you would do:
*
* `setSize(32 / Math.abs(this.scale.x), 32 / Math.abs(this.scale.y), 24,
* 34)`
*
* Where the first two parameters are the new Body size (32x32 pixels)
* relative to the Sprite's scale. 24 is the horizontal offset of the Body
* from the top-left of the Sprites texture, and 34 is the vertical offset.
*
* If you've scaled a Sprite by altering its `width`, `height`, or `scale`
* and you want to position the Body relative to the Sprite's dimensions
* (which will differ from its texture's dimensions), you should divide these
* arguments by the Sprite's current scale:
*
* `setSize(32 / sprite.scale.x, 32 / sprite.scale.y)`
*
* Calling `setSize` on a Body that has already had `setCircle` will reset
* all of the Circle properties, making this Body rectangular again.
* @method Phaser.Physics.Arcade.Body#setSize
* @param {number} width - The width of the Body, relative to the Sprite's
* texture.
* @param {number} height - The height of the Body, relative to the Sprite's
* texture.
* @param {number} [offsetX] - The X offset of the Body from the left of the
* Sprite's texture.
* @param {number} [offsetY] - The Y offset of the Body from the top of the
* Sprite's texture.
*/
setSize: function (width, height, offsetX, offsetY)
{
if (offsetX === undefined) { offsetX = this.offset.x; }
if (offsetY === undefined) { offsetY = this.offset.y; }
this.sourceWidth = width;
this.sourceHeight = height;
this.width = this.sourceWidth * this._sx;
this.height = this.sourceHeight * this._sy;
this.halfWidth = Math.floor(this.width / 2);
this.halfHeight = Math.floor(this.height / 2);
this.offset.setTo(offsetX, offsetY);
this.updateCenter();
this.isCircle = false;
this.radius = 0;
},
/**
* Sets this Body as using a circle, of the given radius, for all collision detection instead of a rectangle.
* The radius is given in pixels (relative to the Sprite's _texture_) and is the distance from the center of the circle to the edge.
*
* You can also control the x and y offset, which is the position of the Body relative to the top-left of the Sprite's texture.
*
* To change a Body back to being rectangular again call `Body.setSize`.
*
* Note: Circular collision only happens with other Arcade Physics bodies, it does not
* work against tile maps, where rectangular collision is the only method supported.
*
* @method Phaser.Physics.Arcade.Body#setCircle
* @param {number} [radius] - The radius of the Body in pixels. Pass a value of zero / undefined, to stop the Body using a circle for collision.
* @param {number} [offsetX] - The X offset of the Body from the left of the Sprite's texture.
* @param {number} [offsetY] - The Y offset of the Body from the top of the Sprite's texture.
*/
setCircle: function (radius, offsetX, offsetY)
{
if (offsetX === undefined) { offsetX = this.offset.x; }
if (offsetY === undefined) { offsetY = this.offset.y; }
if (radius > 0)
{
this.isCircle = true;
this.radius = radius;
this.sourceWidth = radius * 2;
this.sourceHeight = radius * 2;
this.width = this.sourceWidth * this._sx;
this.height = this.sourceHeight * this._sy;
this.halfWidth = Math.floor(this.width / 2);
this.halfHeight = Math.floor(this.height / 2);
this.offset.setTo(offsetX, offsetY);
this.updateCenter();
}
else
{
this.isCircle = false;
}
},
/**
* Resets all Body values (velocity, acceleration, rotation, etc)
*
* @method Phaser.Physics.Arcade.Body#reset
* @param {number} x - The new x position of the Body.
* @param {number} y - The new y position of the Body.
*/
reset: function (x, y)
{
this.stop();
this.position.x = (x - (this.sprite.anchor.x * this.sprite.width)) + this.sprite.scale.x * this.offset.x;
this.position.x -= this.sprite.scale.x < 0 ? this.width : 0;
this.position.y = (y - (this.sprite.anchor.y * this.sprite.height)) + this.sprite.scale.y * this.offset.y;
this.position.y -= this.sprite.scale.y < 0 ? this.height : 0;
this.prev.x = this.position.x;
this.prev.y = this.position.y;
this.rotation = this.sprite.angle;
this.preRotation = this.rotation;
this.updateBounds();
this.updateCenter();
},
/**
* Sets acceleration, velocity, and {@link #speed} to 0.
*
* @method Phaser.Physics.Arcade.Body#stop
*/
stop: function ()
{
this.velocity.set(0);
this.acceleration.set(0);
this.speed = 0;
this.angularVelocity = 0;
this.angularAcceleration = 0;
},
/**
* Returns the bounds of this physics body.
*
* Only used internally by the World collision methods.
*
* @method Phaser.Physics.Arcade.Body#getBounds
* @param {object} obj - The object in which to set the bounds values.
* @return {object} The object that was given to this method.
*/
getBounds: function (obj)
{
obj.x = this.x;
obj.y = this.y;
obj.right = this.right;
obj.bottom = this.bottom;
return obj;
},
/**
* Tests if a world point lies within this Body.
*
* @method Phaser.Physics.Arcade.Body#hitTest
* @param {number} x - The world x coordinate to test.
* @param {number} y - The world y coordinate to test.
* @return {boolean} True if the given coordinates are inside this Body, otherwise false.
*/
hitTest: function (x, y)
{
return (this.isCircle) ? Phaser.Circle.contains(this, x, y) : Phaser.Rectangle.contains(this, x, y);
},
/**
* Returns true if the bottom of this Body is in contact with either the world bounds or a tile.
*
* @method Phaser.Physics.Arcade.Body#onFloor
* @return {boolean} True if in contact with either the world bounds or a tile.
*/
onFloor: function ()
{
return this.blocked.down;
},
/**
* Returns true if the top of this Body is in contact with either the world bounds or a tile.
*
* @method Phaser.Physics.Arcade.Body#onCeiling
* @return {boolean} True if in contact with either the world bounds or a tile.
*/
onCeiling: function ()
{
return this.blocked.up;
},
/**
* Returns true if either side of this Body is in contact with either the world bounds or a tile.
*
* @method Phaser.Physics.Arcade.Body#onWall
* @return {boolean} True if in contact with either the world bounds or a tile.
*/
onWall: function ()
{
return (this.blocked.left || this.blocked.right);
},
/**
* Returns the absolute delta x value.
*
* @method Phaser.Physics.Arcade.Body#deltaAbsX
* @return {number} The absolute delta value.
*/
deltaAbsX: function ()
{
return (this.deltaX() > 0 ? this.deltaX() : -this.deltaX());
},
/**
* Returns the absolute delta y value.
*
* @method Phaser.Physics.Arcade.Body#deltaAbsY
* @return {number} The absolute delta value.
*/
deltaAbsY: function ()
{
return (this.deltaY() > 0 ? this.deltaY() : -this.deltaY());
},
/**
* Returns the delta x value. The difference between Body.x now and in the previous step.
*
* @method Phaser.Physics.Arcade.Body#deltaX
* @return {number} The delta value. Positive if the motion was to the right, negative if to the left.
*/
deltaX: function ()
{
return this.position.x - this.prev.x;
},
/**
* Returns the delta y value. The difference between Body.y now and in the previous step.
*
* @method Phaser.Physics.Arcade.Body#deltaY
* @return {number} The delta value. Positive if the motion was downwards, negative if upwards.
*/
deltaY: function ()
{
return this.position.y - this.prev.y;
},
/**
* Returns the delta z value. The difference between Body.rotation now and in the previous step.
*
* @method Phaser.Physics.Arcade.Body#deltaZ
* @return {number} The delta value. Positive if the motion was clockwise, negative if anti-clockwise.
*/
deltaZ: function ()
{
return this.rotation - this.preRotation;
},
/**
* Destroys this Body.
*
* First it calls Group.removeFromHash if the Game Object this Body belongs to is part of a Group.
* Then it nulls the Game Objects body reference, and nulls this Body.sprite reference.
*
* @method Phaser.Physics.Arcade.Body#destroy
*/
destroy: function ()
{
if (this.sprite.parent && this.sprite.parent instanceof Phaser.Group)
{
this.sprite.parent.removeFromHash(this.sprite);
}
this.sprite.body = null;
this.sprite = null;
}
};
/**
* @name Phaser.Physics.Arcade.Body#left
* @property {number} left - The x position of the Body. The same as `Body.x`.
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, 'left', {
get: function ()
{
return this.position.x;
}
});
/**
* @name Phaser.Physics.Arcade.Body#right
* @property {number} right - The right value of this Body (same as Body.x + Body.width)
* @readonly
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, 'right', {
get: function ()
{
return this.position.x + this.width;
}
});
/**
* @name Phaser.Physics.Arcade.Body#top
* @property {number} top - The y position of the Body. The same as `Body.y`.
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, 'top', {
get: function ()
{
return this.position.y;
}
});
/**
* @name Phaser.Physics.Arcade.Body#bottom
* @property {number} bottom - The bottom value of this Body (same as Body.y + Body.height)
* @readonly
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, 'bottom', {
get: function ()
{
return this.position.y + this.height;
}
});
/**
* @name Phaser.Physics.Arcade.Body#x
* @property {number} x - The x position.
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, 'x', {
get: function ()
{
return this.position.x;
},
set: function (value)
{
this.position.x = value;
}
});
/**
* @name Phaser.Physics.Arcade.Body#y
* @property {number} y - The y position.
*/
Object.defineProperty(Phaser.Physics.Arcade.Body.prototype, 'y', {
get: function ()
{
return this.position.y;
},
set: function (value)
{
this.position.y = value;
}
});
/**
* Render Sprite Body.
*
* @method Phaser.Physics.Arcade.Body#render
* @param {object} context - The context to render to.
* @param {Phaser.Physics.Arcade.Body} body - The Body to render the info of.
* @param {string} [color='rgba(0,255,0,0.4)'] - color of the debug info to be rendered. (format is css color string).
* @param {boolean} [filled=true] - Render the objected as a filled (default, true) or a stroked (false)
* @param {number} [lineWidth=1] - The width of the stroke when unfilled.
*/
Phaser.Physics.Arcade.Body.render = function (context, body, color, filled, lineWidth)
{
if (filled === undefined) { filled = true; }
color = color || 'rgba(0,255,0,0.4)';
context.fillStyle = color;
context.strokeStyle = color;
context.lineWidth = lineWidth || 1;
if (body.isCircle)
{
context.beginPath();
context.arc(body.center.x - body.game.camera.x, body.center.y - body.game.camera.y, body.halfWidth, 0, 2 * Math.PI);
if (filled)
{
context.fill();
}
else
{
context.stroke();
}
}
else
if (filled)
{
context.fillRect(body.position.x - body.game.camera.x, body.position.y - body.game.camera.y, body.width, body.height);
}
else
{
context.strokeRect(body.position.x - body.game.camera.x, body.position.y - body.game.camera.y, body.width, body.height);
}
};
/**
* Render Sprite Body Physics Data as text.
*
* @method Phaser.Physics.Arcade.Body#renderBodyInfo
* @param {Phaser.Physics.Arcade.Body} body - The Body to render the info of.
* @param {number} x - X position of the debug info to be rendered.
* @param {number} y - Y position of the debug info to be rendered.
* @param {string} [color='rgb(255,255,255)'] - color of the debug info to be rendered. (format is css color string).
*/
Phaser.Physics.Arcade.Body.renderBodyInfo = function (debug, body)
{
debug.line('x: ' + body.x.toFixed(2), 'y: ' + body.y.toFixed(2), 'width: ' + body.width, 'height: ' + body.height);
debug.line('velocity x: ' + body.velocity.x.toFixed(2), 'y: ' + body.velocity.y.toFixed(2), 'deltaX: ' + body._dx.toFixed(2), 'deltaY: ' + body._dy.toFixed(2));
debug.line('acceleration x: ' + body.acceleration.x.toFixed(2), 'y: ' + body.acceleration.y.toFixed(2), 'speed: ' + body.speed.toFixed(2), 'angle: ' + body.angle.toFixed(2));
debug.line('gravity x: ' + body.gravity.x, 'y: ' + body.gravity.y, 'bounce x: ' + body.bounce.x.toFixed(2), 'y: ' + body.bounce.y.toFixed(2));
debug.line('touching left: ' + body.touching.left, 'right: ' + body.touching.right, 'up: ' + body.touching.up, 'down: ' + body.touching.down);
debug.line('blocked left: ' + body.blocked.left, 'right: ' + body.blocked.right, 'up: ' + body.blocked.up, 'down: ' + body.blocked.down);
};
Phaser.Physics.Arcade.Body.prototype.constructor = Phaser.Physics.Arcade.Body;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Arcade Physics Tile map collision methods.
*
* These are mixed into {@link Phaser.Physics.Arcade}.
*
* @class Phaser.Physics.Arcade.TilemapCollision
* @constructor
*/
Phaser.Physics.Arcade.TilemapCollision = function () {};
Phaser.Physics.Arcade.TilemapCollision.prototype = {
/**
* @property {number} TILE_BIAS - A value added to the delta values during collision with tiles. The best value probably depends on your tile size. Increase it if sprites are tunneling; decrease it if sprites are flipping across tile edges.
*/
TILE_BIAS: 16,
/**
* An internal function. Use Phaser.Physics.Arcade.collide instead.
*
* @method Phaser.Physics.Arcade#collideSpriteVsTilemapLayer
* @private
* @param {Phaser.Sprite} sprite - The sprite to check.
* @param {Phaser.TilemapLayer} tilemapLayer - The layer to check.
* @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
* @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
*/
collideSpriteVsTilemapLayer: function (sprite, tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly)
{
if (!sprite.body)
{
return;
}
var mapData = tilemapLayer.getTiles(
sprite.body.position.x - sprite.body.tilePadding.x - tilemapLayer.getTileOffsetX(),
sprite.body.position.y - sprite.body.tilePadding.y - tilemapLayer.getTileOffsetY(),
sprite.body.width + sprite.body.tilePadding.x,
sprite.body.height + sprite.body.tilePadding.y,
false, false);
if (mapData.length === 0)
{
return;
}
for (var i = 0; i < mapData.length; i++)
{
if (processCallback)
{
if (processCallback.call(callbackContext, sprite, mapData[i]))
{
if (this.separateTile(i, sprite.body, mapData[i], tilemapLayer, overlapOnly))
{
this._total++;
if (collideCallback)
{
collideCallback.call(callbackContext, sprite, mapData[i]);
}
}
}
}
else
if (this.separateTile(i, sprite.body, mapData[i], tilemapLayer, overlapOnly))
{
this._total++;
if (collideCallback)
{
collideCallback.call(callbackContext, sprite, mapData[i]);
}
}
}
},
/**
* An internal function. Use Phaser.Physics.Arcade.collide instead.
*
* @private
* @method Phaser.Physics.Arcade#collideGroupVsTilemapLayer
* @param {Phaser.Group} group - The Group to check.
* @param {Phaser.TilemapLayer} tilemapLayer - The layer to check.
* @param {function} collideCallback - An optional callback function that is called if the objects collide. The two objects will be passed to this function in the same order in which you specified them.
* @param {function} processCallback - A callback function that lets you perform additional checks against the two objects if they overlap. If this is set then collision will only happen if processCallback returns true. The two objects will be passed to this function in the same order in which you specified them.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Just run an overlap or a full collision.
*/
collideGroupVsTilemapLayer: function (group, tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly)
{
if (group.length === 0)
{
return;
}
for (var i = 0; i < group.children.length; i++)
{
if (group.children[i].exists)
{
this.collideSpriteVsTilemapLayer(group.children[i], tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly);
}
}
},
/**
* The core separation function to separate a physics body and a tile.
*
* @private
* @method Phaser.Physics.Arcade#separateTile
* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate.
* @param {Phaser.Tile} tile - The tile to collide against.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemapLayer to collide against.
* @return {boolean} Returns true if the body was separated, otherwise false.
*/
separateTile: function (i, body, tile, tilemapLayer, overlapOnly)
{
if (!body.enable)
{
return false;
}
var tilemapLayerOffsetX = tilemapLayer.getTileOffsetX();
var tilemapLayerOffsetY = tilemapLayer.getTileOffsetY();
// We re-check for collision in case body was separated in a previous step
if (!tile.intersects((body.position.x - tilemapLayerOffsetX), (body.position.y - tilemapLayerOffsetY), (body.right - tilemapLayerOffsetX), (body.bottom - tilemapLayerOffsetY)))
{
// no collision so bail out (separated in a previous step)
return false;
}
else if (overlapOnly)
{
// There is an overlap, and we don't need to separate. Bail.
return true;
}
// They overlap. Any custom callbacks?
// A local callback always takes priority over a layer level callback
if (tile.collisionCallback && !tile.collisionCallback.call(tile.collisionCallbackContext, body.sprite, tile))
{
// If it returns true then we can carry on, otherwise we should abort.
return false;
}
else if (typeof tile.layer.callbacks !== 'undefined' && tile.layer.callbacks[tile.index] && !tile.layer.callbacks[tile.index].callback.call(tile.layer.callbacks[tile.index].callbackContext, body.sprite, tile))
{
// If it returns true then we can carry on, otherwise we should abort.
return false;
}
// We don't need to go any further if this tile doesn't actually separate
if (!tile.faceLeft && !tile.faceRight && !tile.faceTop && !tile.faceBottom)
{
// This could happen if the tile was meant to be collided with re: a callback, but otherwise isn't needed for separation
return false;
}
var ox = 0;
var oy = 0;
var minX = 0;
var minY = 1;
if (body.deltaAbsX() > body.deltaAbsY())
{
// Moving faster horizontally, check X axis first
minX = -1;
}
else if (body.deltaAbsX() < body.deltaAbsY())
{
// Moving faster vertically, check Y axis first
minY = -1;
}
if (body.deltaX() !== 0 && body.deltaY() !== 0 && (tile.faceLeft || tile.faceRight) && (tile.faceTop || tile.faceBottom))
{
// We only need do this if both axis have checking faces AND we're moving in both directions
minX = Math.min(Math.abs((body.position.x - tilemapLayerOffsetX) - tile.right), Math.abs((body.right - tilemapLayerOffsetX) - tile.left));
minY = Math.min(Math.abs((body.position.y - tilemapLayerOffsetY) - tile.bottom), Math.abs((body.bottom - tilemapLayerOffsetY) - tile.top));
}
if (minX < minY)
{
if (tile.faceLeft || tile.faceRight)
{
ox = this.tileCheckX(body, tile, tilemapLayer);
// That's horizontal done, check if we still intersects? If not then we can return now
if (ox !== 0 && !tile.intersects((body.position.x - tilemapLayerOffsetX), (body.position.y - tilemapLayerOffsetY), (body.right - tilemapLayerOffsetX), (body.bottom - tilemapLayerOffsetY)))
{
return true;
}
}
if (tile.faceTop || tile.faceBottom)
{
oy = this.tileCheckY(body, tile, tilemapLayer);
}
}
else
{
if (tile.faceTop || tile.faceBottom)
{
oy = this.tileCheckY(body, tile, tilemapLayer);
// That's vertical done, check if we still intersects? If not then we can return now
if (oy !== 0 && !tile.intersects((body.position.x - tilemapLayerOffsetX), (body.position.y - tilemapLayerOffsetY), (body.right - tilemapLayerOffsetX), (body.bottom - tilemapLayerOffsetY)))
{
return true;
}
}
if (tile.faceLeft || tile.faceRight)
{
ox = this.tileCheckX(body, tile, tilemapLayer);
}
}
return (ox !== 0 || oy !== 0);
},
/**
* Check the body against the given tile on the X axis.
*
* @private
* @method Phaser.Physics.Arcade#tileCheckX
* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate.
* @param {Phaser.Tile} tile - The tile to check.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemapLayer to collide against.
* @return {number} The amount of separation that occurred.
*/
tileCheckX: function (body, tile, tilemapLayer)
{
var ox = 0;
var tilemapLayerOffsetX = tilemapLayer.getTileOffsetX();
if (body.deltaX() < 0 && !body.blocked.left && tile.collideRight && body.checkCollision.left)
{
// Body is moving LEFT
if (tile.faceRight && (body.x - tilemapLayerOffsetX) < tile.right)
{
ox = (body.x - tilemapLayerOffsetX) - tile.right;
if (ox < -this.TILE_BIAS)
{
ox = 0;
}
}
}
else if (body.deltaX() > 0 && !body.blocked.right && tile.collideLeft && body.checkCollision.right)
{
// Body is moving RIGHT
if (tile.faceLeft && (body.right - tilemapLayerOffsetX) > tile.left)
{
ox = (body.right - tilemapLayerOffsetX) - tile.left;
if (ox > this.TILE_BIAS)
{
ox = 0;
}
}
}
if (ox !== 0)
{
if (body.customSeparateX)
{
body.overlapX = ox;
}
else
{
this.processTileSeparationX(body, ox);
}
}
return ox;
},
/**
* Check the body against the given tile on the Y axis.
*
* @private
* @method Phaser.Physics.Arcade#tileCheckY
* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate.
* @param {Phaser.Tile} tile - The tile to check.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemapLayer to collide against.
* @return {number} The amount of separation that occurred.
*/
tileCheckY: function (body, tile, tilemapLayer)
{
var oy = 0;
var tilemapLayerOffsetY = tilemapLayer.getTileOffsetY();
if (body.deltaY() < 0 && !body.blocked.up && tile.collideDown && body.checkCollision.up)
{
// Body is moving UP
if (tile.faceBottom && (body.y - tilemapLayerOffsetY) < tile.bottom)
{
oy = (body.y - tilemapLayerOffsetY) - tile.bottom;
if (oy < -this.TILE_BIAS)
{
oy = 0;
}
}
}
else if (body.deltaY() > 0 && !body.blocked.down && tile.collideUp && body.checkCollision.down)
{
// Body is moving DOWN
if (tile.faceTop && (body.bottom - tilemapLayerOffsetY) > tile.top)
{
oy = (body.bottom - tilemapLayerOffsetY) - tile.top;
if (oy > this.TILE_BIAS)
{
oy = 0;
}
}
}
if (oy !== 0)
{
if (body.customSeparateY)
{
body.overlapY = oy;
}
else
{
this.processTileSeparationY(body, oy);
}
}
return oy;
},
/**
* Internal function to process the separation of a physics body from a tile.
*
* @private
* @method Phaser.Physics.Arcade#processTileSeparationX
* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate.
* @param {number} x - The x separation amount.
*/
processTileSeparationX: function (body, x)
{
if (x < 0)
{
body.blocked.left = true;
body.blocked.none = false;
}
else if (x > 0)
{
body.blocked.right = true;
body.blocked.none = false;
}
body.position.x -= x;
if (body.bounce.x === 0)
{
body.velocity.x = 0;
}
else
{
body.velocity.x = -body.velocity.x * body.bounce.x;
}
},
/**
* Internal function to process the separation of a physics body from a tile.
*
* @private
* @method Phaser.Physics.Arcade#processTileSeparationY
* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate.
* @param {number} y - The y separation amount.
*/
processTileSeparationY: function (body, y)
{
if (y < 0)
{
body.blocked.up = true;
body.blocked.none = false;
}
else if (y > 0)
{
body.blocked.down = true;
body.blocked.none = false;
}
body.position.y -= y;
if (body.bounce.y === 0)
{
body.velocity.y = 0;
}
else
{
body.velocity.y = -body.velocity.y * body.bounce.y;
}
}
};
// Merge this with the Arcade Physics prototype
Phaser.Utils.mixinPrototype(Phaser.Physics.Arcade.prototype, Phaser.Physics.Arcade.TilemapCollision.prototype);
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
// Add an extra properties to p2 that we need
p2.Body.prototype.parent = null;
p2.Spring.prototype.parent = null;
/**
* This is your main access to the P2 Physics World.
* From here you can create materials, listen for events and add bodies into the physics simulation.
*
* @class Phaser.Physics.P2
* @constructor
* @param {Phaser.Game} game - Reference to the current game instance.
* @param {object} [config] - Physics configuration object passed in from the game constructor.
*/
Phaser.Physics.P2 = function (game, config)
{
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = game;
if (config === undefined)
{
config = { gravity: [ 0, 0 ], broadphase: new p2.SAPBroadphase() };
}
else
{
if (!config.hasOwnProperty('gravity'))
{
config.gravity = [ 0, 0 ];
}
if (!config.hasOwnProperty('broadphase'))
{
config.broadphase = new p2.SAPBroadphase();
}
}
/**
* @property {object} config - The p2 World configuration object.
* @protected
*/
this.config = config;
/**
* @property {p2.World} world - The p2 World in which the simulation is run.
* @protected
*/
this.world = new p2.World(this.config);
/**
* @property {number} frameRate - The frame rate the world will be stepped at. Defaults to 1 / 60, but you can change here. Also see useElapsedTime property.
* @default
*/
this.frameRate = 1 / 60;
/**
* @property {boolean} useElapsedTime - If true the frameRate value will be ignored and instead p2 will step with the value of Game.Time.physicsElapsed, which is a delta time value.
* @default
*/
this.useElapsedTime = false;
/**
* @property {boolean} paused - The paused state of the P2 World.
* @default
*/
this.paused = false;
/**
* @property {array} materials - A local array of all created Materials.
* @protected
*/
this.materials = [];
/**
* @property {Phaser.Physics.P2.InversePointProxy} gravity - The gravity applied to all bodies each step.
*/
this.gravity = new Phaser.Physics.P2.InversePointProxy(this, this.world.gravity);
/**
* @property {object} walls - An object containing the 4 wall bodies that bound the physics world.
*/
this.walls = { left: null, right: null, top: null, bottom: null };
/**
* This signal is dispatched when a new Body is added to the World.
*
* It sends 1 argument: `body` which is the `Phaser.Physics.P2.Body` that was added to the world.
*
* @property {Phaser.Signal} onBodyAdded
*/
this.onBodyAdded = new Phaser.Signal();
/**
* This signal is dispatched when a Body is removed to the World.
*
* It sends 1 argument: `body` which is the `Phaser.Physics.P2.Body` that was removed from the world.
*
* @property {Phaser.Signal} onBodyRemoved
*/
this.onBodyRemoved = new Phaser.Signal();
/**
* This signal is dispatched when a Spring is added to the World.
*
* It sends 1 argument: `spring` which is either a `Phaser.Physics.P2.Spring`, `p2.LinearSpring` or `p2.RotationalSpring` that was added to the world.
*
* @property {Phaser.Signal} onSpringAdded
*/
this.onSpringAdded = new Phaser.Signal();
/**
* This signal is dispatched when a Spring is removed from the World.
*
* It sends 1 argument: `spring` which is either a `Phaser.Physics.P2.Spring`, `p2.LinearSpring` or `p2.RotationalSpring` that was removed from the world.
*
* @property {Phaser.Signal} onSpringRemoved
*/
this.onSpringRemoved = new Phaser.Signal();
/**
* This signal is dispatched when a Constraint is added to the World.
*
* It sends 1 argument: `constraint` which is the `Phaser.Physics.P2.Constraint` that was added to the world.
*
* @property {Phaser.Signal} onConstraintAdded
*/
this.onConstraintAdded = new Phaser.Signal();
/**
* This signal is dispatched when a Constraint is removed from the World.
*
* It sends 1 argument: `constraint` which is the `Phaser.Physics.P2.Constraint` that was removed from the world.
*
* @property {Phaser.Signal} onConstraintRemoved
*/
this.onConstraintRemoved = new Phaser.Signal();
/**
* This signal is dispatched when a Contact Material is added to the World.
*
* It sends 1 argument: `material` which is the `Phaser.Physics.P2.ContactMaterial` that was added to the world.
*
* @property {Phaser.Signal} onContactMaterialAdded
*/
this.onContactMaterialAdded = new Phaser.Signal();
/**
* This signal is dispatched when a Contact Material is removed from the World.
*
* It sends 1 argument: `material` which is the `Phaser.Physics.P2.ContactMaterial` that was removed from the world.
*
* @property {Phaser.Signal} onContactMaterialRemoved
*/
this.onContactMaterialRemoved = new Phaser.Signal();
/**
* @property {function} postBroadphaseCallback - A postBroadphase callback.
*/
this.postBroadphaseCallback = null;
/**
* @property {object} callbackContext - The context under which the callbacks are fired.
*/
this.callbackContext = null;
/**
* This Signal is dispatched when a first contact is created between two bodies. This happens *before* the step has been done.
*
* It sends 5 arguments: `bodyA`, `bodyB`, `shapeA`, `shapeB` and `contactEquations`.
*
* It is possible that in certain situations the `bodyA` or `bodyB` values are `null`. You should check for this
* in your own code to avoid processing potentially null physics bodies.
*
* @property {Phaser.Signal} onBeginContact
*/
this.onBeginContact = new Phaser.Signal();
/**
* This Signal is dispatched when final contact occurs between two bodies. This happens *before* the step has been done.
*
* It sends 4 arguments: `bodyA`, `bodyB`, `shapeA` and `shapeB`.
*
* It is possible that in certain situations the `bodyA` or `bodyB` values are `null`. You should check for this
* in your own code to avoid processing potentially null physics bodies.
*
* @property {Phaser.Signal} onEndContact
*/
this.onEndContact = new Phaser.Signal();
// Pixel to meter function overrides
if (config.hasOwnProperty('mpx') && config.hasOwnProperty('pxm') && config.hasOwnProperty('mpxi') && config.hasOwnProperty('pxmi'))
{
this.mpx = config.mpx;
this.mpxi = config.mpxi;
this.pxm = config.pxm;
this.pxmi = config.pxmi;
}
// Hook into the World events
this.world.on('beginContact', this.beginContactHandler, this);
this.world.on('endContact', this.endContactHandler, this);
/**
* @property {array} collisionGroups - An array containing the collision groups that have been defined in the World.
*/
this.collisionGroups = [];
/**
* @property {Phaser.Physics.P2.CollisionGroup} nothingCollisionGroup - A default collision group.
*/
this.nothingCollisionGroup = new Phaser.Physics.P2.CollisionGroup(1);
/**
* @property {Phaser.Physics.P2.CollisionGroup} boundsCollisionGroup - A default collision group.
*/
this.boundsCollisionGroup = new Phaser.Physics.P2.CollisionGroup(2);
/**
* @property {Phaser.Physics.P2.CollisionGroup} everythingCollisionGroup - A default collision group.
*/
this.everythingCollisionGroup = new Phaser.Physics.P2.CollisionGroup(2147483648);
/**
* @property {array} boundsCollidesWith - An array of the bodies the world bounds collides with.
*/
this.boundsCollidesWith = [];
/**
* @property {array} _toRemove - Internal var used to hold references to bodies to remove from the world on the next step.
* @private
*/
this._toRemove = [];
/**
* @property {number} _collisionGroupID - Internal var.
* @private
*/
this._collisionGroupID = 2;
/**
* @property {boolean} _boundsLeft - Internal var that keeps track of world bounds settings.
* @private
*/
this._boundsLeft = true;
/**
* @property {boolean} _boundsRight - Internal var that keeps track of world bounds settings.
* @private
*/
this._boundsRight = true;
/**
* @property {boolean} _boundsTop - Internal var that keeps track of world bounds settings.
* @private
*/
this._boundsTop = true;
/**
* @property {boolean} _boundsBottom - Internal var that keeps track of world bounds settings.
* @private
*/
this._boundsBottom = true;
/**
* @property {boolean} _boundsOwnGroup - Internal var that keeps track of world bounds settings.
* @private
*/
this._boundsOwnGroup = false;
// By default we want everything colliding with everything
this.setBoundsToWorld(true, true, true, true, false);
};
Phaser.Physics.P2.prototype = {
/**
* This will add a P2 Physics body into the removal list for the next step.
*
* @method Phaser.Physics.P2#removeBodyNextStep
* @param {Phaser.Physics.P2.Body} body - The body to remove at the start of the next step.
*/
removeBodyNextStep: function (body)
{
this._toRemove.push(body);
},
/**
* Called at the start of the core update loop. Purges flagged bodies from the world.
*
* @method Phaser.Physics.P2#preUpdate
*/
preUpdate: function ()
{
var i = this._toRemove.length;
while (i--)
{
this.removeBody(this._toRemove[i]);
}
this._toRemove.length = 0;
},
/**
* This will create a P2 Physics body on the given game object or array of game objects.
* A game object can only have 1 physics body active at any one time, and it can't be changed until the object is destroyed.
* Note: When the game object is enabled for P2 physics it has its anchor x/y set to 0.5 so it becomes centered.
*
* @method Phaser.Physics.P2#enable
* @param {object|array|Phaser.Group} object - The game object to create the physics body on. Can also be an array or Group of objects, a body will be created on every child that has a `body` property.
* @param {boolean} [debug=false] - Create a debug object to go with this body?
* @param {boolean} [children=true] - Should a body be created on all children of this object? If true it will recurse down the display list as far as it can go.
*/
enable: function (object, debug, children)
{
if (debug === undefined) { debug = false; }
if (children === undefined) { children = true; }
var i = 1;
if (Array.isArray(object))
{
i = object.length;
while (i--)
{
if (object[i] instanceof Phaser.Group)
{
// If it's a Group then we do it on the children regardless
this.enable(object[i].children, debug, children);
}
else
{
this.enableBody(object[i], debug);
if (children && object[i].hasOwnProperty('children') && object[i].children.length > 0)
{
this.enable(object[i], debug, true);
}
}
}
}
else
if (object instanceof Phaser.Group)
{
// If it's a Group then we do it on the children regardless
this.enable(object.children, debug, children);
}
else
{
this.enableBody(object, debug);
if (children && object.hasOwnProperty('children') && object.children.length > 0)
{
this.enable(object.children, debug, true);
}
}
},
/**
* Creates a P2 Physics body on the given game object.
* A game object can only have 1 physics body active at any one time, and it can't be changed until the body is nulled.
*
* @method Phaser.Physics.P2#enableBody
* @param {object} object - The game object to create the physics body on. A body will only be created if this object has a null `body` property.
* @param {boolean} debug - Create a debug object to go with this body?
*/
enableBody: function (object, debug)
{
if (object.hasOwnProperty('body') && object.body === null)
{
object.body = new Phaser.Physics.P2.Body(this.game, object, object.x, object.y, 1);
object.body.debug = debug;
if (typeof object.anchor !== 'undefined')
{
object.anchor.set(0.5);
}
}
},
/**
* Impact event handling is disabled by default. Enable it before any impact events will be dispatched.
* In a busy world hundreds of impact events can be generated every step, so only enable this if you cannot do what you need via beginContact or collision masks.
*
* @method Phaser.Physics.P2#setImpactEvents
* @param {boolean} state - Set to true to enable impact events, or false to disable.
*/
setImpactEvents: function (state)
{
if (state)
{
this.world.on('impact', this.impactHandler, this);
}
else
{
this.world.off('impact', this.impactHandler, this);
}
},
/**
* Sets a callback to be fired after the Broadphase has collected collision pairs in the world.
* Just because a pair exists it doesn't mean they *will* collide, just that they potentially could do.
* If your calback returns `false` the pair will be removed from the narrowphase. This will stop them testing for collision this step.
* Returning `true` from the callback will ensure they are checked in the narrowphase.
*
* @method Phaser.Physics.P2#setPostBroadphaseCallback
* @param {function} callback - The callback that will receive the postBroadphase event data. It must return a boolean. Set to null to disable an existing callback.
* @param {object} context - The context under which the callback will be fired.
*/
setPostBroadphaseCallback: function (callback, context)
{
this.postBroadphaseCallback = callback;
this.callbackContext = context;
if (callback !== null)
{
this.world.on('postBroadphase', this.postBroadphaseHandler, this);
}
else
{
this.world.off('postBroadphase', this.postBroadphaseHandler, this);
}
},
/**
* Internal handler for the postBroadphase event.
*
* @method Phaser.Physics.P2#postBroadphaseHandler
* @private
* @param {object} event - The event data.
*/
postBroadphaseHandler: function (event)
{
if (!this.postBroadphaseCallback || event.pairs.length === 0)
{
return;
}
for (var i = event.pairs.length - 2; i >= 0; i -= 2)
{
if (event.pairs[i].parent && event.pairs[i + 1].parent && !this.postBroadphaseCallback.call(this.callbackContext, event.pairs[i].parent, event.pairs[i + 1].parent))
{
event.pairs.splice(i, 2);
}
}
},
/**
* Handles a p2 impact event.
*
* @method Phaser.Physics.P2#impactHandler
* @private
* @param {object} event - The event data.
*/
impactHandler: function (event)
{
if (event.bodyA.parent && event.bodyB.parent)
{
// Body vs. Body callbacks
var a = event.bodyA.parent;
var b = event.bodyB.parent;
if (a._bodyCallbacks[event.bodyB.id])
{
a._bodyCallbacks[event.bodyB.id].call(a._bodyCallbackContext[event.bodyB.id], a, b, event.shapeA, event.shapeB);
}
if (b._bodyCallbacks[event.bodyA.id])
{
b._bodyCallbacks[event.bodyA.id].call(b._bodyCallbackContext[event.bodyA.id], b, a, event.shapeB, event.shapeA);
}
// Body vs. Group callbacks
if (a._groupCallbacks[event.shapeB.collisionGroup])
{
a._groupCallbacks[event.shapeB.collisionGroup].call(a._groupCallbackContext[event.shapeB.collisionGroup], a, b, event.shapeA, event.shapeB);
}
if (b._groupCallbacks[event.shapeA.collisionGroup])
{
b._groupCallbacks[event.shapeA.collisionGroup].call(b._groupCallbackContext[event.shapeA.collisionGroup], b, a, event.shapeB, event.shapeA);
}
}
},
/**
* Handles a p2 begin contact event.
*
* @method Phaser.Physics.P2#beginContactHandler
* @param {object} event - The event data.
*/
beginContactHandler: function (event)
{
if (event.bodyA && event.bodyB)
{
this.onBeginContact.dispatch(event.bodyA, event.bodyB, event.shapeA, event.shapeB, event.contactEquations);
if (event.bodyA.parent)
{
event.bodyA.parent.onBeginContact.dispatch(event.bodyB.parent, event.bodyB, event.shapeA, event.shapeB, event.contactEquations);
}
if (event.bodyB.parent)
{
event.bodyB.parent.onBeginContact.dispatch(event.bodyA.parent, event.bodyA, event.shapeB, event.shapeA, event.contactEquations);
}
}
},
/**
* Handles a p2 end contact event.
*
* @method Phaser.Physics.P2#endContactHandler
* @param {object} event - The event data.
*/
endContactHandler: function (event)
{
if (event.bodyA && event.bodyB)
{
this.onEndContact.dispatch(event.bodyA, event.bodyB, event.shapeA, event.shapeB);
if (event.bodyA.parent)
{
event.bodyA.parent.onEndContact.dispatch(event.bodyB.parent, event.bodyB, event.shapeA, event.shapeB);
}
if (event.bodyB.parent)
{
event.bodyB.parent.onEndContact.dispatch(event.bodyA.parent, event.bodyA, event.shapeB, event.shapeA);
}
}
},
/**
* Sets the bounds of the Physics world to match the Game.World dimensions.
* You can optionally set which 'walls' to create: left, right, top or bottom.
*
* @method Phaser.Physics#setBoundsToWorld
* @param {boolean} [left=true] - If true will create the left bounds wall.
* @param {boolean} [right=true] - If true will create the right bounds wall.
* @param {boolean} [top=true] - If true will create the top bounds wall.
* @param {boolean} [bottom=true] - If true will create the bottom bounds wall.
* @param {boolean} [setCollisionGroup=true] - If true the Bounds will be set to use its own Collision Group.
*/
setBoundsToWorld: function (left, right, top, bottom, setCollisionGroup)
{
this.setBounds(this.game.world.bounds.x, this.game.world.bounds.y, this.game.world.bounds.width, this.game.world.bounds.height, left, right, top, bottom, setCollisionGroup);
},
/**
* Sets the given material against the 4 bounds of this World.
*
* @method Phaser.Physics#setWorldMaterial
* @param {Phaser.Physics.P2.Material} material - The material to set.
* @param {boolean} [left=true] - If true will set the material on the left bounds wall.
* @param {boolean} [right=true] - If true will set the material on the right bounds wall.
* @param {boolean} [top=true] - If true will set the material on the top bounds wall.
* @param {boolean} [bottom=true] - If true will set the material on the bottom bounds wall.
*/
setWorldMaterial: function (material, left, right, top, bottom)
{
if (left === undefined) { left = true; }
if (right === undefined) { right = true; }
if (top === undefined) { top = true; }
if (bottom === undefined) { bottom = true; }
if (left && this.walls.left)
{
this.walls.left.shapes[0].material = material;
}
if (right && this.walls.right)
{
this.walls.right.shapes[0].material = material;
}
if (top && this.walls.top)
{
this.walls.top.shapes[0].material = material;
}
if (bottom && this.walls.bottom)
{
this.walls.bottom.shapes[0].material = material;
}
},
/**
* By default the World will be set to collide everything with everything. The bounds of the world is a Body with 4 shapes, one for each face.
* If you start to use your own collision groups then your objects will no longer collide with the bounds.
* To fix this you need to adjust the bounds to use its own collision group first BEFORE changing your Sprites collision group.
*
* @method Phaser.Physics.P2#updateBoundsCollisionGroup
* @param {boolean} [setCollisionGroup=true] - If true the Bounds will be set to use its own Collision Group.
*/
updateBoundsCollisionGroup: function (setCollisionGroup)
{
if (setCollisionGroup === undefined) { setCollisionGroup = true; }
var mask = (setCollisionGroup) ? this.boundsCollisionGroup.mask : this.everythingCollisionGroup.mask;
if (this.walls.left)
{
this.walls.left.shapes[0].collisionGroup = mask;
}
if (this.walls.right)
{
this.walls.right.shapes[0].collisionGroup = mask;
}
if (this.walls.top)
{
this.walls.top.shapes[0].collisionGroup = mask;
}
if (this.walls.bottom)
{
this.walls.bottom.shapes[0].collisionGroup = mask;
}
this._boundsOwnGroup = setCollisionGroup;
},
/**
* Sets the bounds of the Physics world to match the given world pixel dimensions.
* You can optionally set which 'walls' to create: left, right, top or bottom.
* If none of the walls are given it will default to use the walls settings it had previously.
* I.e. if you previously told it to not have the left or right walls, and you then adjust the world size
* the newly created bounds will also not have the left and right walls.
* Explicitly state them in the parameters to override this.
*
* @method Phaser.Physics.P2#setBounds
* @param {number} x - The x coordinate of the top-left corner of the bounds.
* @param {number} y - The y coordinate of the top-left corner of the bounds.
* @param {number} width - The width of the bounds.
* @param {number} height - The height of the bounds.
* @param {boolean} [left=true] - If true will create the left bounds wall.
* @param {boolean} [right=true] - If true will create the right bounds wall.
* @param {boolean} [top=true] - If true will create the top bounds wall.
* @param {boolean} [bottom=true] - If true will create the bottom bounds wall.
* @param {boolean} [setCollisionGroup=true] - If true the Bounds will be set to use its own Collision Group.
*/
setBounds: function (x, y, width, height, left, right, top, bottom, setCollisionGroup)
{
if (left === undefined) { left = this._boundsLeft; }
if (right === undefined) { right = this._boundsRight; }
if (top === undefined) { top = this._boundsTop; }
if (bottom === undefined) { bottom = this._boundsBottom; }
if (setCollisionGroup === undefined) { setCollisionGroup = this._boundsOwnGroup; }
this.setupWall(left, 'left', x, y, 1.5707963267948966, setCollisionGroup);
this.setupWall(right, 'right', x + width, y, -1.5707963267948966, setCollisionGroup);
this.setupWall(top, 'top', x, y, -3.141592653589793, setCollisionGroup);
this.setupWall(bottom, 'bottom', x, y + height, 0, setCollisionGroup);
// Remember the bounds settings in case they change later on via World.setBounds
this._boundsLeft = left;
this._boundsRight = right;
this._boundsTop = top;
this._boundsBottom = bottom;
this._boundsOwnGroup = setCollisionGroup;
},
/**
* Internal method called by setBounds. Responsible for creating, updating or
* removing the wall body shapes.
*
* @method Phaser.Physics.P2#setupWall
* @private
* @param {boolean} create - True to create the wall shape, false to remove it.
* @param {string} wall - The wall segment to update.
* @param {number} x - The x coordinate of the wall.
* @param {number} y - The y coordinate of the wall.
* @param {float} angle - The angle of the wall.
* @param {boolean} [setCollisionGroup=true] - If true the Bounds will be set to use its own Collision Group.
*/
setupWall: function (create, wall, x, y, angle, setCollisionGroup)
{
if (create)
{
// We need a left wall. Do we have one already?
if (this.walls[wall])
{
this.walls[wall].position = [ this.pxmi(x), this.pxmi(y) ];
}
else
{
this.walls[wall] = new p2.Body({ mass: 0, position: [ this.pxmi(x), this.pxmi(y) ], angle: angle });
this.walls[wall].addShape(new p2.Plane());
this.world.addBody(this.walls[wall]);
}
if (setCollisionGroup)
{
this.walls[wall].shapes[0].collisionGroup = this.boundsCollisionGroup.mask;
}
}
else
if (this.walls[wall])
{
this.world.removeBody(this.walls[wall]);
this.walls[wall] = null;
}
},
/**
* Pauses the P2 World independent of the game pause state.
*
* @method Phaser.Physics.P2#pause
*/
pause: function ()
{
this.paused = true;
},
/**
* Resumes a paused P2 World.
*
* @method Phaser.Physics.P2#resume
*/
resume: function ()
{
this.paused = false;
},
/**
* Internal P2 update loop.
*
* @method Phaser.Physics.P2#update
*/
update: function ()
{
// Do nothing if the physics engine was paused before
if (this.paused)
{
return;
}
if (this.useElapsedTime)
{
this.world.step(this.game.time.physicsElapsed);
}
else
{
this.world.step(this.frameRate);
}
},
/**
* Called by Phaser.Physics when a State swap occurs.
* Starts the begin and end Contact listeners again.
*
* @method Phaser.Physics.P2#reset
*/
reset: function ()
{
this.world.on('beginContact', this.beginContactHandler, this);
this.world.on('endContact', this.endContactHandler, this);
this.nothingCollisionGroup = new Phaser.Physics.P2.CollisionGroup(1);
this.boundsCollisionGroup = new Phaser.Physics.P2.CollisionGroup(2);
this.everythingCollisionGroup = new Phaser.Physics.P2.CollisionGroup(2147483648);
this._collisionGroupID = 2;
this.setBoundsToWorld(true, true, true, true, false);
},
/**
* Clears all bodies from the simulation, resets callbacks and resets the collision bitmask.
*
* The P2 world is also cleared:
*
* * Removes all solver equations
* * Removes all constraints
* * Removes all bodies
* * Removes all springs
* * Removes all contact materials
*
* This is called automatically when you switch state.
*
* @method Phaser.Physics.P2#clear
*/
clear: function ()
{
this.world.time = 0;
this.world.fixedStepTime = 0;
// Remove all solver equations
if (this.world.solver && this.world.solver.equations.length)
{
this.world.solver.removeAllEquations();
}
// Remove all constraints
var cs = this.world.constraints;
for (var i = cs.length - 1; i >= 0; i--)
{
this.world.removeConstraint(cs[i]);
}
// Remove all bodies
var bodies = this.world.bodies;
for (var i = bodies.length - 1; i >= 0; i--)
{
this.world.removeBody(bodies[i]);
}
// Remove all springs
var springs = this.world.springs;
for (var i = springs.length - 1; i >= 0; i--)
{
this.world.removeSpring(springs[i]);
}
// Remove all contact materials
var cms = this.world.contactMaterials;
for (var i = cms.length - 1; i >= 0; i--)
{
this.world.removeContactMaterial(cms[i]);
}
this.world.off('beginContact', this.beginContactHandler, this);
this.world.off('endContact', this.endContactHandler, this);
this.postBroadphaseCallback = null;
this.callbackContext = null;
this.impactCallback = null;
this.collisionGroups = [];
this._toRemove = [];
this.boundsCollidesWith = [];
// Remove the world bounds
this.walls = { left: null, right: null, top: null, bottom: null };
},
/**
* Clears all bodies from the simulation and unlinks World from Game. Should only be called on game shutdown. Call `clear` on a State change.
*
* @method Phaser.Physics.P2#destroy
*/
destroy: function ()
{
this.clear();
this.game = null;
},
/**
* Add a body to the world.
*
* @method Phaser.Physics.P2#addBody
* @param {Phaser.Physics.P2.Body} body - The Body to add to the World.
* @return {boolean} True if the Body was added successfully, otherwise false.
*/
addBody: function (body)
{
if (body.data.world)
{
return false;
}
else
{
this.world.addBody(body.data);
this.onBodyAdded.dispatch(body);
return true;
}
},
/**
* Removes a body from the world. This will silently fail if the body wasn't part of the world to begin with.
*
* @method Phaser.Physics.P2#removeBody
* @param {Phaser.Physics.P2.Body} body - The Body to remove from the World.
* @return {Phaser.Physics.P2.Body} The Body that was removed.
*/
removeBody: function (body)
{
if (body.data.world === this.world)
{
this.world.removeBody(body.data);
this.onBodyRemoved.dispatch(body);
}
return body;
},
/**
* Adds a Spring to the world.
*
* @method Phaser.Physics.P2#addSpring
* @param {Phaser.Physics.P2.Spring|p2.LinearSpring|p2.RotationalSpring} spring - The Spring to add to the World.
* @return {Phaser.Physics.P2.Spring} The Spring that was added.
*/
addSpring: function (spring)
{
if (spring instanceof Phaser.Physics.P2.Spring || spring instanceof Phaser.Physics.P2.RotationalSpring)
{
this.world.addSpring(spring.data);
}
else
{
this.world.addSpring(spring);
}
this.onSpringAdded.dispatch(spring);
return spring;
},
/**
* Removes a Spring from the world.
*
* @method Phaser.Physics.P2#removeSpring
* @param {Phaser.Physics.P2.Spring} spring - The Spring to remove from the World.
* @return {Phaser.Physics.P2.Spring} The Spring that was removed.
*/
removeSpring: function (spring)
{
if (spring instanceof Phaser.Physics.P2.Spring || spring instanceof Phaser.Physics.P2.RotationalSpring)
{
this.world.removeSpring(spring.data);
}
else
{
this.world.removeSpring(spring);
}
this.onSpringRemoved.dispatch(spring);
return spring;
},
/**
* Creates a constraint that tries to keep the distance between two bodies constant.
*
* @method Phaser.Physics.P2#createDistanceConstraint
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyA - First connected body.
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyB - Second connected body.
* @param {number} distance - The distance to keep between the bodies.
* @param {Array} [localAnchorA] - The anchor point for bodyA, defined locally in bodyA frame. Defaults to [0,0].
* @param {Array} [localAnchorB] - The anchor point for bodyB, defined locally in bodyB frame. Defaults to [0,0].
* @param {number} [maxForce] - The maximum force that should be applied to constrain the bodies.
* @return {Phaser.Physics.P2.DistanceConstraint} The constraint
*/
createDistanceConstraint: function (bodyA, bodyB, distance, localAnchorA, localAnchorB, maxForce)
{
bodyA = this.getBody(bodyA);
bodyB = this.getBody(bodyB);
if (!bodyA || !bodyB)
{
console.warn('Cannot create Constraint, invalid body objects given');
}
else
{
return this.addConstraint(new Phaser.Physics.P2.DistanceConstraint(this, bodyA, bodyB, distance, localAnchorA, localAnchorB, maxForce));
}
},
/**
* Creates a constraint that tries to keep the relative angle between two bodies constant.
*
* @method Phaser.Physics.P2#createGearConstraint
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyA - First connected body.
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyB - Second connected body.
* @param {number} [angle=0] - The relative angle
* @param {number} [ratio=1] - The gear ratio.
* @return {Phaser.Physics.P2.GearConstraint} The constraint
*/
createGearConstraint: function (bodyA, bodyB, angle, ratio)
{
bodyA = this.getBody(bodyA);
bodyB = this.getBody(bodyB);
if (!bodyA || !bodyB)
{
console.warn('Cannot create Constraint, invalid body objects given');
}
else
{
return this.addConstraint(new Phaser.Physics.P2.GearConstraint(this, bodyA, bodyB, angle, ratio));
}
},
/**
* Connects two bodies at given offset points, letting them rotate relative to each other around this point.
* The pivot points are given in world (pixel) coordinates.
*
* @method Phaser.Physics.P2#createRevoluteConstraint
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyA - First connected body.
* @param {Array} pivotA - The point relative to the center of mass of bodyA which bodyA is constrained to. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyB - Second connected body.
* @param {Array} pivotB - The point relative to the center of mass of bodyB which bodyB is constrained to. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {number} [maxForce=0] - The maximum force that should be applied to constrain the bodies.
* @param {Float32Array} [worldPivot=null] - A pivot point given in world coordinates. If specified, localPivotA and localPivotB are automatically computed from this value.
* @return {Phaser.Physics.P2.RevoluteConstraint} The constraint
*/
createRevoluteConstraint: function (bodyA, pivotA, bodyB, pivotB, maxForce, worldPivot)
{
bodyA = this.getBody(bodyA);
bodyB = this.getBody(bodyB);
if (!bodyA || !bodyB)
{
console.warn('Cannot create Constraint, invalid body objects given');
}
else
{
return this.addConstraint(new Phaser.Physics.P2.RevoluteConstraint(this, bodyA, pivotA, bodyB, pivotB, maxForce, worldPivot));
}
},
/**
* Locks the relative position between two bodies.
*
* @method Phaser.Physics.P2#createLockConstraint
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyA - First connected body.
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyB - Second connected body.
* @param {Array} [offset] - The offset of bodyB in bodyA's frame. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {number} [angle=0] - The angle of bodyB in bodyA's frame.
* @param {number} [maxForce] - The maximum force that should be applied to constrain the bodies.
* @return {Phaser.Physics.P2.LockConstraint} The constraint
*/
createLockConstraint: function (bodyA, bodyB, offset, angle, maxForce)
{
bodyA = this.getBody(bodyA);
bodyB = this.getBody(bodyB);
if (!bodyA || !bodyB)
{
console.warn('Cannot create Constraint, invalid body objects given');
}
else
{
return this.addConstraint(new Phaser.Physics.P2.LockConstraint(this, bodyA, bodyB, offset, angle, maxForce));
}
},
/**
* Constraint that only allows bodies to move along a line, relative to each other.
* See http://www.iforce2d.net/b2dtut/joints-prismatic
*
* @method Phaser.Physics.P2#createPrismaticConstraint
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyA - First connected body.
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyB - Second connected body.
* @param {boolean} [lockRotation=true] - If set to false, bodyB will be free to rotate around its anchor point.
* @param {Array} [anchorA] - Body A's anchor point, defined in its own local frame. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Array} [anchorB] - Body A's anchor point, defined in its own local frame. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Array} [axis] - An axis, defined in body A frame, that body B's anchor point may slide along. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {number} [maxForce] - The maximum force that should be applied to constrain the bodies.
* @return {Phaser.Physics.P2.PrismaticConstraint} The constraint
*/
createPrismaticConstraint: function (bodyA, bodyB, lockRotation, anchorA, anchorB, axis, maxForce)
{
bodyA = this.getBody(bodyA);
bodyB = this.getBody(bodyB);
if (!bodyA || !bodyB)
{
console.warn('Cannot create Constraint, invalid body objects given');
}
else
{
return this.addConstraint(new Phaser.Physics.P2.PrismaticConstraint(this, bodyA, bodyB, lockRotation, anchorA, anchorB, axis, maxForce));
}
},
/**
* Adds a Constraint to the world.
*
* @method Phaser.Physics.P2#addConstraint
* @param {Phaser.Physics.P2.Constraint} constraint - The Constraint to add to the World.
* @return {Phaser.Physics.P2.Constraint} The Constraint that was added.
*/
addConstraint: function (constraint)
{
this.world.addConstraint(constraint);
this.onConstraintAdded.dispatch(constraint);
return constraint;
},
/**
* Removes a Constraint from the world.
*
* @method Phaser.Physics.P2#removeConstraint
* @param {Phaser.Physics.P2.Constraint} constraint - The Constraint to be removed from the World.
* @return {Phaser.Physics.P2.Constraint} The Constraint that was removed.
*/
removeConstraint: function (constraint)
{
this.world.removeConstraint(constraint);
this.onConstraintRemoved.dispatch(constraint);
return constraint;
},
/**
* Adds a Contact Material to the world.
*
* @method Phaser.Physics.P2#addContactMaterial
* @param {Phaser.Physics.P2.ContactMaterial} material - The Contact Material to be added to the World.
* @return {Phaser.Physics.P2.ContactMaterial} The Contact Material that was added.
*/
addContactMaterial: function (material)
{
this.world.addContactMaterial(material);
this.onContactMaterialAdded.dispatch(material);
return material;
},
/**
* Removes a Contact Material from the world.
*
* @method Phaser.Physics.P2#removeContactMaterial
* @param {Phaser.Physics.P2.ContactMaterial} material - The Contact Material to be removed from the World.
* @return {Phaser.Physics.P2.ContactMaterial} The Contact Material that was removed.
*/
removeContactMaterial: function (material)
{
this.world.removeContactMaterial(material);
this.onContactMaterialRemoved.dispatch(material);
return material;
},
/**
* Gets a Contact Material based on the two given Materials.
*
* @method Phaser.Physics.P2#getContactMaterial
* @param {Phaser.Physics.P2.Material} materialA - The first Material to search for.
* @param {Phaser.Physics.P2.Material} materialB - The second Material to search for.
* @return {Phaser.Physics.P2.ContactMaterial|boolean} The Contact Material or false if none was found matching the Materials given.
*/
getContactMaterial: function (materialA, materialB)
{
return this.world.getContactMaterial(materialA, materialB);
},
/**
* Sets the given Material against all Shapes owned by all the Bodies in the given array.
*
* @method Phaser.Physics.P2#setMaterial
* @param {Phaser.Physics.P2.Material} material - The Material to be applied to the given Bodies.
* @param {array} bodies - An Array of Body objects that the given Material will be set on.
*/
setMaterial: function (material, bodies)
{
var i = bodies.length;
while (i--)
{
bodies[i].setMaterial(material);
}
},
/**
* Creates a Material. Materials are applied to Shapes owned by a Body and can be set with Body.setMaterial().
* Materials are a way to control what happens when Shapes collide. Combine unique Materials together to create Contact Materials.
* Contact Materials have properties such as friction and restitution that allow for fine-grained collision control between different Materials.
*
* @method Phaser.Physics.P2#createMaterial
* @param {string} [name] - Optional name of the Material. Each Material has a unique ID but string names are handy for debugging.
* @param {Phaser.Physics.P2.Body} [body] - Optional Body. If given it will assign the newly created Material to the Body shapes.
* @return {Phaser.Physics.P2.Material} The Material that was created. This is also stored in Phaser.Physics.P2.materials.
*/
createMaterial: function (name, body)
{
name = name || '';
var material = new Phaser.Physics.P2.Material(name);
this.materials.push(material);
if (typeof body !== 'undefined')
{
body.setMaterial(material);
}
return material;
},
/**
* Creates a Contact Material from the two given Materials. You can then edit the properties of the Contact Material directly.
*
* @method Phaser.Physics.P2#createContactMaterial
* @param {Phaser.Physics.P2.Material} [materialA] - The first Material to create the ContactMaterial from. If undefined it will create a new Material object first.
* @param {Phaser.Physics.P2.Material} [materialB] - The second Material to create the ContactMaterial from. If undefined it will create a new Material object first.
* @param {object} [options] - Material options object.
* @return {Phaser.Physics.P2.ContactMaterial} The Contact Material that was created.
*/
createContactMaterial: function (materialA, materialB, options)
{
if (materialA === undefined) { materialA = this.createMaterial(); }
if (materialB === undefined) { materialB = this.createMaterial(); }
var contact = new Phaser.Physics.P2.ContactMaterial(materialA, materialB, options);
return this.addContactMaterial(contact);
},
/**
* Populates and returns an array with references to of all current Bodies in the world.
*
* @method Phaser.Physics.P2#getBodies
* @return {array} An array containing all current Bodies in the world.
*/
getBodies: function ()
{
var output = [];
var i = this.world.bodies.length;
while (i--)
{
output.push(this.world.bodies[i].parent);
}
return output;
},
/**
* Checks the given object to see if it has a p2.Body and if so returns it.
*
* @method Phaser.Physics.P2#getBody
* @param {object} object - The object to check for a p2.Body on.
* @return {p2.Body} The p2.Body, or null if not found.
*/
getBody: function (object)
{
if (object instanceof p2.Body)
{
// Native p2 body
return object;
}
else if (object instanceof Phaser.Physics.P2.Body)
{
// Phaser P2 Body
return object.data;
}
else if (object['body'] && object['body'].type === Phaser.Physics.P2JS)
{
// Sprite, TileSprite, etc
return object.body.data;
}
return null;
},
/**
* Populates and returns an array of all current Springs in the world.
*
* @method Phaser.Physics.P2#getSprings
* @return {array} An array containing all current Springs in the world.
*/
getSprings: function ()
{
var output = [];
var i = this.world.springs.length;
while (i--)
{
output.push(this.world.springs[i].parent);
}
return output;
},
/**
* Populates and returns an array of all current Constraints in the world.
* You will get an array of p2 constraints back. This can be of mixed types, for example the array may contain
* PrismaticConstraints, RevoluteConstraints or any other valid p2 constraint type.
*
* @method Phaser.Physics.P2#getConstraints
* @return {array} An array containing all current Constraints in the world.
*/
getConstraints: function ()
{
var output = [];
var i = this.world.constraints.length;
while (i--)
{
output.push(this.world.constraints[i]);
}
return output;
},
/**
* Test if a world point overlaps bodies. You will get an array of actual P2 bodies back. You can find out which Sprite a Body belongs to
* (if any) by checking the Body.parent.sprite property. Body.parent is a Phaser.Physics.P2.Body property.
*
* @method Phaser.Physics.P2#hitTest
* @param {Phaser.Point} worldPoint - Point to use for intersection tests. The points values must be in world (pixel) coordinates.
* @param {Array} [bodies] - A list of objects to check for intersection. If not given it will check Phaser.Physics.P2.world.bodies (i.e. all world bodies)
* @param {number} [precision=5] - Used for matching against particles and lines. Adds some margin to these infinitesimal objects.
* @param {boolean} [filterStatic=false] - If true all Static objects will be removed from the results array.
* @return {Array} Array of bodies that overlap the point.
*/
hitTest: function (worldPoint, bodies, precision, filterStatic)
{
if (bodies === undefined) { bodies = this.world.bodies; }
if (precision === undefined) { precision = 5; }
if (filterStatic === undefined) { filterStatic = false; }
var physicsPosition = [ this.pxmi(worldPoint.x), this.pxmi(worldPoint.y) ];
var query = [];
var i = bodies.length;
while (i--)
{
if (bodies[i] instanceof Phaser.Physics.P2.Body && !(filterStatic && bodies[i].data.type === p2.Body.STATIC))
{
query.push(bodies[i].data);
}
else if (bodies[i] instanceof p2.Body && bodies[i].parent && !(filterStatic && bodies[i].type === p2.Body.STATIC))
{
query.push(bodies[i]);
}
else if (bodies[i] instanceof Phaser.Sprite && bodies[i].hasOwnProperty('body') && !(filterStatic && bodies[i].body.data.type === p2.Body.STATIC))
{
query.push(bodies[i].body.data);
}
}
return this.world.hitTest(physicsPosition, query, precision);
},
/**
* Converts the current world into a JSON object.
*
* @method Phaser.Physics.P2#toJSON
* @return {object} A JSON representation of the world.
*/
toJSON: function ()
{
return this.world.toJSON();
},
/**
* Creates a new Collision Group and optionally applies it to the given object.
* Collision Groups are handled using bitmasks, therefore you have a fixed limit you can create before you need to re-use older groups.
*
* @method Phaser.Physics.P2#createCollisionGroup
* @param {Phaser.Group|Phaser.Sprite} [object] - An optional Sprite or Group to apply the Collision Group to. If a Group is given it will be applied to all top-level children.
*/
createCollisionGroup: function (object)
{
var bitmask = Math.pow(2, this._collisionGroupID);
if (this.walls.left)
{
this.walls.left.shapes[0].collisionMask = this.walls.left.shapes[0].collisionMask | bitmask;
}
if (this.walls.right)
{
this.walls.right.shapes[0].collisionMask = this.walls.right.shapes[0].collisionMask | bitmask;
}
if (this.walls.top)
{
this.walls.top.shapes[0].collisionMask = this.walls.top.shapes[0].collisionMask | bitmask;
}
if (this.walls.bottom)
{
this.walls.bottom.shapes[0].collisionMask = this.walls.bottom.shapes[0].collisionMask | bitmask;
}
this._collisionGroupID++;
var group = new Phaser.Physics.P2.CollisionGroup(bitmask);
this.collisionGroups.push(group);
if (object)
{
this.setCollisionGroup(object, group);
}
return group;
},
/**
* Sets the given CollisionGroup to be the collision group for all shapes in this Body, unless a shape is specified.
* Note that this resets the collisionMask and any previously set groups. See Body.collides() for appending them.
*
* @method Phaser.Physics.P2y#setCollisionGroup
* @param {Phaser.Group|Phaser.Sprite} object - A Sprite or Group to apply the Collision Group to. If a Group is given it will be applied to all top-level children.
* @param {Phaser.Physics.CollisionGroup} group - The Collision Group that this Bodies shapes will use.
*/
setCollisionGroup: function (object, group)
{
if (object instanceof Phaser.Group)
{
for (var i = 0; i < object.total; i++)
{
if (object.children[i]['body'] && object.children[i]['body'].type === Phaser.Physics.P2JS)
{
object.children[i].body.setCollisionGroup(group);
}
}
}
else
{
object.body.setCollisionGroup(group);
}
},
/**
* Creates a linear spring, connecting two bodies. A spring can have a resting length, a stiffness and damping.
*
* @method Phaser.Physics.P2#createSpring
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyA - First connected body.
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyB - Second connected body.
* @param {number} [restLength=1] - Rest length of the spring. A number > 0.
* @param {number} [stiffness=100] - Stiffness of the spring. A number >= 0.
* @param {number} [damping=1] - Damping of the spring. A number >= 0.
* @param {Array} [worldA] - Where to hook the spring to body A in world coordinates. This value is an array by 2 elements, x and y, i.e: [32, 32].
* @param {Array} [worldB] - Where to hook the spring to body B in world coordinates. This value is an array by 2 elements, x and y, i.e: [32, 32].
* @param {Array} [localA] - Where to hook the spring to body A in local body coordinates. This value is an array by 2 elements, x and y, i.e: [32, 32].
* @param {Array} [localB] - Where to hook the spring to body B in local body coordinates. This value is an array by 2 elements, x and y, i.e: [32, 32].
* @return {Phaser.Physics.P2.Spring} The spring
*/
createSpring: function (bodyA, bodyB, restLength, stiffness, damping, worldA, worldB, localA, localB)
{
bodyA = this.getBody(bodyA);
bodyB = this.getBody(bodyB);
if (!bodyA || !bodyB)
{
console.warn('Cannot create Spring, invalid body objects given');
}
else
{
return this.addSpring(new Phaser.Physics.P2.Spring(this, bodyA, bodyB, restLength, stiffness, damping, worldA, worldB, localA, localB));
}
},
/**
* Creates a rotational spring, connecting two bodies. A spring can have a resting length, a stiffness and damping.
*
* @method Phaser.Physics.P2#createRotationalSpring
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyA - First connected body.
* @param {Phaser.Sprite|Phaser.Physics.P2.Body|p2.Body} bodyB - Second connected body.
* @param {number} [restAngle] - The relative angle of bodies at which the spring is at rest. If not given, it's set to the current relative angle between the bodies.
* @param {number} [stiffness=100] - Stiffness of the spring. A number >= 0.
* @param {number} [damping=1] - Damping of the spring. A number >= 0.
* @return {Phaser.Physics.P2.RotationalSpring} The spring
*/
createRotationalSpring: function (bodyA, bodyB, restAngle, stiffness, damping)
{
bodyA = this.getBody(bodyA);
bodyB = this.getBody(bodyB);
if (!bodyA || !bodyB)
{
console.warn('Cannot create Rotational Spring, invalid body objects given');
}
else
{
return this.addSpring(new Phaser.Physics.P2.RotationalSpring(this, bodyA, bodyB, restAngle, stiffness, damping));
}
},
/**
* Creates a new Body and adds it to the World.
*
* @method Phaser.Physics.P2#createBody
* @param {number} x - The x coordinate of Body.
* @param {number} y - The y coordinate of Body.
* @param {number} mass - The mass of the Body. A mass of 0 means a 'static' Body is created.
* @param {boolean} [addToWorld=false] - Automatically add this Body to the world? (usually false as it won't have any shapes on construction).
* @param {object} options - An object containing the build options:
* @param {boolean} [options.optimalDecomp=false] - Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices.
* @param {boolean} [options.skipSimpleCheck=false] - Set to true if you already know that the path is not intersecting itself.
* @param {boolean|number} [options.removeCollinearPoints=false] - Set to a number (angle threshold value) to remove collinear points, or false to keep all points.
* @param {(number[]|...number)} points - An array of 2d vectors that form the convex or concave polygon.
* Either [[0,0], [0,1],...] or a flat array of numbers that will be interpreted as [x,y, x,y, ...],
* or the arguments passed can be flat x,y values e.g. `setPolygon(options, x,y, x,y, x,y, ...)` where `x` and `y` are numbers.
* @return {Phaser.Physics.P2.Body} The body
*/
createBody: function (x, y, mass, addToWorld, options, data)
{
if (addToWorld === undefined) { addToWorld = false; }
var body = new Phaser.Physics.P2.Body(this.game, null, x, y, mass);
if (data)
{
var result = body.addPolygon(options, data);
if (!result)
{
return false;
}
}
if (addToWorld)
{
this.world.addBody(body.data);
}
return body;
},
/**
* Creates a new Particle and adds it to the World.
*
* @method Phaser.Physics.P2#createParticle
* @param {number} x - The x coordinate of Body.
* @param {number} y - The y coordinate of Body.
* @param {number} mass - The mass of the Body. A mass of 0 means a 'static' Body is created.
* @param {boolean} [addToWorld=false] - Automatically add this Body to the world? (usually false as it won't have any shapes on construction).
* @param {object} options - An object containing the build options:
* @param {boolean} [options.optimalDecomp=false] - Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices.
* @param {boolean} [options.skipSimpleCheck=false] - Set to true if you already know that the path is not intersecting itself.
* @param {boolean|number} [options.removeCollinearPoints=false] - Set to a number (angle threshold value) to remove collinear points, or false to keep all points.
* @param {(number[]|...number)} points - An array of 2d vectors that form the convex or concave polygon.
* Either [[0,0], [0,1],...] or a flat array of numbers that will be interpreted as [x,y, x,y, ...],
* or the arguments passed can be flat x,y values e.g. `setPolygon(options, x,y, x,y, x,y, ...)` where `x` and `y` are numbers.
*/
createParticle: function (x, y, mass, addToWorld, options, data)
{
if (addToWorld === undefined) { addToWorld = false; }
var body = new Phaser.Physics.P2.Body(this.game, null, x, y, mass);
if (data)
{
var result = body.addPolygon(options, data);
if (!result)
{
return false;
}
}
if (addToWorld)
{
this.world.addBody(body.data);
}
return body;
},
/**
* Converts all of the polyline, polygon, and rectangle objects inside a Tiled ObjectGroup into physics bodies that are added to the world.
* Note that the polylines and polygons must be created in such a way that they can withstand polygon decomposition.
*
* @method Phaser.Physics.P2#convertCollisionObjects
* @param {Phaser.Tilemap} map - The Tilemap to get the map data from.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to map.currentLayer.
* @param {boolean} [addToWorld=true] - If true it will automatically add each body to the world.
* @return {array} An array of the Phaser.Physics.Body objects that have been created.
*/
convertCollisionObjects: function (map, layer, addToWorld)
{
if (addToWorld === undefined) { addToWorld = true; }
var output = [];
for (var i = 0, len = map.collision[layer].length; i < len; i++)
{
// name: json.layers[i].objects[v].name,
// x: json.layers[i].objects[v].x,
// y: json.layers[i].objects[v].y,
// width: json.layers[i].objects[v].width,
// height: json.layers[i].objects[v].height,
// visible: json.layers[i].objects[v].visible,
// properties: json.layers[i].objects[v].properties,
// polyline: json.layers[i].objects[v].polyline
var object = map.collision[layer][i];
var shapeData = object.polyline || object.polygon;
// polyline/polygon shape data present
if (shapeData)
{
var body = this.createBody(object.x, object.y, 0, addToWorld, {}, shapeData);
}
// tilemap parser sets rectangle=true when parsing object groups
else if (object.rectangle)
{
var body = this.createBody(object.x, object.y, 0, addToWorld);
body.addRectangle(object.width, object.height, object.width / 2, object.height / 2);
}
// ellipse could be added here, but Tiled ellipses use height/width instead of radius
// (to support oblong ellipses), which p2 doesn't currently support.
if (body)
{
output.push(body);
}
}
return output;
},
/**
* Clears all physics bodies from the given TilemapLayer that were created with `World.convertTilemap`.
*
* @method Phaser.Physics.P2#clearTilemapLayerBodies
* @param {Phaser.Tilemap} map - The Tilemap to get the map data from.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to map.currentLayer.
*/
clearTilemapLayerBodies: function (map, layer)
{
layer = map.getLayer(layer);
var i = map.layers[layer].bodies.length;
while (i--)
{
map.layers[layer].bodies[i].destroy();
}
map.layers[layer].bodies.length = 0;
},
/**
* Goes through all tiles in the given Tilemap and TilemapLayer and converts those set to collide into physics bodies.
* Only call this *after* you have specified all of the tiles you wish to collide with calls like Tilemap.setCollisionBetween, etc.
* Every time you call this method it will destroy any previously created bodies and remove them from the world.
* Therefore understand it's a very expensive operation and not to be done in a core game update loop.
*
* @method Phaser.Physics.P2#convertTilemap
* @param {Phaser.Tilemap} map - The Tilemap to get the map data from.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to map.currentLayer.
* @param {boolean} [addToWorld=true] - If true it will automatically add each body to the world, otherwise it's up to you to do so.
* @param {boolean} [optimize=true] - If true adjacent colliding tiles will be combined into a single body to save processing. However it means you cannot perform specific Tile to Body collision responses.
* @return {array} An array of the Phaser.Physics.P2.Body objects that were created.
*/
convertTilemap: function (map, layer, addToWorld, optimize)
{
layer = map.getLayer(layer);
if (addToWorld === undefined) { addToWorld = true; }
if (optimize === undefined) { optimize = true; }
// If the bodies array is already populated we need to nuke it
this.clearTilemapLayerBodies(map, layer);
var width = 0;
var sx = 0;
var sy = 0;
for (var y = 0, h = map.layers[layer].height; y < h; y++)
{
width = 0;
for (var x = 0, w = map.layers[layer].width; x < w; x++)
{
var tile = map.layers[layer].data[y][x];
if (tile && tile.index > -1 && tile.collides)
{
if (optimize)
{
var right = map.getTileRight(layer, x, y);
if (width === 0)
{
sx = tile.x * tile.width;
sy = tile.y * tile.height;
width = tile.width;
}
if (right && right.collides)
{
width += tile.width;
}
else
{
var body = this.createBody(sx, sy, 0, false);
body.addRectangle(width, tile.height, width / 2, tile.height / 2, 0);
if (addToWorld)
{
this.addBody(body);
}
map.layers[layer].bodies.push(body);
width = 0;
}
}
else
{
var body = this.createBody(tile.x * tile.width, tile.y * tile.height, 0, false);
body.addRectangle(tile.width, tile.height, tile.width / 2, tile.height / 2, 0);
if (addToWorld)
{
this.addBody(body);
}
map.layers[layer].bodies.push(body);
}
}
}
}
return map.layers[layer].bodies;
},
/**
* Convert p2 physics value (meters) to pixel scale.
* By default Phaser uses a scale of 20px per meter.
* If you need to modify this you can over-ride these functions via the Physics Configuration object.
*
* @method Phaser.Physics.P2#mpx
* @param {number} v - The value to convert.
* @return {number} The scaled value.
*/
mpx: function (v)
{
return v *= 20;
},
/**
* Convert pixel value to p2 physics scale (meters).
* By default Phaser uses a scale of 20px per meter.
* If you need to modify this you can over-ride these functions via the Physics Configuration object.
*
* @method Phaser.Physics.P2#pxm
* @param {number} v - The value to convert.
* @return {number} The scaled value.
*/
pxm: function (v)
{
return v * 0.05;
},
/**
* Convert p2 physics value (meters) to pixel scale and inverses it.
* By default Phaser uses a scale of 20px per meter.
* If you need to modify this you can over-ride these functions via the Physics Configuration object.
*
* @method Phaser.Physics.P2#mpxi
* @param {number} v - The value to convert.
* @return {number} The scaled value.
*/
mpxi: function (v)
{
return v *= -20;
},
/**
* Convert pixel value to p2 physics scale (meters) and inverses it.
* By default Phaser uses a scale of 20px per meter.
* If you need to modify this you can over-ride these functions via the Physics Configuration object.
*
* @method Phaser.Physics.P2#pxmi
* @param {number} v - The value to convert.
* @return {number} The scaled value.
*/
pxmi: function (v)
{
return v * -0.05;
}
};
/**
* @name Phaser.Physics.P2#friction
* @property {number} friction - Friction between colliding bodies. This value is used if no matching ContactMaterial is found for a Material pair.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'friction', {
get: function ()
{
return this.world.defaultContactMaterial.friction;
},
set: function (value)
{
this.world.defaultContactMaterial.friction = value;
}
});
/**
* @name Phaser.Physics.P2#restitution
* @property {number} restitution - Default coefficient of restitution between colliding bodies. This value is used if no matching ContactMaterial is found for a Material pair.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'restitution', {
get: function ()
{
return this.world.defaultContactMaterial.restitution;
},
set: function (value)
{
this.world.defaultContactMaterial.restitution = value;
}
});
/**
* @name Phaser.Physics.P2#contactMaterial
* @property {p2.ContactMaterial} contactMaterial - The default Contact Material being used by the World.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'contactMaterial', {
get: function ()
{
return this.world.defaultContactMaterial;
},
set: function (value)
{
this.world.defaultContactMaterial = value;
}
});
/**
* @name Phaser.Physics.P2#applySpringForces
* @property {boolean} applySpringForces - Enable to automatically apply spring forces each step.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'applySpringForces', {
get: function ()
{
return this.world.applySpringForces;
},
set: function (value)
{
this.world.applySpringForces = value;
}
});
/**
* @name Phaser.Physics.P2#applyDamping
* @property {boolean} applyDamping - Enable to automatically apply body damping each step.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'applyDamping', {
get: function ()
{
return this.world.applyDamping;
},
set: function (value)
{
this.world.applyDamping = value;
}
});
/**
* @name Phaser.Physics.P2#applyGravity
* @property {boolean} applyGravity - Enable to automatically apply gravity each step.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'applyGravity', {
get: function ()
{
return this.world.applyGravity;
},
set: function (value)
{
this.world.applyGravity = value;
}
});
/**
* @name Phaser.Physics.P2#solveConstraints
* @property {boolean} solveConstraints - Enable/disable constraint solving in each step.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'solveConstraints', {
get: function ()
{
return this.world.solveConstraints;
},
set: function (value)
{
this.world.solveConstraints = value;
}
});
/**
* @name Phaser.Physics.P2#time
* @property {boolean} time - The World time.
* @readonly
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'time', {
get: function ()
{
return this.world.time;
}
});
/**
* @name Phaser.Physics.P2#emitImpactEvent
* @property {boolean} emitImpactEvent - Set to true if you want to the world to emit the "impact" event. Turning this off could improve performance.
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'emitImpactEvent', {
get: function ()
{
return this.world.emitImpactEvent;
},
set: function (value)
{
this.world.emitImpactEvent = value;
}
});
/**
* How to deactivate bodies during simulation. Possible modes are: World.NO_SLEEPING, World.BODY_SLEEPING and World.ISLAND_SLEEPING.
* If sleeping is enabled, you might need to wake up the bodies if they fall asleep when they shouldn't. If you want to enable sleeping in the world, but want to disable it for a particular body, see Body.allowSleep.
* @name Phaser.Physics.P2#sleepMode
* @property {number} sleepMode
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'sleepMode', {
get: function ()
{
return this.world.sleepMode;
},
set: function (value)
{
this.world.sleepMode = value;
}
});
/**
* @name Phaser.Physics.P2#total
* @property {number} total - The total number of bodies in the world.
* @readonly
*/
Object.defineProperty(Phaser.Physics.P2.prototype, 'total', {
get: function ()
{
return this.world.bodies.length;
}
});
/* jshint noarg: false */
/**
* @author Georgios Kaleadis https://github.com/georgiee
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Allow to access a list of created fixture (coming from Body#addPhaserPolygon)
* which itself parse the input from PhysicsEditor with the custom phaser exporter.
* You can access fixtures of a Body by a group index or even by providing a fixture Key.
* You can set the fixture key and also the group index for a fixture in PhysicsEditor.
* This gives you the power to create a complex body built of many fixtures and modify them
* during runtime (to remove parts, set masks, categories & sensor properties)
*
* @class Phaser.Physics.P2.FixtureList
* @constructor
* @param {Array} list - A list of fixtures (from Phaser.Physics.P2.Body#addPhaserPolygon)
*/
Phaser.Physics.P2.FixtureList = function (list)
{
if (!Array.isArray(list))
{
list = [ list ];
}
this.rawList = list;
this.init();
this.parse(this.rawList);
};
Phaser.Physics.P2.FixtureList.prototype = {
/**
* @method Phaser.Physics.P2.FixtureList#init
*/
init: function ()
{
/**
* @property {object} namedFixtures - Collect all fixtures with a key
* @private
*/
this.namedFixtures = {};
/**
* @property {Array} groupedFixtures - Collect all given fixtures per group index. Notice: Every fixture with a key also belongs to a group
* @private
*/
this.groupedFixtures = [];
/**
* @property {Array} allFixtures - This is a list of everything in this collection
* @private
*/
this.allFixtures = [];
},
/**
* @method Phaser.Physics.P2.FixtureList#setCategory
* @param {number} bit - The bit to set as the collision group.
* @param {string} fixtureKey - Only apply to the fixture with the given key.
*/
setCategory: function (bit, fixtureKey)
{
var setter = function (fixture)
{
fixture.collisionGroup = bit;
};
this.getFixtures(fixtureKey).forEach(setter);
},
/**
* @method Phaser.Physics.P2.FixtureList#setMask
* @param {number} bit - The bit to set as the collision mask
* @param {string} fixtureKey - Only apply to the fixture with the given key
*/
setMask: function (bit, fixtureKey)
{
var setter = function (fixture)
{
fixture.collisionMask = bit;
};
this.getFixtures(fixtureKey).forEach(setter);
},
/**
* @method Phaser.Physics.P2.FixtureList#setSensor
* @param {boolean} value - sensor true or false
* @param {string} fixtureKey - Only apply to the fixture with the given key
*/
setSensor: function (value, fixtureKey)
{
var setter = function (fixture)
{
fixture.sensor = value;
};
this.getFixtures(fixtureKey).forEach(setter);
},
/**
* @method Phaser.Physics.P2.FixtureList#setMaterial
* @param {Object} material - The contact material for a fixture
* @param {string} fixtureKey - Only apply to the fixture with the given key
*/
setMaterial: function (material, fixtureKey)
{
var setter = function (fixture)
{
fixture.material = material;
};
this.getFixtures(fixtureKey).forEach(setter);
},
/**
* Accessor to get either a list of specified fixtures by key or the whole fixture list
*
* @method Phaser.Physics.P2.FixtureList#getFixtures
* @param {array} keys - A list of fixture keys
*/
getFixtures: function (keys)
{
var fixtures = [];
if (keys)
{
if (!(keys instanceof Array))
{
keys = [ keys ];
}
var self = this;
keys.forEach(function (key)
{
if (self.namedFixtures[key])
{
fixtures.push(self.namedFixtures[key]);
}
});
return this.flatten(fixtures);
}
else
{
return this.allFixtures;
}
},
/**
* Accessor to get either a single fixture by its key.
*
* @method Phaser.Physics.P2.FixtureList#getFixtureByKey
* @param {string} key - The key of the fixture.
*/
getFixtureByKey: function (key)
{
return this.namedFixtures[key];
},
/**
* Accessor to get a group of fixtures by its group index.
*
* @method Phaser.Physics.P2.FixtureList#getGroup
* @param {number} groupID - The group index.
*/
getGroup: function (groupID)
{
return this.groupedFixtures[groupID];
},
/**
* Parser for the output of Phaser.Physics.P2.Body#addPhaserPolygon
*
* @method Phaser.Physics.P2.FixtureList#parse
*/
parse: function ()
{
var key, value, _ref, _results;
_ref = this.rawList;
_results = [];
for (key in _ref)
{
value = _ref[key];
if (!isNaN(key - 0))
{
this.groupedFixtures[key] = this.groupedFixtures[key] || [];
this.groupedFixtures[key] = this.groupedFixtures[key].concat(value);
}
else
{
this.namedFixtures[key] = this.flatten(value);
}
_results.push(this.allFixtures = this.flatten(this.groupedFixtures));
}
},
/**
* A helper to flatten arrays. This is very useful as the fixtures are nested from time to time due to the way P2 creates and splits polygons.
*
* @method Phaser.Physics.P2.FixtureList#flatten
* @param {array} array - The array to flatten. Notice: This will happen recursive not shallow.
*/
flatten: function (array)
{
var result, self;
result = [];
self = arguments.callee;
array.forEach(function (item)
{
return Array.prototype.push.apply(result, (Array.isArray(item) ? self(item) : [ item ]));
});
return result;
}
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A PointProxy is an internal class that allows for direct getter/setter style property access to Arrays and TypedArrays.
*
* @class Phaser.Physics.P2.PointProxy
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {any} destination - The object to bind to.
*/
Phaser.Physics.P2.PointProxy = function (world, destination)
{
this.world = world;
this.destination = destination;
};
Phaser.Physics.P2.PointProxy.prototype.constructor = Phaser.Physics.P2.PointProxy;
/**
* @name Phaser.Physics.P2.PointProxy#x
* @property {number} x - The x property of this PointProxy get and set in pixels.
*/
Object.defineProperty(Phaser.Physics.P2.PointProxy.prototype, 'x', {
get: function ()
{
return this.world.mpx(this.destination[0]);
},
set: function (value)
{
this.destination[0] = this.world.pxm(value);
}
});
/**
* @name Phaser.Physics.P2.PointProxy#y
* @property {number} y - The y property of this PointProxy get and set in pixels.
*/
Object.defineProperty(Phaser.Physics.P2.PointProxy.prototype, 'y', {
get: function ()
{
return this.world.mpx(this.destination[1]);
},
set: function (value)
{
this.destination[1] = this.world.pxm(value);
}
});
/**
* @name Phaser.Physics.P2.PointProxy#mx
* @property {number} mx - The x property of this PointProxy get and set in meters.
*/
Object.defineProperty(Phaser.Physics.P2.PointProxy.prototype, 'mx', {
get: function ()
{
return this.destination[0];
},
set: function (value)
{
this.destination[0] = value;
}
});
/**
* @name Phaser.Physics.P2.PointProxy#my
* @property {number} my - The x property of this PointProxy get and set in meters.
*/
Object.defineProperty(Phaser.Physics.P2.PointProxy.prototype, 'my', {
get: function ()
{
return this.destination[1];
},
set: function (value)
{
this.destination[1] = value;
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A InversePointProxy is an internal class that allows for direct getter/setter style property access to Arrays and TypedArrays but inverses the values on set.
*
* @class Phaser.Physics.P2.InversePointProxy
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {any} destination - The object to bind to.
*/
Phaser.Physics.P2.InversePointProxy = function (world, destination)
{
this.world = world;
this.destination = destination;
};
Phaser.Physics.P2.InversePointProxy.prototype.constructor = Phaser.Physics.P2.InversePointProxy;
/**
* @name Phaser.Physics.P2.InversePointProxy#x
* @property {number} x - The x property of this InversePointProxy get and set in pixels.
*/
Object.defineProperty(Phaser.Physics.P2.InversePointProxy.prototype, 'x', {
get: function ()
{
return this.world.mpxi(this.destination[0]);
},
set: function (value)
{
this.destination[0] = this.world.pxmi(value);
}
});
/**
* @name Phaser.Physics.P2.InversePointProxy#y
* @property {number} y - The y property of this InversePointProxy get and set in pixels.
*/
Object.defineProperty(Phaser.Physics.P2.InversePointProxy.prototype, 'y', {
get: function ()
{
return this.world.mpxi(this.destination[1]);
},
set: function (value)
{
this.destination[1] = this.world.pxmi(value);
}
});
/**
* @name Phaser.Physics.P2.InversePointProxy#mx
* @property {number} mx - The x property of this InversePointProxy get and set in meters.
*/
Object.defineProperty(Phaser.Physics.P2.InversePointProxy.prototype, 'mx', {
get: function ()
{
return this.destination[0];
},
set: function (value)
{
this.destination[0] = -value;
}
});
/**
* @name Phaser.Physics.P2.InversePointProxy#my
* @property {number} my - The y property of this InversePointProxy get and set in meters.
*/
Object.defineProperty(Phaser.Physics.P2.InversePointProxy.prototype, 'my', {
get: function ()
{
return this.destination[1];
},
set: function (value)
{
this.destination[1] = -value;
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Physics Body is typically linked to a single Sprite and defines properties that determine how the physics body is simulated.
* These properties affect how the body reacts to forces, what forces it generates on itself (to simulate friction), and how it reacts to collisions in the scene.
* In most cases, the properties are used to simulate physical effects. Each body also has its own property values that determine exactly how it reacts to forces and collisions in the scene.
* By default a single Rectangle shape is added to the Body that matches the dimensions of the parent Sprite. See addShape, removeShape, clearShapes to add extra shapes around the Body.
* Note: When bound to a Sprite to avoid single-pixel jitters on mobile devices we strongly recommend using Sprite sizes that are even on both axis, i.e. 128x128 not 127x127.
* Note: When a game object is given a P2 body it has its anchor x/y set to 0.5, so it becomes centered.
*
* @class Phaser.Physics.P2.Body
* @constructor
* @param {Phaser.Game} game - Game reference to the currently running game.
* @param {Phaser.Sprite} [sprite] - The Sprite object this physics body belongs to.
* @param {number} [x=0] - The x coordinate of this Body.
* @param {number} [y=0] - The y coordinate of this Body.
* @param {number} [mass=1] - The default mass of this Body (0 = static).
*/
Phaser.Physics.P2.Body = function (game, sprite, x, y, mass)
{
sprite = sprite || null;
x = x || 0;
y = y || 0;
if (mass === undefined) { mass = 1; }
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = game;
/**
* @property {Phaser.Physics.P2} world - Local reference to the P2 World.
*/
this.world = game.physics.p2;
/**
* @property {Phaser.Sprite} sprite - Reference to the parent Sprite.
*/
this.sprite = sprite;
/**
* @property {number} type - The type of physics system this body belongs to.
*/
this.type = Phaser.Physics.P2JS;
/**
* @property {Phaser.Point} offset - The offset of the Physics Body from the Sprite x/y position.
*/
this.offset = new Phaser.Point();
/**
* @property {p2.Body} data - The p2 Body data.
* @protected
*/
this.data = new p2.Body({ position: [ this.world.pxmi(x), this.world.pxmi(y) ], mass: mass });
this.data.parent = this;
/**
* @property {Phaser.Physics.P2.InversePointProxy} velocity - The velocity of the body. Set velocity.x to a negative value to move to the left, position to the right. velocity.y negative values move up, positive move down.
*/
this.velocity = new Phaser.Physics.P2.InversePointProxy(this.world, this.data.velocity);
/**
* @property {Phaser.Physics.P2.InversePointProxy} force - The force applied to the body.
*/
this.force = new Phaser.Physics.P2.InversePointProxy(this.world, this.data.force);
/**
* @property {Phaser.Point} gravity - A locally applied gravity force to the Body. Applied directly before the world step. NOTE: Not currently implemented.
*/
this.gravity = new Phaser.Point();
/**
* Dispatched when a first contact is created between shapes in two bodies.
* This event is fired during the step, so collision has already taken place.
*
* The event will be sent 5 arguments in this order:
*
* The Phaser.Physics.P2.Body it is in contact with. *This might be null* if the Body was created directly in the p2 world.
* The p2.Body this Body is in contact with.
* The Shape from this body that caused the contact.
* The Shape from the contact body.
* The Contact Equation data array.
*
* @property {Phaser.Signal} onBeginContact
*/
this.onBeginContact = new Phaser.Signal();
/**
* Dispatched when contact ends between shapes in two bodies.
* This event is fired during the step, so collision has already taken place.
*
* The event will be sent 4 arguments in this order:
*
* The Phaser.Physics.P2.Body it is in contact with. *This might be null* if the Body was created directly in the p2 world.
* The p2.Body this Body has ended contact with.
* The Shape from this body that caused the original contact.
* The Shape from the contact body.
*
* @property {Phaser.Signal} onEndContact
*/
this.onEndContact = new Phaser.Signal();
/**
* @property {array} collidesWith - Array of CollisionGroups that this Bodies shapes collide with.
*/
this.collidesWith = [];
/**
* @property {boolean} removeNextStep - To avoid deleting this body during a physics step, and causing all kinds of problems, set removeNextStep to true to have it removed in the next preUpdate.
*/
this.removeNextStep = false;
/**
* @property {Phaser.Physics.P2.BodyDebug} debugBody - Reference to the debug body.
*/
this.debugBody = null;
/**
* @property {boolean} dirty - Internally used by Sprite.x/y
*/
this.dirty = false;
/**
* @property {boolean} _collideWorldBounds - Internal var that determines if this Body collides with the world bounds or not.
* @private
*/
this._collideWorldBounds = true;
/**
* @property {object} _bodyCallbacks - Array of Body callbacks.
* @private
*/
this._bodyCallbacks = {};
/**
* @property {object} _bodyCallbackContext - Array of Body callback contexts.
* @private
*/
this._bodyCallbackContext = {};
/**
* @property {object} _groupCallbacks - Array of Group callbacks.
* @private
*/
this._groupCallbacks = {};
/**
* @property {object} _bodyCallbackContext - Array of Grouo callback contexts.
* @private
*/
this._groupCallbackContext = {};
/**
* @property {boolean} _reset - Internal var.
* @private
*/
this._reset = false;
// Set-up the default shape
if (sprite)
{
this.setRectangleFromSprite(sprite);
if (sprite.exists)
{
this.game.physics.p2.addBody(this);
}
}
};
Phaser.Physics.P2.Body.prototype = {
/**
* Sets a callback to be fired any time a shape in this Body impacts with a shape in the given Body. The impact test is performed against body.id values.
* The callback will be sent 4 parameters: This body, the body that impacted, the Shape in this body and the shape in the impacting body.
* Note that the impact event happens after collision resolution, so it cannot be used to prevent a collision from happening.
* It also happens mid-step. So do not destroy a Body during this callback, instead set safeDestroy to true so it will be killed on the next preUpdate.
*
* @method Phaser.Physics.P2.Body#createBodyCallback
* @param {Phaser.Sprite|Phaser.TileSprite|Phaser.Physics.P2.Body|p2.Body} object - The object to send impact events for.
* @param {function} callback - The callback to fire on impact. Set to null to clear a previously set callback.
* @param {object} callbackContext - The context under which the callback will fire.
*/
createBodyCallback: function (object, callback, callbackContext)
{
var id = -1;
if (object['id'])
{
id = object.id;
}
else if (object['body'])
{
id = object.body.id;
}
if (id > -1)
{
if (callback === null)
{
delete (this._bodyCallbacks[id]);
delete (this._bodyCallbackContext[id]);
}
else
{
this._bodyCallbacks[id] = callback;
this._bodyCallbackContext[id] = callbackContext;
}
}
},
/**
* Sets a callback to be fired any time this Body impacts with the given Group. The impact test is performed against shape.collisionGroup values.
* The callback will be sent 4 parameters: This body, the body that impacted, the Shape in this body and the shape in the impacting body.
* This callback will only fire if this Body has been assigned a collision group.
* Note that the impact event happens after collision resolution, so it cannot be used to prevent a collision from happening.
* It also happens mid-step. So do not destroy a Body during this callback, instead set safeDestroy to true so it will be killed on the next preUpdate.
*
* @method Phaser.Physics.P2.Body#createGroupCallback
* @param {Phaser.Physics.CollisionGroup} group - The Group to send impact events for.
* @param {function} callback - The callback to fire on impact. Set to null to clear a previously set callback.
* @param {object} callbackContext - The context under which the callback will fire.
*/
createGroupCallback: function (group, callback, callbackContext)
{
if (callback === null)
{
delete (this._groupCallbacks[group.mask]);
delete (this._groupCallbackContext[group.mask]);
}
else
{
this._groupCallbacks[group.mask] = callback;
this._groupCallbackContext[group.mask] = callbackContext;
}
},
/**
* Gets the collision bitmask from the groups this body collides with.
*
* @method Phaser.Physics.P2.Body#getCollisionMask
* @return {number} The bitmask.
*/
getCollisionMask: function ()
{
var mask = 0;
if (this._collideWorldBounds)
{
mask = this.game.physics.p2.boundsCollisionGroup.mask;
}
for (var i = 0; i < this.collidesWith.length; i++)
{
mask = mask | this.collidesWith[i].mask;
}
return mask;
},
/**
* Updates the collisionMask.
*
* @method Phaser.Physics.P2.Body#updateCollisionMask
* @param {p2.Shape} [shape] - An optional Shape. If not provided the collision group will be added to all Shapes in this Body.
*/
updateCollisionMask: function (shape)
{
var mask = this.getCollisionMask();
if (shape === undefined)
{
for (var i = this.data.shapes.length - 1; i >= 0; i--)
{
this.data.shapes[i].collisionMask = mask;
}
}
else
{
shape.collisionMask = mask;
}
},
/**
* Sets the given CollisionGroup to be the collision group for all shapes in this Body, unless a shape is specified.
* This also resets the collisionMask.
*
* @method Phaser.Physics.P2.Body#setCollisionGroup
* @param {Phaser.Physics.CollisionGroup} group - The Collision Group that this Bodies shapes will use.
* @param {p2.Shape} [shape] - An optional Shape. If not provided the collision group will be added to all Shapes in this Body.
*/
setCollisionGroup: function (group, shape)
{
var mask = this.getCollisionMask();
if (shape === undefined)
{
for (var i = this.data.shapes.length - 1; i >= 0; i--)
{
this.data.shapes[i].collisionGroup = group.mask;
this.data.shapes[i].collisionMask = mask;
}
}
else
{
shape.collisionGroup = group.mask;
shape.collisionMask = mask;
}
},
/**
* Clears the collision data from the shapes in this Body. Optionally clears Group and/or Mask.
*
* @method Phaser.Physics.P2.Body#clearCollision
* @param {boolean} [clearGroup=true] - Clear the collisionGroup value from the shape/s?
* @param {boolean} [clearMask=true] - Clear the collisionMask value from the shape/s?
* @param {p2.Shape} [shape] - An optional Shape. If not provided the collision data will be cleared from all Shapes in this Body.
*/
clearCollision: function (clearGroup, clearMask, shape)
{
if (clearGroup === undefined) { clearGroup = true; }
if (clearMask === undefined) { clearMask = true; }
if (shape === undefined)
{
for (var i = this.data.shapes.length - 1; i >= 0; i--)
{
if (clearGroup)
{
this.data.shapes[i].collisionGroup = null;
}
if (clearMask)
{
this.data.shapes[i].collisionMask = null;
}
}
}
else
{
if (clearGroup)
{
shape.collisionGroup = null;
}
if (clearMask)
{
shape.collisionMask = null;
}
}
if (clearGroup)
{
this.collidesWith.length = 0;
}
},
/**
* Removes the given CollisionGroup, or array of CollisionGroups, from the list of groups that this body will collide with and updates the collision masks.
*
* @method Phaser.Physics.P2.Body#removeCollisionGroup
* @param {Phaser.Physics.CollisionGroup|array} group - The Collision Group or Array of Collision Groups that this Bodies shapes should not collide with anymore.
* @param {boolean} [clearCallback=true] - Clear the callback that will be triggered when this Body impacts with the given Group?
* @param {p2.Shape} [shape] - An optional Shape. If not provided the updated collision mask will be added to all Shapes in this Body.
*/
removeCollisionGroup: function (group, clearCallback, shape)
{
if (clearCallback === undefined) { clearCallback = true; }
var index;
if (Array.isArray(group))
{
for (var i = 0; i < group.length; i++)
{
index = this.collidesWith.indexOf(group[i]);
if (index > -1)
{
this.collidesWith.splice(index, 1);
if (clearCallback)
{
delete (this._groupCallbacks[group.mask]);
delete (this._groupCallbackContext[group.mask]);
}
}
}
}
else
{
index = this.collidesWith.indexOf(group);
if (index > -1)
{
this.collidesWith.splice(index, 1);
if (clearCallback)
{
delete (this._groupCallbacks[group.mask]);
delete (this._groupCallbackContext[group.mask]);
}
}
}
var mask = this.getCollisionMask();
if (shape === undefined)
{
for (var i = this.data.shapes.length - 1; i >= 0; i--)
{
this.data.shapes[i].collisionMask = mask;
}
}
else
{
shape.collisionMask = mask;
}
},
/**
* Adds the given CollisionGroup, or array of CollisionGroups, to the list of groups that this body will collide with and updates the collision masks.
*
* @method Phaser.Physics.P2.Body#collides
* @param {Phaser.Physics.CollisionGroup|array} group - The Collision Group or Array of Collision Groups that this Bodies shapes will collide with.
* @param {function} [callback] - Optional callback that will be triggered when this Body impacts with the given Group.
* @param {object} [callbackContext] - The context under which the callback will be called.
* @param {p2.Shape} [shape] - An optional Shape. If not provided the collision mask will be added to all Shapes in this Body.
*/
collides: function (group, callback, callbackContext, shape)
{
if (Array.isArray(group))
{
for (var i = 0; i < group.length; i++)
{
if (this.collidesWith.indexOf(group[i]) === -1)
{
this.collidesWith.push(group[i]);
if (callback)
{
this.createGroupCallback(group[i], callback, callbackContext);
}
}
}
}
else
if (this.collidesWith.indexOf(group) === -1)
{
this.collidesWith.push(group);
if (callback)
{
this.createGroupCallback(group, callback, callbackContext);
}
}
var mask = this.getCollisionMask();
if (shape === undefined)
{
for (var i = this.data.shapes.length - 1; i >= 0; i--)
{
this.data.shapes[i].collisionMask = mask;
}
}
else
{
shape.collisionMask = mask;
}
},
/**
* Moves the shape offsets so their center of mass becomes the body center of mass.
*
* @method Phaser.Physics.P2.Body#adjustCenterOfMass
*/
adjustCenterOfMass: function ()
{
this.data.adjustCenterOfMass();
this.shapeChanged();
},
/**
* Gets the velocity of a point in the body.
*
* @method Phaser.Physics.P2.Body#getVelocityAtPoint
* @param {Array} result - A vector to store the result in.
* @param {Array} relativePoint - A world oriented vector, indicating the position of the point to get the velocity from.
* @return {Array} The result vector.
*/
getVelocityAtPoint: function (result, relativePoint)
{
return this.data.getVelocityAtPoint(result, relativePoint);
},
/**
* Apply damping, see http://code.google.com/p/bullet/issues/detail?id=74 for details.
*
* @method Phaser.Physics.P2.Body#applyDamping
* @param {number} dt - Current time step.
*/
applyDamping: function (dt)
{
this.data.applyDamping(dt);
},
/**
* Apply impulse to a point relative to the body.
* This could for example be a point on the Body surface. An impulse is a force added to a body during a short
* period of time (impulse = force * time). Impulses will be added to Body.velocity and Body.angularVelocity.
*
* @method Phaser.Physics.P2.Body#applyImpulse
* @param {Float32Array|Array} impulse - The impulse vector to add, oriented in world space.
* @param {number} worldX - A point relative to the body in world space. If not given, it is set to zero and all of the impulse will be exerted on the center of mass.
* @param {number} worldY - A point relative to the body in world space. If not given, it is set to zero and all of the impulse will be exerted on the center of mass.
*/
applyImpulse: function (impulse, worldX, worldY)
{
this.data.applyImpulse(impulse, [ this.world.pxmi(worldX), this.world.pxmi(worldY) ]);
},
/**
* Apply impulse to a point local to the body.
*
* This could for example be a point on the Body surface. An impulse is a force added to a body during a short
* period of time (impulse = force * time). Impulses will be added to Body.velocity and Body.angularVelocity.
*
* @method Phaser.Physics.P2.Body#applyImpulseLocal
* @param {Float32Array|Array} impulse - The impulse vector to add, oriented in local space.
* @param {number} localX - A local point on the body.
* @param {number} localY - A local point on the body.
*/
applyImpulseLocal: function (impulse, localX, localY)
{
this.data.applyImpulseLocal(impulse, [ this.world.pxmi(localX), this.world.pxmi(localY) ]);
},
/**
* Apply force to a world point.
*
* This could for example be a point on the RigidBody surface. Applying force
* this way will add to Body.force and Body.angularForce.
*
* @method Phaser.Physics.P2.Body#applyForce
* @param {Float32Array|Array} force - The force vector to add.
* @param {number} worldX - The world x point to apply the force on.
* @param {number} worldY - The world y point to apply the force on.
*/
applyForce: function (force, worldX, worldY)
{
this.data.applyForce(force, [ this.world.pxmi(worldX), this.world.pxmi(worldY) ]);
},
/**
* Sets the force on the body to zero.
*
* @method Phaser.Physics.P2.Body#setZeroForce
*/
setZeroForce: function ()
{
this.data.setZeroForce();
},
/**
* If this Body is dynamic then this will zero its angular velocity.
*
* @method Phaser.Physics.P2.Body#setZeroRotation
*/
setZeroRotation: function ()
{
this.data.angularVelocity = 0;
},
/**
* If this Body is dynamic then this will zero its velocity on both axis.
*
* @method Phaser.Physics.P2.Body#setZeroVelocity
*/
setZeroVelocity: function ()
{
this.data.velocity[0] = 0;
this.data.velocity[1] = 0;
},
/**
* Sets the Body damping and angularDamping to zero.
*
* @method Phaser.Physics.P2.Body#setZeroDamping
*/
setZeroDamping: function ()
{
this.data.damping = 0;
this.data.angularDamping = 0;
},
/**
* Transform a world point to local body frame.
*
* @method Phaser.Physics.P2.Body#toLocalFrame
* @param {Float32Array|Array} out - The vector to store the result in.
* @param {Float32Array|Array} worldPoint - The input world vector.
*/
toLocalFrame: function (out, worldPoint)
{
return this.data.toLocalFrame(out, worldPoint);
},
/**
* Transform a local point to world frame.
*
* @method Phaser.Physics.P2.Body#toWorldFrame
* @param {Array} out - The vector to store the result in.
* @param {Array} localPoint - The input local vector.
*/
toWorldFrame: function (out, localPoint)
{
return this.data.toWorldFrame(out, localPoint);
},
/**
* This will rotate the Body by the given speed to the left (counter-clockwise).
*
* @method Phaser.Physics.P2.Body#rotateLeft
* @param {number} speed - The speed at which it should rotate.
*/
rotateLeft: function (speed)
{
this.data.angularVelocity = this.world.pxm(-speed);
},
/**
* This will rotate the Body by the given speed to the left (clockwise).
*
* @method Phaser.Physics.P2.Body#rotateRight
* @param {number} speed - The speed at which it should rotate.
*/
rotateRight: function (speed)
{
this.data.angularVelocity = this.world.pxm(speed);
},
/**
* Moves the Body forwards based on its current angle and the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#moveForward
* @param {number} speed - The speed at which it should move forwards.
*/
moveForward: function (speed)
{
var magnitude = this.world.pxmi(-speed);
var angle = this.data.angle + Math.PI / 2;
this.data.velocity[0] = magnitude * Math.cos(angle);
this.data.velocity[1] = magnitude * Math.sin(angle);
},
/**
* Moves the Body backwards based on its current angle and the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#moveBackward
* @param {number} speed - The speed at which it should move backwards.
*/
moveBackward: function (speed)
{
var magnitude = this.world.pxmi(-speed);
var angle = this.data.angle + Math.PI / 2;
this.data.velocity[0] = -(magnitude * Math.cos(angle));
this.data.velocity[1] = -(magnitude * Math.sin(angle));
},
/**
* Applies a force to the Body that causes it to 'thrust' forwards, based on its current angle and the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#thrust
* @param {number} speed - The speed at which it should thrust.
*/
thrust: function (speed)
{
var magnitude = this.world.pxmi(-speed);
var angle = this.data.angle + Math.PI / 2;
this.data.force[0] += magnitude * Math.cos(angle);
this.data.force[1] += magnitude * Math.sin(angle);
},
/**
* Applies a force to the Body that causes it to 'thrust' to the left, based on its current angle and the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#thrustLeft
* @param {number} speed - The speed at which it should move to the left.
*/
thrustLeft: function (speed)
{
var magnitude = this.world.pxmi(-speed);
var angle = this.data.angle;
this.data.force[0] += magnitude * Math.cos(angle);
this.data.force[1] += magnitude * Math.sin(angle);
},
/**
* Applies a force to the Body that causes it to 'thrust' to the right, based on its current angle and the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#thrustRight
* @param {number} speed - The speed at which it should move to the right.
*/
thrustRight: function (speed)
{
var magnitude = this.world.pxmi(-speed);
var angle = this.data.angle;
this.data.force[0] -= magnitude * Math.cos(angle);
this.data.force[1] -= magnitude * Math.sin(angle);
},
/**
* Applies a force to the Body that causes it to 'thrust' backwards (in reverse), based on its current angle and the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#reverse
* @param {number} speed - The speed at which it should reverse.
*/
reverse: function (speed)
{
var magnitude = this.world.pxmi(-speed);
var angle = this.data.angle + Math.PI / 2;
this.data.force[0] -= magnitude * Math.cos(angle);
this.data.force[1] -= magnitude * Math.sin(angle);
},
/**
* If this Body is dynamic then this will move it to the left by setting its x velocity to the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#moveLeft
* @param {number} speed - The speed at which it should move to the left, in pixels per second.
*/
moveLeft: function (speed)
{
this.data.velocity[0] = this.world.pxmi(-speed);
},
/**
* If this Body is dynamic then this will move it to the right by setting its x velocity to the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#moveRight
* @param {number} speed - The speed at which it should move to the right, in pixels per second.
*/
moveRight: function (speed)
{
this.data.velocity[0] = this.world.pxmi(speed);
},
/**
* If this Body is dynamic then this will move it up by setting its y velocity to the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#moveUp
* @param {number} speed - The speed at which it should move up, in pixels per second.
*/
moveUp: function (speed)
{
this.data.velocity[1] = this.world.pxmi(-speed);
},
/**
* If this Body is dynamic then this will move it down by setting its y velocity to the given speed.
* The speed is represented in pixels per second. So a value of 100 would move 100 pixels in 1 second (1000ms).
*
* @method Phaser.Physics.P2.Body#moveDown
* @param {number} speed - The speed at which it should move down, in pixels per second.
*/
moveDown: function (speed)
{
this.data.velocity[1] = this.world.pxmi(speed);
},
/**
* Internal method. This is called directly before the sprites are sent to the renderer and after the update function has finished.
*
* @method Phaser.Physics.P2.Body#preUpdate
* @protected
*/
preUpdate: function ()
{
this.dirty = true;
if (this.removeNextStep)
{
this.removeFromWorld();
this.removeNextStep = false;
}
},
/**
* Internal method. This is called directly before the sprites are sent to the renderer and after the update function has finished.
*
* @method Phaser.Physics.P2.Body#postUpdate
* @protected
*/
postUpdate: function ()
{
this.sprite.x = this.world.mpxi(this.data.position[0]) + this.offset.x;
this.sprite.y = this.world.mpxi(this.data.position[1]) + this.offset.y;
if (!this.fixedRotation)
{
this.sprite.rotation = this.data.angle;
}
if (this.debugBody)
{
this.debugBody.updateSpriteTransform();
}
this.dirty = false;
},
/**
* Resets the Body force, velocity (linear and angular) and rotation. Optionally resets damping and mass.
*
* @method Phaser.Physics.P2.Body#reset
* @param {number} x - The new x position of the Body.
* @param {number} y - The new x position of the Body.
* @param {boolean} [resetDamping=false] - Resets the linear and angular damping.
* @param {boolean} [resetMass=false] - Sets the Body mass back to 1.
*/
reset: function (x, y, resetDamping, resetMass)
{
if (resetDamping === undefined) { resetDamping = false; }
if (resetMass === undefined) { resetMass = false; }
this.setZeroForce();
this.setZeroVelocity();
this.setZeroRotation();
if (resetDamping)
{
this.setZeroDamping();
}
if (resetMass)
{
this.mass = 1;
}
this.x = x;
this.y = y;
},
/**
* Adds this physics body to the world.
*
* @method Phaser.Physics.P2.Body#addToWorld
*/
addToWorld: function ()
{
if (this.game.physics.p2._toRemove)
{
for (var i = 0; i < this.game.physics.p2._toRemove.length; i++)
{
if (this.game.physics.p2._toRemove[i] === this)
{
this.game.physics.p2._toRemove.splice(i, 1);
}
}
}
if (this.data.world !== this.game.physics.p2.world)
{
this.game.physics.p2.addBody(this);
}
},
/**
* Removes this physics body from the world.
*
* @method Phaser.Physics.P2.Body#removeFromWorld
*/
removeFromWorld: function ()
{
if (this.data.world === this.game.physics.p2.world)
{
this.game.physics.p2.removeBodyNextStep(this);
}
},
/**
* Destroys this Body and all references it holds to other objects.
*
* @method Phaser.Physics.P2.Body#destroy
*/
destroy: function ()
{
this.removeFromWorld();
this.clearShapes();
this._bodyCallbacks = {};
this._bodyCallbackContext = {};
this._groupCallbacks = {};
this._groupCallbackContext = {};
if (this.debugBody)
{
this.debugBody.destroy(true, true);
}
this.debugBody = null;
if (this.sprite)
{
this.sprite.body = null;
this.sprite = null;
}
},
/**
* Removes all Shapes from this Body.
*
* @method Phaser.Physics.P2.Body#clearShapes
*/
clearShapes: function ()
{
var i = this.data.shapes.length;
while (i--)
{
this.data.removeShape(this.data.shapes[i]);
}
this.shapeChanged();
},
/**
* Add a shape to the body. You can pass a local transform when adding a shape, so that the shape gets an offset and an angle relative to the body center of mass.
* Will automatically update the mass properties and bounding radius.
* If this Body had a previously set Collision Group you will need to re-apply it to the new Shape this creates.
*
* @method Phaser.Physics.P2.Body#addShape
* @param {p2.Shape} shape - The shape to add to the body.
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Shape} The shape that was added to the body.
*/
addShape: function (shape, offsetX, offsetY, rotation)
{
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
if (rotation === undefined) { rotation = 0; }
this.data.addShape(shape, [ this.world.pxmi(offsetX), this.world.pxmi(offsetY) ], rotation);
this.shapeChanged();
return shape;
},
/**
* Adds a Circle shape to this Body. You can control the offset from the center of the body and the rotation.
*
* @method Phaser.Physics.P2.Body#addCircle
* @param {number} radius - The radius of this circle (in pixels)
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Circle} The Circle shape that was added to the Body.
*/
addCircle: function (radius, offsetX, offsetY, rotation)
{
var shape = new p2.Circle({ radius: this.world.pxm(radius) });
return this.addShape(shape, offsetX, offsetY, rotation);
},
/**
* Adds a Rectangle shape to this Body. You can control the offset from the center of the body and the rotation.
*
* @method Phaser.Physics.P2.Body#addRectangle
* @param {number} width - The width of the rectangle in pixels.
* @param {number} height - The height of the rectangle in pixels.
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Box} The shape that was added to the Body.
*/
addRectangle: function (width, height, offsetX, offsetY, rotation)
{
var shape = new p2.Box({ width: this.world.pxm(width), height: this.world.pxm(height)});
return this.addShape(shape, offsetX, offsetY, rotation);
},
/**
* Adds a Plane shape to this Body. The plane is facing in the Y direction. You can control the offset from the center of the body and the rotation.
*
* @method Phaser.Physics.P2.Body#addPlane
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Plane} The Plane shape that was added to the Body.
*/
addPlane: function (offsetX, offsetY, rotation)
{
var shape = new p2.Plane();
return this.addShape(shape, offsetX, offsetY, rotation);
},
/**
* Adds a Particle shape to this Body. You can control the offset from the center of the body and the rotation.
*
* @method Phaser.Physics.P2.Body#addParticle
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Particle} The Particle shape that was added to the Body.
*/
addParticle: function (offsetX, offsetY, rotation)
{
var shape = new p2.Particle();
return this.addShape(shape, offsetX, offsetY, rotation);
},
/**
* Adds a Line shape to this Body.
* The line shape is along the x direction, and stretches from [-length/2, 0] to [length/2,0].
* You can control the offset from the center of the body and the rotation.
*
* @method Phaser.Physics.P2.Body#addLine
* @param {number} length - The length of this line (in pixels)
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Line} The Line shape that was added to the Body.
*/
addLine: function (length, offsetX, offsetY, rotation)
{
var shape = new p2.Line({ length: this.world.pxm(length)});
return this.addShape(shape, offsetX, offsetY, rotation);
},
/**
* Adds a Capsule shape to this Body.
* You can control the offset from the center of the body and the rotation.
*
* @method Phaser.Physics.P2.Body#addCapsule
* @param {number} length - The distance between the end points in pixels.
* @param {number} radius - Radius of the capsule in pixels.
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Capsule} The Capsule shape that was added to the Body.
*/
addCapsule: function (length, radius, offsetX, offsetY, rotation)
{
var shape = new p2.Capsule({ length: this.world.pxm(length), radius: this.world.pxm(radius) });
return this.addShape(shape, offsetX, offsetY, rotation);
},
/**
* Reads a polygon shape path, and assembles convex shapes from that and puts them at proper offset points. The shape must be simple and without holes.
* This function expects the x.y values to be given in pixels. If you want to provide them at p2 world scales then call Body.data.fromPolygon directly.
*
* @method Phaser.Physics.P2.Body#addPolygon
* @param {object} options - An object containing the build options:
* @param {boolean} [options.optimalDecomp=false] - Set to true if you need optimal decomposition. Warning: very slow for polygons with more than 10 vertices.
* @param {boolean} [options.skipSimpleCheck=false] - Set to true if you already know that the path is not intersecting itself.
* @param {boolean|number} [options.removeCollinearPoints=false] - Set to a number (angle threshold value) to remove collinear points, or false to keep all points.
* @param {(number[]|...number)} points - An array of 2d vectors that form the convex or concave polygon.
* Either [[0,0], [0,1],...] or a flat array of numbers that will be interpreted as [x,y, x,y, ...]. In the first form **the array will mutate**.
* Or the arguments passed can be flat x,y values e.g. `setPolygon(options, x,y, x,y, x,y, ...)` where `x` and `y` are numbers.
* @return {boolean} True on success, else false.
*/
addPolygon: function (options, points)
{
options = options || {};
if (!Array.isArray(points))
{
points = Array.prototype.slice.call(arguments, 1);
}
var path = [];
// Did they pass in a single array of points?
if (points.length === 1 && Array.isArray(points[0]))
{
path = points[0].slice(0);
}
else if (Array.isArray(points[0]))
{
path = points.slice();
}
else if (typeof points[0] === 'number')
{
// We've a list of numbers
for (var i = 0, len = points.length; i < len; i += 2)
{
path.push([ points[i], points[i + 1] ]);
}
}
// top and tail
var idx = path.length - 1;
if (path[idx][0] === path[0][0] && path[idx][1] === path[0][1])
{
path.pop();
}
// Now process them into p2 values
for (var p = 0; p < path.length; p++)
{
path[p][0] = this.world.pxmi(path[p][0]);
path[p][1] = this.world.pxmi(path[p][1]);
}
var result = this.data.fromPolygon(path, options);
this.shapeChanged();
return result;
},
/**
* Remove a shape from the body. Will automatically update the mass properties and bounding radius.
*
* @method Phaser.Physics.P2.Body#removeShape
* @param {p2.Circle|p2.Rectangle|p2.Plane|p2.Line|p2.Particle} shape - The shape to remove from the body.
* @return {boolean} True if the shape was found and removed, else false.
*/
removeShape: function (shape)
{
var result = this.data.removeShape(shape);
this.shapeChanged();
return result;
},
/**
* Clears any previously set shapes. Then creates a new Circle shape and adds it to this Body.
* If this Body had a previously set Collision Group you will need to re-apply it to the new Shape this creates.
*
* @method Phaser.Physics.P2.Body#setCircle
* @param {number} radius - The radius of this circle (in pixels)
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
*/
setCircle: function (radius, offsetX, offsetY, rotation)
{
this.clearShapes();
return this.addCircle(radius, offsetX, offsetY, rotation);
},
/**
* Clears any previously set shapes. The creates a new Rectangle shape at the given size and offset, and adds it to this Body.
* If you wish to create a Rectangle to match the size of a Sprite or Image see Body.setRectangleFromSprite.
* If this Body had a previously set Collision Group you will need to re-apply it to the new Shape this creates.
*
* @method Phaser.Physics.P2.Body#setRectangle
* @param {number} [width=16] - The width of the rectangle in pixels.
* @param {number} [height=16] - The height of the rectangle in pixels.
* @param {number} [offsetX=0] - Local horizontal offset of the shape relative to the body center of mass.
* @param {number} [offsetY=0] - Local vertical offset of the shape relative to the body center of mass.
* @param {number} [rotation=0] - Local rotation of the shape relative to the body center of mass, specified in radians.
* @return {p2.Rectangle} The Rectangle shape that was added to the Body.
*/
setRectangle: function (width, height, offsetX, offsetY, rotation)
{
if (width === undefined) { width = 16; }
if (height === undefined) { height = 16; }
this.clearShapes();
return this.addRectangle(width, height, offsetX, offsetY, rotation);
},
/**
* Clears any previously set shapes.
* Then creates a Rectangle shape sized to match the dimensions and orientation of the Sprite given.
* If no Sprite is given it defaults to using the parent of this Body.
* If this Body had a previously set Collision Group you will need to re-apply it to the new Shape this creates.
*
* @method Phaser.Physics.P2.Body#setRectangleFromSprite
* @param {Phaser.Sprite|Phaser.Image} [sprite] - The Sprite on which the Rectangle will get its dimensions.
* @return {p2.Rectangle} The Rectangle shape that was added to the Body.
*/
setRectangleFromSprite: function (sprite)
{
if (sprite === undefined) { sprite = this.sprite; }
this.clearShapes();
return this.addRectangle(sprite.width, sprite.height, 0, 0, sprite.rotation);
},
/**
* Adds the given Material to all Shapes that belong to this Body.
* If you only wish to apply it to a specific Shape in this Body then provide that as the 2nd parameter.
*
* @method Phaser.Physics.P2.Body#setMaterial
* @param {Phaser.Physics.P2.Material} material - The Material that will be applied.
* @param {p2.Shape} [shape] - An optional Shape. If not provided the Material will be added to all Shapes in this Body.
*/
setMaterial: function (material, shape)
{
if (shape === undefined)
{
for (var i = this.data.shapes.length - 1; i >= 0; i--)
{
this.data.shapes[i].material = material;
}
}
else
{
shape.material = material;
}
},
/**
* Updates the debug draw if any body shapes change.
*
* @method Phaser.Physics.P2.Body#shapeChanged
*/
shapeChanged: function ()
{
if (this.debugBody)
{
this.debugBody.draw();
}
},
/**
* Reads the shape data from a physics data file stored in the Game.Cache and adds it as a polygon to this Body.
* The shape data format is based on the output of the
* {@link https://github.com/photonstorm/phaser/tree/master/resources/PhysicsEditor%20Exporter|custom phaser exporter} for
* {@link https://www.codeandweb.com/physicseditor|PhysicsEditor}
*
* @method Phaser.Physics.P2.Body#addPhaserPolygon
* @param {string} key - The key of the Physics Data file as stored in Game.Cache.
* @param {string} object - The key of the object within the Physics data file that you wish to load the shape data from.
* @returns {Array} A list of created fixtures to be used with Phaser.Physics.P2.FixtureList
*/
addPhaserPolygon: function (key, object)
{
var data = this.game.cache.getPhysicsData(key, object);
var createdFixtures = [];
// Cycle through the fixtures
for (var i = 0; i < data.length; i++)
{
var fixtureData = data[i];
var shapesOfFixture = this.addFixture(fixtureData);
// Always add to a group
createdFixtures[fixtureData.filter.group] = createdFixtures[fixtureData.filter.group] || [];
createdFixtures[fixtureData.filter.group] = createdFixtures[fixtureData.filter.group].concat(shapesOfFixture);
// if (unique) fixture key is provided
if (fixtureData.fixtureKey)
{
createdFixtures[fixtureData.fixtureKey] = shapesOfFixture;
}
}
this.data.aabbNeedsUpdate = true;
this.shapeChanged();
return createdFixtures;
},
/**
* Add a polygon fixture. This is used during #loadPolygon.
*
* @method Phaser.Physics.P2.Body#addFixture
* @param {string} fixtureData - The data for the fixture. It contains: isSensor, filter (collision) and the actual polygon shapes.
* @return {array} An array containing the generated shapes for the given polygon.
*/
addFixture: function (fixtureData)
{
var generatedShapes = [];
if (fixtureData.circle)
{
var shape = new p2.Circle({ radius: this.world.pxm(fixtureData.circle.radius) });
shape.collisionGroup = fixtureData.filter.categoryBits;
shape.collisionMask = fixtureData.filter.maskBits;
shape.sensor = fixtureData.isSensor;
var offset = p2.vec2.create();
offset[0] = this.world.pxmi(fixtureData.circle.position[0] - this.sprite.width / 2);
offset[1] = this.world.pxmi(fixtureData.circle.position[1] - this.sprite.height / 2);
this.data.addShape(shape, offset);
generatedShapes.push(shape);
}
else
{
var polygons = fixtureData.polygons;
var cm = p2.vec2.create();
for (var i = 0; i < polygons.length; i++)
{
var shapes = polygons[i];
var vertices = [];
for (var s = 0; s < shapes.length; s += 2)
{
vertices.push([ this.world.pxmi(shapes[s]), this.world.pxmi(shapes[s + 1]) ]);
}
var shape = new p2.Convex({ vertices: vertices });
// Move all vertices so its center of mass is in the local center of the convex
for (var j = 0; j !== shape.vertices.length; j++)
{
var v = shape.vertices[j];
p2.vec2.sub(v, v, shape.centerOfMass);
}
p2.vec2.scale(cm, shape.centerOfMass, 1);
cm[0] -= this.world.pxmi(this.sprite.width / 2);
cm[1] -= this.world.pxmi(this.sprite.height / 2);
shape.updateTriangles();
shape.updateCenterOfMass();
shape.updateBoundingRadius();
shape.collisionGroup = fixtureData.filter.categoryBits;
shape.collisionMask = fixtureData.filter.maskBits;
shape.sensor = fixtureData.isSensor;
this.data.addShape(shape, cm);
generatedShapes.push(shape);
}
}
return generatedShapes;
},
/**
* Reads the shape data from a physics data file stored in the Game.Cache and adds it as a polygon to this Body.
*
* As well as reading the data from the Cache you can also pass `null` as the first argument and a
* physics data object as the second. When doing this you must ensure the structure of the object is correct in advance.
*
* For more details see the format of the Lime / Corona Physics Editor export.
*
* @method Phaser.Physics.P2.Body#loadPolygon
* @param {string} key - The key of the Physics Data file as stored in Game.Cache. Alternatively set to `null` and pass the
* data as the 2nd argument.
* @param {string|object} object - The key of the object within the Physics data file that you wish to load the shape data from,
* or if key is null pass the actual physics data object itself as this parameter.
* @param {number} [scale=1] - Optionally resize the loaded polygon.
* @return {boolean} True on success, else false.
*/
loadPolygon: function (key, object, scale)
{
if (key === null)
{
var data = object;
}
else
{
var data = this.game.cache.getPhysicsData(key, object);
}
if (typeof scale !== 'number')
{
scale = 1;
}
// We've multiple Convex shapes, they should be CCW automatically
var cm = p2.vec2.create();
for (var i = 0; i < data.length; i++)
{
var vertices = [];
for (var s = 0; s < data[i].shape.length; s += 2)
{
vertices.push([
this.world.pxmi(data[i].shape[s] * scale),
this.world.pxmi(data[i].shape[s + 1] * scale)
]);
}
var c = new p2.Convex({ vertices: vertices });
// Move all vertices so its center of mass is in the local center of the convex
for (var j = 0; j !== c.vertices.length; j++)
{
var v = c.vertices[j];
p2.vec2.sub(v, v, c.centerOfMass);
}
p2.vec2.scale(cm, c.centerOfMass, 1);
cm[0] -= this.world.pxmi(this.sprite.width / 2);
cm[1] -= this.world.pxmi(this.sprite.height / 2);
c.updateTriangles();
c.updateCenterOfMass();
c.updateBoundingRadius();
this.data.addShape(c, cm);
}
this.data.aabbNeedsUpdate = true;
this.shapeChanged();
return true;
}
};
Phaser.Physics.P2.Body.prototype.constructor = Phaser.Physics.P2.Body;
/**
* Dynamic body. Dynamic bodies body can move and respond to collisions and forces.
* @property DYNAMIC
* @type {Number}
* @static
*/
Phaser.Physics.P2.Body.DYNAMIC = 1;
/**
* Static body. Static bodies do not move, and they do not respond to forces or collision.
* @property STATIC
* @type {Number}
* @static
*/
Phaser.Physics.P2.Body.STATIC = 2;
/**
* Kinematic body. Kinematic bodies only moves according to its .velocity, and does not respond to collisions or force.
* @property KINEMATIC
* @type {Number}
* @static
*/
Phaser.Physics.P2.Body.KINEMATIC = 4;
/**
* @name Phaser.Physics.P2.Body#static
* @property {boolean} static - Returns true if the Body is static. Setting Body.static to 'false' will make it dynamic.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'static', {
get: function ()
{
return (this.data.type === Phaser.Physics.P2.Body.STATIC);
},
set: function (value)
{
if (value && this.data.type !== Phaser.Physics.P2.Body.STATIC)
{
this.data.type = Phaser.Physics.P2.Body.STATIC;
this.mass = 0;
}
else if (!value && this.data.type === Phaser.Physics.P2.Body.STATIC)
{
this.data.type = Phaser.Physics.P2.Body.DYNAMIC;
this.mass = 1;
}
}
});
/**
* @name Phaser.Physics.P2.Body#dynamic
* @property {boolean} dynamic - Returns true if the Body is dynamic. Setting Body.dynamic to 'false' will make it static.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'dynamic', {
get: function ()
{
return (this.data.type === Phaser.Physics.P2.Body.DYNAMIC);
},
set: function (value)
{
if (value && this.data.type !== Phaser.Physics.P2.Body.DYNAMIC)
{
this.data.type = Phaser.Physics.P2.Body.DYNAMIC;
this.mass = 1;
}
else if (!value && this.data.type === Phaser.Physics.P2.Body.DYNAMIC)
{
this.data.type = Phaser.Physics.P2.Body.STATIC;
this.mass = 0;
}
}
});
/**
* @name Phaser.Physics.P2.Body#kinematic
* @property {boolean} kinematic - Returns true if the Body is kinematic. Setting Body.kinematic to 'false' will make it static.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'kinematic', {
get: function ()
{
return (this.data.type === Phaser.Physics.P2.Body.KINEMATIC);
},
set: function (value)
{
if (value && this.data.type !== Phaser.Physics.P2.Body.KINEMATIC)
{
this.data.type = Phaser.Physics.P2.Body.KINEMATIC;
this.mass = 4;
}
else if (!value && this.data.type === Phaser.Physics.P2.Body.KINEMATIC)
{
this.data.type = Phaser.Physics.P2.Body.STATIC;
this.mass = 0;
}
}
});
/**
* @name Phaser.Physics.P2.Body#allowSleep
* @property {boolean} allowSleep -
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'allowSleep', {
get: function ()
{
return this.data.allowSleep;
},
set: function (value)
{
if (value !== this.data.allowSleep)
{
this.data.allowSleep = value;
}
}
});
/**
* The angle of the Body in degrees from its original orientation. Values from 0 to 180 represent clockwise rotation; values from 0 to -180 represent counterclockwise rotation.
* Values outside this range are added to or subtracted from 360 to obtain a value within the range. For example, the statement Body.angle = 450 is the same as Body.angle = 90.
* If you wish to work in radians instead of degrees use the property Body.rotation instead. Working in radians is faster as it doesn't have to convert values.
*
* @name Phaser.Physics.P2.Body#angle
* @property {number} angle - The angle of this Body in degrees.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'angle', {
get: function ()
{
return Phaser.Math.wrapAngle(Phaser.Math.radToDeg(this.data.angle));
},
set: function (value)
{
this.data.angle = Phaser.Math.degToRad(Phaser.Math.wrapAngle(value));
}
});
/**
* Damping is specified as a value between 0 and 1, which is the proportion of velocity lost per second.
* @name Phaser.Physics.P2.Body#angularDamping
* @property {number} angularDamping - The angular damping acting acting on the body.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'angularDamping', {
get: function ()
{
return this.data.angularDamping;
},
set: function (value)
{
this.data.angularDamping = value;
}
});
/**
* @name Phaser.Physics.P2.Body#angularForce
* @property {number} angularForce - The angular force acting on the body.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'angularForce', {
get: function ()
{
return this.data.angularForce;
},
set: function (value)
{
this.data.angularForce = value;
}
});
/**
* @name Phaser.Physics.P2.Body#angularVelocity
* @property {number} angularVelocity - The angular velocity of the body.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'angularVelocity', {
get: function ()
{
return this.data.angularVelocity;
},
set: function (value)
{
this.data.angularVelocity = value;
}
});
/**
* Damping is specified as a value between 0 and 1, which is the proportion of velocity lost per second.
* @name Phaser.Physics.P2.Body#damping
* @property {number} damping - The linear damping acting on the body in the velocity direction.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'damping', {
get: function ()
{
return this.data.damping;
},
set: function (value)
{
this.data.damping = value;
}
});
/**
* @name Phaser.Physics.P2.Body#fixedRotation
* @property {boolean} fixedRotation -
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'fixedRotation', {
get: function ()
{
return this.data.fixedRotation;
},
set: function (value)
{
if (value !== this.data.fixedRotation)
{
this.data.fixedRotation = value;
}
}
});
/**
* @name Phaser.Physics.P2.Body#inertia
* @property {number} inertia - The inertia of the body around the Z axis..
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'inertia', {
get: function ()
{
return this.data.inertia;
},
set: function (value)
{
this.data.inertia = value;
}
});
/**
* @name Phaser.Physics.P2.Body#mass
* @property {number} mass - The mass of the body.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'mass', {
get: function ()
{
return this.data.mass;
},
set: function (value)
{
if (value !== this.data.mass)
{
this.data.mass = value;
this.data.updateMassProperties();
}
}
});
/**
* @name Phaser.Physics.P2.Body#motionState
* @property {number} motionState - The type of motion this body has. Should be one of: Body.STATIC (the body does not move), Body.DYNAMIC (body can move and respond to collisions) and Body.KINEMATIC (only moves according to its .velocity).
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'motionState', {
get: function ()
{
return this.data.type;
},
set: function (value)
{
if (value !== this.data.type)
{
this.data.type = value;
}
}
});
/**
* The angle of the Body in radians.
* If you wish to work in degrees instead of radians use the Body.angle property instead. Working in radians is faster as it doesn't have to convert values.
*
* @name Phaser.Physics.P2.Body#rotation
* @property {number} rotation - The angle of this Body in radians.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'rotation', {
get: function ()
{
return this.data.angle;
},
set: function (value)
{
this.data.angle = value;
}
});
/**
* @name Phaser.Physics.P2.Body#sleepSpeedLimit
* @property {number} sleepSpeedLimit - .
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'sleepSpeedLimit', {
get: function ()
{
return this.data.sleepSpeedLimit;
},
set: function (value)
{
this.data.sleepSpeedLimit = value;
}
});
/**
* @name Phaser.Physics.P2.Body#x
* @property {number} x - The x coordinate of this Body.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'x', {
get: function ()
{
return this.world.mpxi(this.data.position[0]);
},
set: function (value)
{
this.data.position[0] = this.world.pxmi(value);
}
});
/**
* @name Phaser.Physics.P2.Body#y
* @property {number} y - The y coordinate of this Body.
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'y', {
get: function ()
{
return this.world.mpxi(this.data.position[1]);
},
set: function (value)
{
this.data.position[1] = this.world.pxmi(value);
}
});
/**
* @name Phaser.Physics.P2.Body#id
* @property {number} id - The Body ID. Each Body that has been added to the World has a unique ID.
* @readonly
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'id', {
get: function ()
{
return this.data.id;
}
});
/**
* @name Phaser.Physics.P2.Body#debug
* @property {boolean} debug - Enable or disable debug drawing of this body
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'debug', {
get: function ()
{
return (this.debugBody !== null);
},
set: function (value)
{
if (value && !this.debugBody)
{
// This will be added to the global space
this.debugBody = new Phaser.Physics.P2.BodyDebug(this.game, this.data);
}
else if (!value && this.debugBody)
{
this.debugBody.destroy();
this.debugBody = null;
}
}
});
/**
* A Body can be set to collide against the World bounds automatically if this is set to true. Otherwise it will leave the World.
* Note that this only applies if your World has bounds! The response to the collision should be managed via CollisionMaterials.
* Also note that when you set this it will only affect Body shapes that already exist. If you then add further shapes to your Body
* after setting this it will *not* proactively set them to collide with the bounds.
*
* @name Phaser.Physics.P2.Body#collideWorldBounds
* @property {boolean} collideWorldBounds - Should the Body collide with the World bounds?
* @default true
*/
Object.defineProperty(Phaser.Physics.P2.Body.prototype, 'collideWorldBounds', {
get: function ()
{
return this._collideWorldBounds;
},
set: function (value)
{
if (value && !this._collideWorldBounds)
{
this._collideWorldBounds = true;
this.updateCollisionMask();
}
else if (!value && this._collideWorldBounds)
{
this._collideWorldBounds = false;
this.updateCollisionMask();
}
}
});
/**
* @author George https://github.com/georgiee
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Draws a P2 Body to a Graphics instance for visual debugging.
* Needless to say, for every body you enable debug drawing on, you are adding processor and graphical overhead.
* So use sparingly and rarely (if ever) in production code.
*
* Also be aware that the Debug body is only updated when the Sprite it is connected to changes position. If you
* manipulate the sprite in any other way (such as moving it to another Group or bringToTop, etc) then you will
* need to manually adjust its BodyDebug as well.
*
* @class Phaser.Physics.P2.BodyDebug
* @constructor
* @extends Phaser.Group
* @param {Phaser.Game} game - Game reference to the currently running game.
* @param {Phaser.Physics.P2.Body} body - The P2 Body to display debug data for.
* @param {object} settings - Settings object.
*/
Phaser.Physics.P2.BodyDebug = function (game, body, settings)
{
Phaser.Group.call(this, game);
/**
* @property {object} defaultSettings - Default debug settings.
* @private
*/
var defaultSettings = {
pixelsPerLengthUnit: game.physics.p2.mpx(1),
debugPolygons: false,
lineWidth: 1,
alpha: 0.5
};
this.settings = Object.assign(defaultSettings, settings);
/**
* @property {number} ppu - Pixels per Length Unit.
*/
this.ppu = this.settings.pixelsPerLengthUnit;
this.ppu = -1 * this.ppu;
/**
* @property {Phaser.Physics.P2.Body} body - The P2 Body to display debug data for.
*/
this.body = body;
/**
* @property {Phaser.Graphics} canvas - The canvas to render the debug info to.
*/
this.canvas = new Phaser.Graphics(game);
this.canvas.alpha = this.settings.alpha;
this.add(this.canvas);
this.draw();
this.updateSpriteTransform();
};
Phaser.Physics.P2.BodyDebug.prototype = Object.create(Phaser.Group.prototype);
Phaser.Physics.P2.BodyDebug.prototype.constructor = Phaser.Physics.P2.BodyDebug;
Object.assign(Phaser.Physics.P2.BodyDebug.prototype, {
/**
* Core update.
*
* @method Phaser.Physics.P2.BodyDebug#updateSpriteTransform
*/
updateSpriteTransform: function ()
{
this.position.x = this.body.position[0] * this.ppu;
this.position.y = this.body.position[1] * this.ppu;
this.rotation = this.body.angle;
},
/**
* Draws the P2 shapes to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#draw
*/
draw: function ()
{
var angle, child, color, i, j, lineColor, lw, obj, offset, sprite, v, verts, vrot, _j, _ref1;
obj = this.body;
sprite = this.canvas;
sprite.clear();
color = parseInt(this.randomPastelHex(), 16);
lineColor = 0xff0000;
lw = this.lineWidth;
if (obj instanceof p2.Body && obj.shapes.length)
{
var l = obj.shapes.length;
i = 0;
while (i !== l)
{
child = obj.shapes[i];
offset = child.position || 0;
angle = child.angle || 0;
if (child instanceof p2.Circle)
{
this.drawCircle(sprite, offset[0] * this.ppu, offset[1] * this.ppu, angle, child.radius * this.ppu, color, lw);
}
else if (child instanceof p2.Capsule)
{
this.drawCapsule(sprite, offset[0] * this.ppu, offset[1] * this.ppu, angle, child.length * this.ppu, child.radius * this.ppu, lineColor, color, lw);
}
else if (child instanceof p2.Plane)
{
this.drawPlane(sprite, offset[0] * this.ppu, -offset[1] * this.ppu, color, lineColor, lw * 5, lw * 10, lw * 10, this.ppu * 100, angle);
}
else if (child instanceof p2.Line)
{
this.drawLine(sprite, child.length * this.ppu, lineColor, lw);
}
else if (child instanceof p2.Box)
{
this.drawRectangle(sprite, offset[0] * this.ppu, offset[1] * this.ppu, angle, child.width * this.ppu, child.height * this.ppu, lineColor, color, lw);
}
else if (child instanceof p2.Convex)
{
verts = [];
vrot = p2.vec2.create();
for (j = _j = 0, _ref1 = child.vertices.length; _ref1 >= 0 ? _j < _ref1 : _j > _ref1; j = _ref1 >= 0 ? ++_j : --_j)
{
v = child.vertices[j];
p2.vec2.rotate(vrot, v, angle);
verts.push([ (vrot[0] + offset[0]) * this.ppu, -(vrot[1] + offset[1]) * this.ppu ]);
}
this.drawConvex(sprite, verts, child.triangles, lineColor, color, lw, this.settings.debugPolygons, [ offset[0] * this.ppu, -offset[1] * this.ppu ]);
}
i++;
}
}
},
/**
* Draws a p2.Box to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#drawRectangle
* @private
*/
drawRectangle: function (g, x, y, angle, w, h, color, fillColor, lineWidth)
{
if (lineWidth === undefined) { lineWidth = 1; }
if (color === undefined) { color = 0x000000; }
g.lineStyle(lineWidth, color, 1);
g.beginFill(fillColor);
g.drawRect(x - w / 2, y - h / 2, w, h);
},
/**
* Draws a p2.Circle to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#drawCircle
* @private
*/
drawCircle: function (g, x, y, angle, radius, color, lineWidth)
{
if (lineWidth === undefined) { lineWidth = 1; }
if (color === undefined) { color = 0xffffff; }
g.lineStyle(lineWidth, 0x000000, 1);
g.beginFill(color, 1.0);
g.drawCircle(x, y, -radius * 2);
g.endFill();
g.moveTo(x, y);
g.lineTo(x + radius * Math.cos(-angle), y + radius * Math.sin(-angle));
},
/**
* Draws a p2.Line to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#drawLine
* @private
*/
drawLine: function (g, len, color, lineWidth)
{
if (lineWidth === undefined) { lineWidth = 1; }
if (color === undefined) { color = 0x000000; }
g.lineStyle(lineWidth * 5, color, 1);
g.moveTo(-len / 2, 0);
g.lineTo(len / 2, 0);
},
/**
* Draws a p2.Convex to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#drawConvex
* @private
*/
drawConvex: function (g, verts, triangles, color, fillColor, lineWidth, debug, offset)
{
var colors, i, v, v0, v1, x, x0, x1, y, y0, y1;
if (lineWidth === undefined) { lineWidth = 1; }
if (color === undefined) { color = 0x000000; }
if (!debug)
{
g.lineStyle(lineWidth, color, 1);
g.beginFill(fillColor);
i = 0;
while (i !== verts.length)
{
v = verts[i];
x = v[0];
y = v[1];
if (i === 0)
{
g.moveTo(x, -y);
}
else
{
g.lineTo(x, -y);
}
i++;
}
g.endFill();
if (verts.length > 2)
{
g.moveTo(verts[verts.length - 1][0], -verts[verts.length - 1][1]);
return g.lineTo(verts[0][0], -verts[0][1]);
}
}
else
{
colors = [ 0xff0000, 0x00ff00, 0x0000ff ];
i = 0;
while (i !== verts.length + 1)
{
v0 = verts[i % verts.length];
v1 = verts[(i + 1) % verts.length];
x0 = v0[0];
y0 = v0[1];
x1 = v1[0];
y1 = v1[1];
g.lineStyle(lineWidth, colors[i % colors.length], 1);
g.moveTo(x0, -y0);
g.lineTo(x1, -y1);
g.drawCircle(x0, -y0, lineWidth * 2);
i++;
}
g.lineStyle(lineWidth, 0x000000, 1);
return g.drawCircle(offset[0], offset[1], lineWidth * 2);
}
},
/**
* Draws a p2.Path to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#drawPath
* @private
*/
drawPath: function (g, path, color, fillColor, lineWidth)
{
var area, i, lastx, lasty, p1x, p1y, p2x, p2y, p3x, p3y, v, x, y;
if (lineWidth === undefined) { lineWidth = 1; }
if (color === undefined) { color = 0x000000; }
g.lineStyle(lineWidth, color, 1);
if (typeof fillColor === 'number')
{
g.beginFill(fillColor);
}
lastx = null;
lasty = null;
i = 0;
while (i < path.length)
{
v = path[i];
x = v[0];
y = v[1];
if (x !== lastx || y !== lasty)
{
if (i === 0)
{
g.moveTo(x, y);
}
else
{
p1x = lastx;
p1y = lasty;
p2x = x;
p2y = y;
p3x = path[(i + 1) % path.length][0];
p3y = path[(i + 1) % path.length][1];
area = ((p2x - p1x) * (p3y - p1y)) - ((p3x - p1x) * (p2y - p1y));
if (area !== 0)
{
g.lineTo(x, y);
}
}
lastx = x;
lasty = y;
}
i++;
}
if (typeof fillColor === 'number')
{
g.endFill();
}
if (path.length > 2 && typeof fillColor === 'number')
{
g.moveTo(path[path.length - 1][0], path[path.length - 1][1]);
g.lineTo(path[0][0], path[0][1]);
}
},
/**
* Draws a p2.Plane to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#drawPlane
* @private
*/
drawPlane: function (g, x0, x1, color, lineColor, lineWidth, diagMargin, diagSize, maxLength, angle)
{
var max, xd, yd;
if (lineWidth === undefined) { lineWidth = 1; }
if (color === undefined) { color = 0xffffff; }
g.lineStyle(lineWidth, lineColor, 11);
g.beginFill(color);
max = maxLength;
g.moveTo(x0, -x1);
xd = x0 + Math.cos(angle) * this.game.width;
yd = x1 + Math.sin(angle) * this.game.height;
g.lineTo(xd, -yd);
g.moveTo(x0, -x1);
xd = x0 + Math.cos(angle) * -this.game.width;
yd = x1 + Math.sin(angle) * -this.game.height;
g.lineTo(xd, -yd);
},
/**
* Draws a p2.Capsule to the Graphics object.
*
* @method Phaser.Physics.P2.BodyDebug#drawCapsule
* @private
*/
drawCapsule: function (g, x, y, angle, len, radius, color, fillColor, lineWidth)
{
if (lineWidth === undefined) { lineWidth = 1; }
if (color === undefined) { color = 0x000000; }
g.lineStyle(lineWidth, color, 1);
// Draw circles at ends
var c = Math.cos(angle);
var s = Math.sin(angle);
g.beginFill(fillColor, 1);
g.drawCircle(-len / 2 * c + x, -len / 2 * s + y, -radius * 2);
g.drawCircle(len / 2 * c + x, len / 2 * s + y, -radius * 2);
g.endFill();
// Draw rectangle
g.lineStyle(lineWidth, color, 0);
g.beginFill(fillColor, 1);
g.moveTo(-len / 2 * c + radius * s + x, -len / 2 * s + radius * c + y);
g.lineTo(len / 2 * c + radius * s + x, len / 2 * s + radius * c + y);
g.lineTo(len / 2 * c - radius * s + x, len / 2 * s - radius * c + y);
g.lineTo(-len / 2 * c - radius * s + x, -len / 2 * s - radius * c + y);
g.endFill();
// Draw lines in between
g.lineStyle(lineWidth, color, 1);
g.moveTo(-len / 2 * c + radius * s + x, -len / 2 * s + radius * c + y);
g.lineTo(len / 2 * c + radius * s + x, len / 2 * s + radius * c + y);
g.moveTo(-len / 2 * c - radius * s + x, -len / 2 * s - radius * c + y);
g.lineTo(len / 2 * c - radius * s + x, len / 2 * s - radius * c + y);
},
/**
* Picks a random pastel color.
*
* @method Phaser.Physics.P2.BodyDebug#randomPastelHex
* @private
*/
randomPastelHex: function ()
{
var blue, green, mix, red;
mix = [ 255, 255, 255 ];
red = Math.floor(Math.random() * 256);
green = Math.floor(Math.random() * 256);
blue = Math.floor(Math.random() * 256);
red = Math.floor((red + 3 * mix[0]) / 4);
green = Math.floor((green + 3 * mix[1]) / 4);
blue = Math.floor((blue + 3 * mix[2]) / 4);
return this.rgbToHex(red, green, blue);
},
/**
* Converts from RGB to Hex.
*
* @method Phaser.Physics.P2.BodyDebug#rgbToHex
* @private
*/
rgbToHex: function (r, g, b)
{
return this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b);
},
/**
* Component to hex conversion.
*
* @method Phaser.Physics.P2.BodyDebug#componentToHex
* @private
*/
componentToHex: function (c)
{
var hex;
hex = c.toString(16);
if (hex.length === 2)
{
return hex;
}
else
{
return hex + '0';
}
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a linear spring, connecting two bodies. A spring can have a resting length, a stiffness and damping.
*
* @class Phaser.Physics.P2.Spring
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {p2.Body} bodyA - First connected body.
* @param {p2.Body} bodyB - Second connected body.
* @param {number} [restLength=1] - Rest length of the spring. A number > 0.
* @param {number} [stiffness=100] - Stiffness of the spring. A number >= 0.
* @param {number} [damping=1] - Damping of the spring. A number >= 0.
* @param {Array} [worldA] - Where to hook the spring to body A in world coordinates. This value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Array} [worldB] - Where to hook the spring to body B in world coordinates. This value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Array} [localA] - Where to hook the spring to body A in local body coordinates. This value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Array} [localB] - Where to hook the spring to body B in local body coordinates. This value is an array with 2 elements matching x and y, i.e: [32, 32].
*/
Phaser.Physics.P2.Spring = function (world, bodyA, bodyB, restLength, stiffness, damping, worldA, worldB, localA, localB)
{
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = world.game;
/**
* @property {Phaser.Physics.P2} world - Local reference to P2 World.
*/
this.world = world;
if (restLength === undefined) { restLength = 1; }
if (stiffness === undefined) { stiffness = 100; }
if (damping === undefined) { damping = 1; }
restLength = world.pxm(restLength);
var options = {
restLength: restLength,
stiffness: stiffness,
damping: damping
};
if (typeof worldA !== 'undefined' && worldA !== null)
{
options.worldAnchorA = [ world.pxm(worldA[0]), world.pxm(worldA[1]) ];
}
if (typeof worldB !== 'undefined' && worldB !== null)
{
options.worldAnchorB = [ world.pxm(worldB[0]), world.pxm(worldB[1]) ];
}
if (typeof localA !== 'undefined' && localA !== null)
{
options.localAnchorA = [ world.pxm(localA[0]), world.pxm(localA[1]) ];
}
if (typeof localB !== 'undefined' && localB !== null)
{
options.localAnchorB = [ world.pxm(localB[0]), world.pxm(localB[1]) ];
}
/**
* @property {p2.LinearSpring} data - The actual p2 spring object.
*/
this.data = new p2.LinearSpring(bodyA, bodyB, options);
this.data.parent = this;
};
Phaser.Physics.P2.Spring.prototype.constructor = Phaser.Physics.P2.Spring;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a rotational spring, connecting two bodies. A spring can have a resting length, a stiffness and damping.
*
* @class Phaser.Physics.P2.RotationalSpring
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {p2.Body} bodyA - First connected body.
* @param {p2.Body} bodyB - Second connected body.
* @param {number} [restAngle] - The relative angle of bodies at which the spring is at rest. If not given, it's set to the current relative angle between the bodies.
* @param {number} [stiffness=100] - Stiffness of the spring. A number >= 0.
* @param {number} [damping=1] - Damping of the spring. A number >= 0.
*/
Phaser.Physics.P2.RotationalSpring = function (world, bodyA, bodyB, restAngle, stiffness, damping)
{
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = world.game;
/**
* @property {Phaser.Physics.P2} world - Local reference to P2 World.
*/
this.world = world;
if (restAngle === undefined) { restAngle = null; }
if (stiffness === undefined) { stiffness = 100; }
if (damping === undefined) { damping = 1; }
if (restAngle)
{
restAngle = world.pxm(restAngle);
}
var options = {
restAngle: restAngle,
stiffness: stiffness,
damping: damping
};
/**
* @property {p2.RotationalSpring} data - The actual p2 spring object.
*/
this.data = new p2.RotationalSpring(bodyA, bodyB, options);
this.data.parent = this;
};
Phaser.Physics.P2.Spring.prototype.constructor = Phaser.Physics.P2.Spring;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A P2 Material.
*
* \o/ ~ "Because I'm a Material girl"
*
* @class Phaser.Physics.P2.Material
* @constructor
* @param {string} name - The user defined name given to this Material.
*/
Phaser.Physics.P2.Material = function (name)
{
/**
* @property {string} name - The user defined name given to this Material.
* @default
*/
this.name = name;
p2.Material.call(this);
};
Phaser.Physics.P2.Material.prototype = Object.create(p2.Material.prototype);
Phaser.Physics.P2.Material.prototype.constructor = Phaser.Physics.P2.Material;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Defines a physics material
*
* @class Phaser.Physics.P2.ContactMaterial
* @constructor
* @param {Phaser.Physics.P2.Material} materialA - First material participating in the contact material.
* @param {Phaser.Physics.P2.Material} materialB - Second material participating in the contact material.
* @param {object} [options] - Additional configuration options.
*/
Phaser.Physics.P2.ContactMaterial = function (materialA, materialB, options)
{
/**
* @property {number} id - The contact material identifier.
*/
/**
* @property {Phaser.Physics.P2.Material} materialA - First material participating in the contact material.
*/
/**
* @property {Phaser.Physics.P2.Material} materialB - Second material participating in the contact material.
*/
/**
* @property {number} [friction=0.3] - Friction to use in the contact of these two materials.
*/
/**
* @property {number} [restitution=0.0] - Restitution to use in the contact of these two materials.
*/
/**
* @property {number} [stiffness=1e7] - Stiffness of the resulting ContactEquation that this ContactMaterial generates.
*/
/**
* @property {number} [relaxation=3] - Relaxation of the resulting ContactEquation that this ContactMaterial generates.
*/
/**
* @property {number} [frictionStiffness=1e7] - Stiffness of the resulting FrictionEquation that this ContactMaterial generates.
*/
/**
* @property {number} [frictionRelaxation=3] - Relaxation of the resulting FrictionEquation that this ContactMaterial generates.
*/
/**
* @property {number} [surfaceVelocity=0] - Will add surface velocity to this material. If bodyA rests on top if bodyB, and the surface velocity is positive, bodyA will slide to the right.
*/
p2.ContactMaterial.call(this, materialA, materialB, options);
};
Phaser.Physics.P2.ContactMaterial.prototype = Object.create(p2.ContactMaterial.prototype);
Phaser.Physics.P2.ContactMaterial.prototype.constructor = Phaser.Physics.P2.ContactMaterial;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Collision Group
*
* @class Phaser.Physics.P2.CollisionGroup
* @constructor
* @param {number} bitmask - The CollisionGroup bitmask.
*/
Phaser.Physics.P2.CollisionGroup = function (bitmask)
{
/**
* @property {number} mask - The CollisionGroup bitmask.
*/
this.mask = bitmask;
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A constraint that tries to keep the distance between two bodies constant.
*
* @class Phaser.Physics.P2.DistanceConstraint
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {p2.Body} bodyA - First connected body.
* @param {p2.Body} bodyB - Second connected body.
* @param {number} distance - The distance to keep between the bodies.
* @param {Array} [localAnchorA] - The anchor point for bodyA, defined locally in bodyA frame. Defaults to [0,0].
* @param {Array} [localAnchorB] - The anchor point for bodyB, defined locally in bodyB frame. Defaults to [0,0].
* @param {object} [maxForce=Number.MAX_VALUE] - Maximum force to apply.
*/
Phaser.Physics.P2.DistanceConstraint = function (world, bodyA, bodyB, distance, localAnchorA, localAnchorB, maxForce)
{
if (distance === undefined) { distance = 100; }
if (localAnchorA === undefined) { localAnchorA = [ 0, 0 ]; }
if (localAnchorB === undefined) { localAnchorB = [ 0, 0 ]; }
if (maxForce === undefined) { maxForce = Number.MAX_VALUE; }
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = world.game;
/**
* @property {Phaser.Physics.P2} world - Local reference to P2 World.
*/
this.world = world;
distance = world.pxm(distance);
localAnchorA = [ world.pxmi(localAnchorA[0]), world.pxmi(localAnchorA[1]) ];
localAnchorB = [ world.pxmi(localAnchorB[0]), world.pxmi(localAnchorB[1]) ];
var options = { distance: distance, localAnchorA: localAnchorA, localAnchorB: localAnchorB, maxForce: maxForce };
p2.DistanceConstraint.call(this, bodyA, bodyB, options);
};
Phaser.Physics.P2.DistanceConstraint.prototype = Object.create(p2.DistanceConstraint.prototype);
Phaser.Physics.P2.DistanceConstraint.prototype.constructor = Phaser.Physics.P2.DistanceConstraint;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Connects two bodies at given offset points, letting them rotate relative to each other around this point.
*
* @class Phaser.Physics.P2.GearConstraint
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {p2.Body} bodyA - First connected body.
* @param {p2.Body} bodyB - Second connected body.
* @param {number} [angle=0] - The relative angle
* @param {number} [ratio=1] - The gear ratio.
*/
Phaser.Physics.P2.GearConstraint = function (world, bodyA, bodyB, angle, ratio)
{
if (angle === undefined) { angle = 0; }
if (ratio === undefined) { ratio = 1; }
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = world.game;
/**
* @property {Phaser.Physics.P2} world - Local reference to P2 World.
*/
this.world = world;
var options = { angle: angle, ratio: ratio };
p2.GearConstraint.call(this, bodyA, bodyB, options);
};
Phaser.Physics.P2.GearConstraint.prototype = Object.create(p2.GearConstraint.prototype);
Phaser.Physics.P2.GearConstraint.prototype.constructor = Phaser.Physics.P2.GearConstraint;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Locks the relative position between two bodies.
*
* @class Phaser.Physics.P2.LockConstraint
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {p2.Body} bodyA - First connected body.
* @param {p2.Body} bodyB - Second connected body.
* @param {Array} [offset] - The offset of bodyB in bodyA's frame. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {number} [angle=0] - The angle of bodyB in bodyA's frame.
* @param {number} [maxForce] - The maximum force that should be applied to constrain the bodies.
*/
Phaser.Physics.P2.LockConstraint = function (world, bodyA, bodyB, offset, angle, maxForce)
{
if (offset === undefined) { offset = [ 0, 0 ]; }
if (angle === undefined) { angle = 0; }
if (maxForce === undefined) { maxForce = Number.MAX_VALUE; }
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = world.game;
/**
* @property {Phaser.Physics.P2} world - Local reference to P2 World.
*/
this.world = world;
offset = [ world.pxm(offset[0]), world.pxm(offset[1]) ];
var options = { localOffsetB: offset, localAngleB: angle, maxForce: maxForce };
p2.LockConstraint.call(this, bodyA, bodyB, options);
};
Phaser.Physics.P2.LockConstraint.prototype = Object.create(p2.LockConstraint.prototype);
Phaser.Physics.P2.LockConstraint.prototype.constructor = Phaser.Physics.P2.LockConstraint;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Connects two bodies at given offset points, letting them rotate relative to each other around this point.
*
* @class Phaser.Physics.P2.PrismaticConstraint
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {p2.Body} bodyA - First connected body.
* @param {p2.Body} bodyB - Second connected body.
* @param {boolean} [lockRotation=true] - If set to false, bodyB will be free to rotate around its anchor point.
* @param {Array} [anchorA] - Body A's anchor point, defined in its own local frame. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Array} [anchorB] - Body A's anchor point, defined in its own local frame. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {Array} [axis] - An axis, defined in body A frame, that body B's anchor point may slide along. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {number} [maxForce] - The maximum force that should be applied to constrain the bodies.
*/
Phaser.Physics.P2.PrismaticConstraint = function (world, bodyA, bodyB, lockRotation, anchorA, anchorB, axis, maxForce)
{
if (lockRotation === undefined) { lockRotation = true; }
if (anchorA === undefined) { anchorA = [ 0, 0 ]; }
if (anchorB === undefined) { anchorB = [ 0, 0 ]; }
if (axis === undefined) { axis = [ 0, 0 ]; }
if (maxForce === undefined) { maxForce = Number.MAX_VALUE; }
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = world.game;
/**
* @property {Phaser.Physics.P2} world - Local reference to P2 World.
*/
this.world = world;
anchorA = [ world.pxmi(anchorA[0]), world.pxmi(anchorA[1]) ];
anchorB = [ world.pxmi(anchorB[0]), world.pxmi(anchorB[1]) ];
var options = { localAnchorA: anchorA, localAnchorB: anchorB, localAxisA: axis, maxForce: maxForce, disableRotationalLock: !lockRotation };
p2.PrismaticConstraint.call(this, bodyA, bodyB, options);
};
Phaser.Physics.P2.PrismaticConstraint.prototype = Object.create(p2.PrismaticConstraint.prototype);
Phaser.Physics.P2.PrismaticConstraint.prototype.constructor = Phaser.Physics.P2.PrismaticConstraint;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Connects two bodies at given offset points, letting them rotate relative to each other around this point.
* The pivot points are given in world (pixel) coordinates.
*
* @class Phaser.Physics.P2.RevoluteConstraint
* @constructor
* @param {Phaser.Physics.P2} world - A reference to the P2 World.
* @param {p2.Body} bodyA - First connected body.
* @param {Float32Array} pivotA - The point relative to the center of mass of bodyA which bodyA is constrained to. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {p2.Body} bodyB - Second connected body.
* @param {Float32Array} pivotB - The point relative to the center of mass of bodyB which bodyB is constrained to. The value is an array with 2 elements matching x and y, i.e: [32, 32].
* @param {number} [maxForce=0] - The maximum force that should be applied to constrain the bodies.
* @param {Float32Array} [worldPivot=null] - A pivot point given in world coordinates. If specified, localPivotA and localPivotB are automatically computed from this value.
*/
Phaser.Physics.P2.RevoluteConstraint = function (world, bodyA, pivotA, bodyB, pivotB, maxForce, worldPivot)
{
if (maxForce === undefined) { maxForce = Number.MAX_VALUE; }
if (worldPivot === undefined) { worldPivot = null; }
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = world.game;
/**
* @property {Phaser.Physics.P2} world - Local reference to P2 World.
*/
this.world = world;
pivotA = [ world.pxmi(pivotA[0]), world.pxmi(pivotA[1]) ];
pivotB = [ world.pxmi(pivotB[0]), world.pxmi(pivotB[1]) ];
if (worldPivot)
{
worldPivot = [ world.pxmi(worldPivot[0]), world.pxmi(worldPivot[1]) ];
}
var options = { worldPivot: worldPivot, localPivotA: pivotA, localPivotB: pivotB, maxForce: maxForce };
p2.RevoluteConstraint.call(this, bodyA, bodyB, options);
};
Phaser.Physics.P2.RevoluteConstraint.prototype = Object.create(p2.RevoluteConstraint.prototype);
Phaser.Physics.P2.RevoluteConstraint.prototype.constructor = Phaser.Physics.P2.RevoluteConstraint;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* An Image Collection is a special tileset containing mulitple images, with no slicing into each image.
*
* Image Collections are normally created automatically when Tiled data is loaded.
*
* @class Phaser.ImageCollection
* @constructor
* @param {string} name - The name of the image collection in the map data.
* @param {integer} firstgid - The first image index this image collection contains.
* @param {integer} [width=32] - Width of widest image (in pixels).
* @param {integer} [height=32] - Height of tallest image (in pixels).
* @param {integer} [margin=0] - The margin around all images in the collection (in pixels).
* @param {integer} [spacing=0] - The spacing between each image in the collection (in pixels).
* @param {object} [properties={}] - Custom Image Collection properties.
*/
Phaser.ImageCollection = function (name, firstgid, width, height, margin, spacing, properties)
{
if (width === undefined || width <= 0) { width = 32; }
if (height === undefined || height <= 0) { height = 32; }
if (margin === undefined) { margin = 0; }
if (spacing === undefined) { spacing = 0; }
/**
* The name of the Image Collection.
* @property {string} name
*/
this.name = name;
/**
* The Tiled firstgid value.
* This is the starting index of the first image index this Image Collection contains.
* @property {integer} firstgid
*/
this.firstgid = firstgid | 0;
/**
* The width of the widest image (in pixels).
* @property {integer} imageWidth
* @readonly
*/
this.imageWidth = width | 0;
/**
* The height of the tallest image (in pixels).
* @property {integer} imageHeight
* @readonly
*/
this.imageHeight = height | 0;
/**
* The margin around the images in the collection (in pixels).
* Use `setSpacing` to change.
* @property {integer} imageMarge
* @readonly
*/
// Modified internally
this.imageMargin = margin | 0;
/**
* The spacing between each image in the collection (in pixels).
* Use `setSpacing` to change.
* @property {integer} imageSpacing
* @readonly
*/
this.imageSpacing = spacing | 0;
/**
* Image Collection-specific properties that are typically defined in the Tiled editor.
* @property {object} properties
*/
this.properties = properties || {};
/**
* The cached images that are a part of this collection.
* @property {array} images
* @readonly
*/
// Modified internally
this.images = [];
/**
* The total number of images in the image collection.
* @property {integer} total
* @readonly
*/
// Modified internally
this.total = 0;
};
Phaser.ImageCollection.prototype = {
/**
* Returns true if and only if this image collection contains the given image index.
*
* @method Phaser.ImageCollection#containsImageIndex
* @param {integer} imageIndex - The image index to search for.
* @return {boolean} True if this Image Collection contains the given index.
*/
containsImageIndex: function (imageIndex)
{
return (
imageIndex >= this.firstgid &&
imageIndex < (this.firstgid + this.total)
);
},
/**
* Add an image to this Image Collection.
*
* @method Phaser.ImageCollection#addImage
* @param {integer} gid - The gid of the image in the Image Collection.
* @param {string} image - The the key of the image in the Image Collection and in the cache.
*/
addImage: function (gid, image)
{
this.images.push({ gid: gid, image: image });
this.total++;
}
};
Phaser.ImageCollection.prototype.constructor = Phaser.ImageCollection;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Tile is a representation of a single tile within the Tilemap.
*
* @class Phaser.Tile
* @constructor
* @param {object} layer - The layer in the Tilemap data that this tile belongs to.
* @param {number} index - The index of this tile type in the core map data.
* @param {number} x - The x coordinate of this tile.
* @param {number} y - The y coordinate of this tile.
* @param {number} width - Width of the tile.
* @param {number} height - Height of the tile.
*/
Phaser.Tile = function (layer, index, x, y, width, height)
{
/**
* @property {object} layer - The layer in the Tilemap data that this tile belongs to.
*/
this.layer = layer;
/**
* @property {number} index - The index of this tile within the map data corresponding to the tileset, or -1 if this represents a blank/null tile.
*/
this.index = index;
/**
* @property {number} x - The x map coordinate of this tile.
*/
this.x = x;
/**
* @property {number} y - The y map coordinate of this tile.
*/
this.y = y;
/**
* @property {number} rotation - The rotation angle of this tile.
*/
this.rotation = 0;
/**
* @property {boolean} flipped - Whether this tile is flipped (mirrored) or not.
*/
this.flipped = false;
/**
* @property {number} x - The x map coordinate of this tile.
*/
this.worldX = x * width;
/**
* @property {number} y - The y map coordinate of this tile.
*/
this.worldY = y * height;
/**
* @property {number} width - The width of the tile in pixels.
*/
this.width = width;
/**
* @property {number} height - The height of the tile in pixels.
*/
this.height = height;
/**
* @property {number} width - The width of the tile in pixels.
*/
this.centerX = Math.abs(width / 2);
/**
* @property {number} height - The height of the tile in pixels.
*/
this.centerY = Math.abs(height / 2);
/**
* @property {number} alpha - The alpha value at which this tile is drawn to the canvas.
*/
this.alpha = 1;
/**
* @property {object} properties - Tile specific properties.
*/
this.properties = {};
/**
* @property {boolean} scanned - Has this tile been walked / turned into a poly?
*/
this.scanned = false;
/**
* @property {boolean} faceTop - Is the top of this tile an interesting edge?
*/
this.faceTop = false;
/**
* @property {boolean} faceBottom - Is the bottom of this tile an interesting edge?
*/
this.faceBottom = false;
/**
* @property {boolean} faceLeft - Is the left of this tile an interesting edge?
*/
this.faceLeft = false;
/**
* @property {boolean} faceRight - Is the right of this tile an interesting edge?
*/
this.faceRight = false;
/**
* @property {boolean} collideLeft - Indicating collide with any object on the left.
* @default
*/
this.collideLeft = false;
/**
* @property {boolean} collideRight - Indicating collide with any object on the right.
* @default
*/
this.collideRight = false;
/**
* @property {boolean} collideUp - Indicating collide with any object on the top.
* @default
*/
this.collideUp = false;
/**
* @property {boolean} collideDown - Indicating collide with any object on the bottom.
* @default
*/
this.collideDown = false;
/**
* @property {function} collisionCallback - Tile collision callback.
* @default
*/
this.collisionCallback = null;
/**
* @property {object} collisionCallbackContext - The context in which the collision callback will be called.
* @default
*/
this.collisionCallbackContext = this;
/**
* @property {boolean} debug
* @default
*/
this.debug = false;
};
Phaser.Tile.prototype = {
/**
* Check if the given x and y world coordinates are within this Tile.
*
* @method Phaser.Tile#containsPoint
* @param {number} x - The x coordinate to test.
* @param {number} y - The y coordinate to test.
* @return {boolean} True if the coordinates are within this Tile, otherwise false.
*/
containsPoint: function (x, y)
{
return !(x < this.worldX || y < this.worldY || x > this.right || y > this.bottom);
},
/**
* Check for intersection with this tile.
*
* @method Phaser.Tile#intersects
* @param {number} x - The x axis in pixels.
* @param {number} y - The y axis in pixels.
* @param {number} right - The right point.
* @param {number} bottom - The bottom point.
*/
intersects: function (x, y, right, bottom)
{
if (right <= this.worldX)
{
return false;
}
if (bottom <= this.worldY)
{
return false;
}
if (x >= this.worldX + this.width)
{
return false;
}
if (y >= this.worldY + this.height)
{
return false;
}
return true;
},
/**
* Set a callback to be called when this tile is hit by an object.
* The callback must true true for collision processing to take place.
*
* @method Phaser.Tile#setCollisionCallback
* @param {function} callback - Callback function.
* @param {object} context - Callback will be called within this context.
*/
setCollisionCallback: function (callback, context)
{
this.collisionCallback = callback;
this.collisionCallbackContext = context;
},
/**
* Clean up memory.
*
* @method Phaser.Tile#destroy
*/
destroy: function ()
{
this.collisionCallback = null;
this.collisionCallbackContext = null;
this.properties = null;
},
/**
* Sets the collision flags for each side of this tile and updates the interesting faces list.
*
* @method Phaser.Tile#setCollision
* @param {boolean} left - Indicating collide with any object on the left.
* @param {boolean} right - Indicating collide with any object on the right.
* @param {boolean} up - Indicating collide with any object on the top.
* @param {boolean} down - Indicating collide with any object on the bottom.
*/
setCollision: function (left, right, up, down)
{
this.collideLeft = left;
this.collideRight = right;
this.collideUp = up;
this.collideDown = down;
this.faceLeft = left;
this.faceRight = right;
this.faceTop = up;
this.faceBottom = down;
},
/**
* Reset collision status flags.
*
* @method Phaser.Tile#resetCollision
*/
resetCollision: function ()
{
this.collideLeft = false;
this.collideRight = false;
this.collideUp = false;
this.collideDown = false;
this.faceTop = false;
this.faceBottom = false;
this.faceLeft = false;
this.faceRight = false;
},
/**
* Is this tile interesting?
*
* @method Phaser.Tile#isInteresting
* @param {boolean} collides - If true will check any collides value.
* @param {boolean} faces - If true will check any face value.
* @return {boolean} True if the Tile is interesting, otherwise false.
*/
isInteresting: function (collides, faces)
{
if (collides && faces)
{
// Does this tile have any collide flags OR interesting face?
return (this.collideLeft || this.collideRight || this.collideUp || this.collideDown || this.faceTop || this.faceBottom || this.faceLeft || this.faceRight || this.collisionCallback);
}
else if (collides)
{
// Does this tile collide?
return (this.collideLeft || this.collideRight || this.collideUp || this.collideDown);
}
else if (faces)
{
// Does this tile have an interesting face?
return (this.faceTop || this.faceBottom || this.faceLeft || this.faceRight);
}
return false;
},
/**
* Copies the tile data and properties from the given tile to this tile.
*
* @method Phaser.Tile#copy
* @param {Phaser.Tile} tile - The tile to copy from.
*/
copy: function (tile)
{
this.index = tile.index;
this.alpha = tile.alpha;
this.properties = tile.properties;
this.collideUp = tile.collideUp;
this.collideDown = tile.collideDown;
this.collideLeft = tile.collideLeft;
this.collideRight = tile.collideRight;
this.collisionCallback = tile.collisionCallback;
this.collisionCallbackContext = tile.collisionCallbackContext;
}
};
Phaser.Tile.prototype.constructor = Phaser.Tile;
/**
* @name Phaser.Tile#collides
* @property {boolean} collides - True if this tile can collide on any of its faces.
* @readonly
*/
Object.defineProperty(Phaser.Tile.prototype, 'collides', {
get: function ()
{
return (this.collideLeft || this.collideRight || this.collideUp || this.collideDown);
}
});
/**
* @name Phaser.Tile#canCollide
* @property {boolean} canCollide - True if this tile can collide on any of its faces or has a collision callback set.
* @readonly
*/
Object.defineProperty(Phaser.Tile.prototype, 'canCollide', {
get: function ()
{
return (this.collideLeft || this.collideRight || this.collideUp || this.collideDown || this.collisionCallback);
}
});
/**
* @name Phaser.Tile#left
* @property {number} left - The x value in pixels.
* @readonly
*/
Object.defineProperty(Phaser.Tile.prototype, 'left', {
get: function ()
{
return this.worldX;
}
});
/**
* @name Phaser.Tile#right
* @property {number} right - The sum of the x and width properties.
* @readonly
*/
Object.defineProperty(Phaser.Tile.prototype, 'right', {
get: function ()
{
return this.worldX + this.width;
}
});
/**
* @name Phaser.Tile#top
* @property {number} top - The y value.
* @readonly
*/
Object.defineProperty(Phaser.Tile.prototype, 'top', {
get: function ()
{
return this.worldY;
}
});
/**
* @name Phaser.Tile#bottom
* @property {number} bottom - The sum of the y and height properties.
* @readonly
*/
Object.defineProperty(Phaser.Tile.prototype, 'bottom', {
get: function ()
{
return this.worldY + this.height;
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a new Phaser.Tilemap object. The map can either be populated with data from a Tiled JSON file or from a CSV file.
*
* Tiled is a free software package specifically for creating tile maps, and is available from http://www.mapeditor.org
*
* To do this pass the Cache key as the first parameter. When using Tiled data you need only provide the key.
* When using CSV data you must provide the key and the tileWidth and tileHeight parameters.
* If creating a blank tilemap to be populated later, you can either specify no parameters at all and then use `Tilemap.create` or pass the map and tile dimensions here.
* Note that all Tilemaps use a base tile size to calculate dimensions from, but that a TilemapLayer may have its own unique tile size that overrides it.
* A Tile map is rendered to the display using a TilemapLayer. It is not added to the display list directly itself.
* A map may have multiple layers. You can perform operations on the map data such as copying, pasting, filling and shuffling the tiles around.
*
* @class Phaser.Tilemap
* @constructor
* @param {Phaser.Game} game - Game reference to the currently running game.
* @param {string} [key] - The key of the tilemap data as stored in the Cache. If you're creating a blank map either leave this parameter out or pass `null`.
* @param {number} [tileWidth=32] - The pixel width of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data.
* @param {number} [tileHeight=32] - The pixel height of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data.
* @param {number} [width=10] - The width of the map in tiles. If this map is created from Tiled or CSV data you don't need to specify this.
* @param {number} [height=10] - The height of the map in tiles. If this map is created from Tiled or CSV data you don't need to specify this.
*/
Phaser.Tilemap = function (game, key, tileWidth, tileHeight, width, height)
{
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
/**
* @property {string} key - The key of this map data in the Phaser.Cache.
*/
this.key = key;
var data = Phaser.TilemapParser.parse(this.game, key, tileWidth, tileHeight, width, height);
if (data === null)
{
return;
}
/**
* @property {number} width - The width of the map (in tiles).
*/
this.width = data.width;
/**
* @property {number} height - The height of the map (in tiles).
*/
this.height = data.height;
/**
* @property {number} tileWidth - The base width of the tiles in the map (in pixels).
*/
this.tileWidth = data.tileWidth;
/**
* @property {number} tileHeight - The base height of the tiles in the map (in pixels).
*/
this.tileHeight = data.tileHeight;
/**
* @property {string} orientation - The orientation of the map data (as specified in Tiled), usually 'orthogonal'.
*/
this.orientation = data.orientation;
/**
* @property {number} format - The format of the map data, either Phaser.Tilemap.CSV or Phaser.Tilemap.TILED_JSON.
*/
this.format = data.format;
/**
* @property {number} version - The version of the map data (as specified in Tiled, usually 1).
*/
this.version = data.version;
/**
* @property {object} properties - Map specific properties as specified in Tiled.
*/
this.properties = data.properties;
/**
* @property {number} widthInPixels - The width of the map in pixels based on width * tileWidth.
*/
this.widthInPixels = data.widthInPixels;
/**
* @property {number} heightInPixels - The height of the map in pixels based on height * tileHeight.
*/
this.heightInPixels = data.heightInPixels;
/**
* @property {array} layers - An array of Tilemap layer data.
*/
this.layers = data.layers;
/**
* @property {array} tilesets - An array of Tilesets.
*/
this.tilesets = data.tilesets;
/**
* @property {array} imagecollections - An array of Image Collections.
*/
this.imagecollections = data.imagecollections;
/**
* @property {array} tiles - The super array of Tiles.
*/
this.tiles = data.tiles;
/**
* @property {array} objects - An array of Tiled Object Layers.
*/
this.objects = data.objects;
/**
* @property {array} collideIndexes - An array of tile indexes that collide.
*/
this.collideIndexes = [];
/**
* @property {array} collision - An array of collision data (polylines, etc).
*/
this.collision = data.collision;
/**
* @property {array} images - An array of Tiled Image Layers.
*/
this.images = data.images;
/**
* @property {boolean} enableDebug - If set then console.log is used to dump out useful layer creation debug data.
*/
this.enableDebug = false;
/**
* @property {number} currentLayer - The current layer.
*/
this.currentLayer = 0;
/**
* @property {array} debugMap - Map data used for debug values only.
*/
this.debugMap = [];
/**
* @property {array} _results - Internal var.
* @private
*/
this._results = [];
/**
* @property {number} _tempA - Internal var.
* @private
*/
this._tempA = 0;
/**
* @property {number} _tempB - Internal var.
* @private
*/
this._tempB = 0;
};
/**
* @constant
* @type {number}
*/
Phaser.Tilemap.CSV = 0;
/**
* @constant
* @type {number}
*/
Phaser.Tilemap.TILED_JSON = 1;
/**
* @constant
* @type {number}
*/
Phaser.Tilemap.NORTH = 0;
/**
* @constant
* @type {number}
*/
Phaser.Tilemap.EAST = 1;
/**
* @constant
* @type {number}
*/
Phaser.Tilemap.SOUTH = 2;
/**
* @constant
* @type {number}
*/
Phaser.Tilemap.WEST = 3;
Phaser.Tilemap.prototype = {
/**
* Creates an empty map of the given dimensions and one blank layer. If layers already exist they are erased.
*
* @method Phaser.Tilemap#create
* @param {string} name - The name of the default layer of the map.
* @param {number} width - The width of the map in tiles.
* @param {number} height - The height of the map in tiles.
* @param {number} tileWidth - The width of the tiles the map uses for calculations.
* @param {number} tileHeight - The height of the tiles the map uses for calculations.
* @param {Phaser.Group} [group] - Optional Group to add the layer to. If not specified it will be added to the World group.
* @return {Phaser.TilemapLayer} The TilemapLayer object. This is an extension of Phaser.Image and can be moved around the display list accordingly.
*/
create: function (name, width, height, tileWidth, tileHeight, group)
{
if (group === undefined) { group = this.game.world; }
this.width = width;
this.height = height;
this.setTileSize(tileWidth, tileHeight);
this.layers.length = 0;
return this.createBlankLayer(name, width, height, tileWidth, tileHeight, group);
},
/**
* Sets the base tile size for the map.
*
* @method Phaser.Tilemap#setTileSize
* @param {number} tileWidth - The width of the tiles the map uses for calculations.
* @param {number} tileHeight - The height of the tiles the map uses for calculations.
*/
setTileSize: function (tileWidth, tileHeight)
{
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.widthInPixels = this.width * tileWidth;
this.heightInPixels = this.height * tileHeight;
},
/**
* Adds an image to the map to be used as a tileset. A single map may use multiple tilesets.
* Note that the tileset name can be found in the JSON file exported from Tiled, or in the Tiled editor.
*
* @method Phaser.Tilemap#addTilesetImage
* @param {string} tileset - The name of the tileset as specified in the map data.
* @param {string|Phaser.BitmapData} [key] - The key of the Phaser.Cache image used for this tileset.
* If `undefined` or `null` it will look for an image with a key matching the tileset parameter.
* You can also pass in a BitmapData which can be used instead of an Image.
* @param {number} [tileWidth=32] - The width of the tiles in the Tileset Image. If not given it will default to the map.tileWidth value, if that isn't set then 32.
* @param {number} [tileHeight=32] - The height of the tiles in the Tileset Image. If not given it will default to the map.tileHeight value, if that isn't set then 32.
* @param {number} [tileMargin=0] - The width of the tiles in the Tileset Image.
* @param {number} [tileSpacing=0] - The height of the tiles in the Tileset Image.
* @param {number} [gid=0] - If adding multiple tilesets to a blank/dynamic map, specify the starting GID the set will use here.
* @return {Phaser.Tileset} Returns the Tileset object that was created or updated, or null if it failed.
*/
addTilesetImage: function (tileset, key, tileWidth, tileHeight, tileMargin, tileSpacing, gid)
{
if (tileset === undefined) { return null; }
if (tileWidth === undefined) { tileWidth = this.tileWidth; }
if (tileHeight === undefined) { tileHeight = this.tileHeight; }
if (tileMargin === undefined) { tileMargin = 0; }
if (tileSpacing === undefined) { tileSpacing = 0; }
if (gid === undefined) { gid = 0; }
// In-case we're working from a blank map
if (tileWidth === 0)
{
tileWidth = 32;
}
if (tileHeight === 0)
{
tileHeight = 32;
}
var img = null;
if (key === undefined || key === null)
{
key = tileset;
}
if (key instanceof Phaser.BitmapData)
{
img = key.canvas;
}
else
{
if (!this.game.cache.checkImageKey(key))
{
console.warn('Phaser.Tilemap.addTilesetImage: Invalid image key given: "' + key + '"');
return null;
}
img = this.game.cache.getImage(key);
}
var idx = this.getTilesetIndex(tileset);
if (idx === null && this.format === Phaser.Tilemap.TILED_JSON)
{
console.warn('Phaser.Tilemap.addTilesetImage: No data found in the JSON matching the tileset name: "' + tileset + '"');
console.log('Tilesets: ', this.tilesets);
return null;
}
if (this.tilesets[idx])
{
this.tilesets[idx].setImage(img);
return this.tilesets[idx];
}
else
{
var newSet = new Phaser.Tileset(tileset, gid, tileWidth, tileHeight, tileMargin, tileSpacing, {});
newSet.setImage(img);
this.tilesets.push(newSet);
var i = this.tilesets.length - 1;
var x = tileMargin;
var y = tileMargin;
var count = 0;
var countX = 0;
var countY = 0;
for (var t = gid; t < gid + newSet.total; t++)
{
this.tiles[t] = [ x, y, i ];
x += tileWidth + tileSpacing;
count++;
if (count === newSet.total)
{
break;
}
countX++;
if (countX === newSet.columns)
{
x = tileMargin;
y += tileHeight + tileSpacing;
countX = 0;
countY++;
if (countY === newSet.rows)
{
break;
}
}
}
return newSet;
}
},
/**
* Creates a Sprite for every {@link http://doc.mapeditor.org/reference/tmx-map-format/#object object} matching the `gid` argument. You can optionally specify the group that the Sprite will be created in. If none is
* given it will be created in the World. All properties from the map data objectgroup are copied across to the Sprite, so you can use this as an easy way to
* configure Sprite properties from within the map editor. For example giving an object a property of `alpha: 0.5` in the map editor will duplicate that when the
* Sprite is created. You could also give it a value like: `body.velocity.x: 100` to set it moving automatically.
*
* The `gid` argument is matched against:
*
* 1. For a tile object, the tile identifier (`gid`); or
* 2. The object's unique ID (`id`); or
* 3. The object's `name` (a string)
*
* @method Phaser.Tilemap#createFromObjects
* @param {string} name - The name of the Object Group to create Sprites from.
* @param {number|string} gid - The object's tile reference (gid), unique ID (id) or name.
* @param {string} key - The Game.cache key of the image that this Sprite will use.
* @param {number|string} [frame] - If the Sprite image contains multiple frames you can specify which one to use here.
* @param {boolean} [exists=true] - The default exists state of the Sprite.
* @param {boolean} [autoCull=false] - The default autoCull state of the Sprite. Sprites that are autoCulled are culled from the camera if out of its range.
* @param {Phaser.Group} [group=Phaser.World] - Group to add the Sprite to. If not specified it will be added to the World group.
* @param {object} [CustomClass=Phaser.Sprite] - If you wish to create your own class, rather than Phaser.Sprite, pass the class here. Your class must extend Phaser.Sprite and have the same constructor parameters.
* @param {boolean} [adjustY=true] - By default the Tiled map editor uses a bottom-left coordinate system. Phaser uses top-left. So most objects will appear too low down. This parameter moves them up by their height.
* @param {boolean} [adjustSize=true] - By default the width and height of the objects are transferred to the sprite. This parameter controls that behavior.
*/
createFromObjects: function (name, gid, key, frame, exists, autoCull, group, CustomClass, adjustY, adjustSize)
{
if (exists === undefined) { exists = true; }
if (autoCull === undefined) { autoCull = false; }
if (group === undefined) { group = this.game.world; }
if (CustomClass === undefined) { CustomClass = Phaser.Sprite; }
if (adjustY === undefined) { adjustY = true; }
if (adjustSize === undefined) { adjustSize = true; }
if (!this.objects[name])
{
console.warn('Tilemap.createFromObjects: Invalid objectgroup name given: ' + name);
console.log('Objects: ', this.objects);
return;
}
for (var i = 0; i < this.objects[name].length; i++)
{
var found = false;
var obj = this.objects[name][i];
if (obj.gid !== undefined && typeof gid === 'number' && obj.gid === gid)
{
found = true;
}
else if (obj.id !== undefined && typeof gid === 'number' && obj.id === gid)
{
found = true;
}
else if (obj.name !== undefined && typeof gid === 'string' && obj.name === gid)
{
found = true;
}
if (found)
{
var sprite = new CustomClass(this.game, parseFloat(obj.x), parseFloat(obj.y), key, frame);
sprite.name = obj.name;
sprite.autoCull = autoCull;
sprite.exists = exists;
sprite.visible = obj.visible;
if (adjustSize)
{
if (obj.width)
{
sprite.width = obj.width;
}
if (obj.height)
{
sprite.height = obj.height;
}
}
if (obj.rotation)
{
sprite.angle = obj.rotation;
}
if (adjustY)
{
sprite.y -= sprite.height;
}
group.add(sprite);
for (var property in obj.properties)
{
group.set(sprite, property, obj.properties[property], false, false, 0, true);
}
}
}
},
/**
* Creates a Sprite for every object matching the given tile indexes in the map data.
* You can specify the group that the Sprite will be created in. If none is given it will be created in the World.
* You can optional specify if the tile will be replaced with another after the Sprite is created. This is useful if you want to lay down special
* tiles in a level that are converted to Sprites, but want to replace the tile itself with a floor tile or similar once converted.
*
* @method Phaser.Tilemap#createFromTiles
* @param {integer|Array} tiles - The tile index, or array of indexes, to create Sprites from.
* @param {integer|Array} replacements - The tile index, or array of indexes, to change a converted tile to. Set to -1 to remove the tile. Set to `null` to make no change (leave the tile as is).
* @param {string} key - The Game.cache key of the image that this Sprite will use.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on.
* @param {Phaser.Group} [group=Phaser.World] - Group to add the Sprite to. If not specified it will be added to the World group.
* @param {object} [properties] - An object that contains the default properties for your newly created Sprite. This object will be iterated and any matching Sprite property will be set.
* @return {integer} The number of Sprites that were created.
*/
createFromTiles: function (tiles, replacements, key, layer, group, properties)
{
if (typeof tiles === 'number') { tiles = [ tiles ]; }
if (replacements === undefined || replacements === null)
{
replacements = [];
}
else if (typeof replacements === 'number')
{
replacements = [ replacements ];
}
layer = this.getLayer(layer);
if (group === undefined) { group = this.game.world; }
if (properties === undefined) { properties = {}; }
if (properties.customClass === undefined)
{
properties.customClass = Phaser.Sprite;
}
if (properties.adjustY === undefined)
{
properties.adjustY = true;
}
var lw = this.layers[layer].width;
var lh = this.layers[layer].height;
this.copy(0, 0, lw, lh, layer);
if (this._results.length < 2)
{
return 0;
}
var total = 0;
var sprite;
for (var i = 1, len = this._results.length; i < len; i++)
{
if (tiles.indexOf(this._results[i].index) !== -1)
{
sprite = new properties.customClass(this.game, this._results[i].worldX, this._results[i].worldY, key);
for (var property in properties)
{
sprite[property] = properties[property];
}
group.add(sprite);
total++;
}
}
if (replacements.length === 1)
{
// Assume 1 replacement for all types of tile given
for (i = 0; i < tiles.length; i++)
{
this.replace(tiles[i], replacements[0], 0, 0, lw, lh, layer);
}
}
else if (replacements.length > 1)
{
// Assume 1 for 1 mapping
for (i = 0; i < tiles.length; i++)
{
this.replace(tiles[i], replacements[i], 0, 0, lw, lh, layer);
}
}
return total;
},
/**
* Creates a new TilemapLayer object. By default TilemapLayers are fixed to the camera.
* The `layer` parameter is important. If you've created your map in Tiled then you can get this by looking in Tiled and looking at the Layer name.
* Or you can open the JSON file it exports and look at the layers[].name value. Either way it must match.
* If you wish to create a blank layer to put your own tiles on then see Tilemap.createBlankLayer.
*
* @method Phaser.Tilemap#createLayer
* @param {number|string} layer - The layer array index value, or if a string is given the layer name, within the map data that this TilemapLayer represents.
* @param {number} [width] - The rendered width of the layer, should never be wider than Game.width. If not given it will be set to Game.width.
* @param {number} [height] - The rendered height of the layer, should never be wider than Game.height. If not given it will be set to Game.height.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.TilemapLayer} The TilemapLayer object. This is an extension of Phaser.Sprite and can be moved around the display list accordingly.
*/
createLayer: function (layer, width, height, group)
{
// Add Buffer support for the left of the canvas
if (width === undefined) { width = this.game.width; }
if (height === undefined) { height = this.game.height; }
if (group === undefined) { group = this.game.world; }
var index = layer;
if (typeof layer === 'string')
{
index = this.getLayerIndex(layer);
}
if (index === null || index > this.layers.length)
{
console.warn('Tilemap.createLayer: Invalid layer ID given: "' + layer + '"');
console.log('Layers: ', this.layers);
return;
}
// Sort out the display dimensions, so they never render too much, or too little.
if (width === undefined || width <= 0)
{
width = Math.min(this.game.width, this.layers[index].widthInPixels);
}
else if (width > this.game.width)
{
width = this.game.width;
}
if (height === undefined || height <= 0)
{
height = Math.min(this.game.height, this.layers[index].heightInPixels);
}
else if (height > this.game.height)
{
height = this.game.height;
}
if (this.enableDebug)
{
console.group('Tilemap.createLayer');
console.log('Name:', this.layers[index].name);
console.log('Size:', width, 'x', height);
console.log('Tileset:', this.tilesets[0].name, 'index:', index);
}
var rootLayer = group.add(new Phaser.TilemapLayer(this.game, this, index, width, height));
if (this.enableDebug)
{
console.groupEnd();
}
return rootLayer;
},
/**
* Creates a new and empty layer on this Tilemap. By default TilemapLayers are fixed to the camera.
*
* @method Phaser.Tilemap#createBlankLayer
* @param {string} name - The name of this layer. Must be unique within the map.
* @param {number} width - The width of the layer in tiles.
* @param {number} height - The height of the layer in tiles.
* @param {number} tileWidth - The width of the tiles the layer uses for calculations.
* @param {number} tileHeight - The height of the tiles the layer uses for calculations.
* @param {Phaser.Group} [group] - Optional Group to add the layer to. If not specified it will be added to the World group.
* @return {Phaser.TilemapLayer} The TilemapLayer object. This is an extension of Phaser.Image and can be moved around the display list accordingly.
*/
createBlankLayer: function (name, width, height, tileWidth, tileHeight, group)
{
if (group === undefined) { group = this.game.world; }
if (this.getLayerIndex(name) !== null)
{
console.warn('Tilemap.createBlankLayer: Layer with matching name already exists: ' + name);
return;
}
var layer = {
name: name,
x: 0,
y: 0,
width: width,
height: height,
widthInPixels: width * tileWidth,
heightInPixels: height * tileHeight,
alpha: 1,
visible: true,
properties: {},
indexes: [],
callbacks: [],
bodies: [],
data: null
};
var row;
var output = [];
for (var y = 0; y < height; y++)
{
row = [];
for (var x = 0; x < width; x++)
{
row.push(new Phaser.Tile(layer, -1, x, y, tileWidth, tileHeight));
}
output.push(row);
}
layer.data = output;
this.layers.push(layer);
this.currentLayer = this.layers.length - 1;
var w = layer.widthInPixels;
var h = layer.heightInPixels;
if (w > this.game.width)
{
w = this.game.width;
}
if (h > this.game.height)
{
h = this.game.height;
}
var output = new Phaser.TilemapLayer(this.game, this, this.layers.length - 1, w, h);
output.name = name;
return group.add(output);
},
/**
* Gets the layer index based on the layers name.
*
* @method Phaser.Tilemap#getIndex
* @protected
* @param {array} location - The local array to search.
* @param {string} name - The name of the array element to get.
* @return {number} The index of the element in the array, or null if not found.
*/
getIndex: function (location, name)
{
for (var i = 0; i < location.length; i++)
{
if (location[i].name === name)
{
return i;
}
}
return null;
},
/**
* Gets the layer index based on its name.
*
* @method Phaser.Tilemap#getLayerIndex
* @param {string} name - The name of the layer to get.
* @return {number} The index of the layer in this tilemap, or null if not found.
*/
getLayerIndex: function (name)
{
return this.getIndex(this.layers, name);
},
/**
* Gets the tileset index based on its name.
*
* @method Phaser.Tilemap#getTilesetIndex
* @param {string} name - The name of the tileset to get.
* @return {number} The index of the tileset in this tilemap, or null if not found.
*/
getTilesetIndex: function (name)
{
return this.getIndex(this.tilesets, name);
},
/**
* Gets the image index based on its name.
*
* @method Phaser.Tilemap#getImageIndex
* @param {string} name - The name of the image to get.
* @return {number} The index of the image in this tilemap, or null if not found.
*/
getImageIndex: function (name)
{
return this.getIndex(this.images, name);
},
/**
* Sets a global collision callback for the given tile index within the layer. This will affect all tiles on this layer that have the same index.
* If a callback is already set for the tile index it will be replaced. Set the callback to null to remove it.
* If you want to set a callback for a tile at a specific location on the map then see setTileLocationCallback.
*
* Return `true` from the callback to continue separating the tile and colliding object, or `false` to cancel the collision for the current tile (see {@link Phaser.Physics.Arcade#separateTile}).
*
* @method Phaser.Tilemap#setTileIndexCallback
* @param {number|array} indexes - Either a single tile index, or an array of tile indexes to have a collision callback set for.
* @param {function} callback - The callback that will be invoked when the tile is collided with (via {@link Phaser.Physics.Arcade#collide}).
* @param {object} callbackContext - The context under which the callback is called.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer.
*/
setTileIndexCallback: function (indexes, callback, callbackContext, layer)
{
layer = this.getLayer(layer);
if (typeof indexes === 'number')
{
if (callback === null)
{
delete this.layers[layer].callbacks[indexes];
}
else
{
// This may seem a bit wasteful, because it will cause empty array elements to be created, but the look-up cost is much
// less than having to iterate through the callbacks array hunting down tile indexes each frame, so I'll take the small memory hit.
this.layers[layer].callbacks[indexes] = { callback: callback, callbackContext: callbackContext };
}
}
else
{
for (var i = 0, len = indexes.length; i < len; i++)
{
if (callback === null)
{
delete this.layers[layer].callbacks[indexes[i]];
}
else
{
this.layers[layer].callbacks[indexes[i]] = { callback: callback, callbackContext: callbackContext };
}
}
}
},
/**
* Sets a global collision callback for the given map location within the layer. This will affect all tiles on this layer found in the given area.
* If a callback is already set for the tile index it will be replaced. Set the callback to null to remove it.
* If you want to set a callback for a tile at a specific location on the map then see setTileLocationCallback.
*
* Return `true` from the callback to continue separating the tile and colliding object, or `false` to cancel the collision for the current tile (see {@link Phaser.Physics.Arcade#separateTile}).
*
* @method Phaser.Tilemap#setTileLocationCallback
* @param {number} x - X position of the top left of the area to copy (given in tiles, not pixels)
* @param {number} y - Y position of the top left of the area to copy (given in tiles, not pixels)
* @param {number} width - The width of the area to copy (given in tiles, not pixels)
* @param {number} height - The height of the area to copy (given in tiles, not pixels)
* @param {function} callback - The callback that will be invoked when the tile is collided with (via {@link Phaser.Physics.Arcade#collide}).
* @param {object} callbackContext - The context under which the callback is called.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer.
*/
setTileLocationCallback: function (x, y, width, height, callback, callbackContext, layer)
{
layer = this.getLayer(layer);
this.copy(x, y, width, height, layer);
if (this._results.length < 2)
{
return;
}
for (var i = 1; i < this._results.length; i++)
{
this._results[i].setCollisionCallback(callback, callbackContext);
}
},
/**
* Sets collision on the given tile or tiles. You can pass in either a single numeric index or an array of indexes: [2, 3, 15, 20].
* The `collides` parameter controls if collision will be enabled (true) or disabled (false).
*
* Collision-enabled tiles can be collided against Sprites using {@link Phaser.Physics.Arcade#collide}.
*
* You can verify the collision faces by enabling {@link Phaser.TilemapLayer#debug}.
*
* @method Phaser.Tilemap#setCollision
* @param {number|array} indexes - Either a single tile index, or an array of tile IDs to be checked for collision.
* @param {boolean} [collides=true] - If true it will enable collision. If false it will clear collision.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer.
* @param {boolean} [recalculate=true] - Recalculates the tile faces after the update.
*/
setCollision: function (indexes, collides, layer, recalculate)
{
if (collides === undefined) { collides = true; }
if (recalculate === undefined) { recalculate = true; }
layer = this.getLayer(layer);
if (typeof indexes === 'number')
{
return this.setCollisionByIndex(indexes, collides, layer, true);
}
else if (Array.isArray(indexes))
{
// Collide all of the IDs given in the indexes array
for (var i = 0; i < indexes.length; i++)
{
this.setCollisionByIndex(indexes[i], collides, layer, false);
}
if (recalculate)
{
// Now re-calculate interesting faces
this.calculateFaces(layer);
}
}
},
/**
* Sets collision on a range of tiles where the tile IDs increment sequentially.
* Calling this with a start value of 10 and a stop value of 14 would set collision for tiles 10, 11, 12, 13 and 14.
* The `collides` parameter controls if collision will be enabled (true) or disabled (false).
*
* @method Phaser.Tilemap#setCollisionBetween
* @param {number} start - The first index of the tile to be set for collision.
* @param {number} stop - The last index of the tile to be set for collision.
* @param {boolean} [collides=true] - If true it will enable collision. If false it will clear collision.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer.
* @param {boolean} [recalculate=true] - Recalculates the tile faces after the update.
*/
setCollisionBetween: function (start, stop, collides, layer, recalculate)
{
if (collides === undefined) { collides = true; }
if (recalculate === undefined) { recalculate = true; }
layer = this.getLayer(layer);
if (start > stop)
{
return;
}
for (var index = start; index <= stop; index++)
{
this.setCollisionByIndex(index, collides, layer, false);
}
if (recalculate)
{
// Now re-calculate interesting faces
this.calculateFaces(layer);
}
},
/**
* Sets collision on all tiles in the given layer, except for the IDs of those in the given array.
* The `collides` parameter controls if collision will be enabled (true) or disabled (false).
*
* @method Phaser.Tilemap#setCollisionByExclusion
* @param {array} indexes - An array of the tile IDs to not be counted for collision.
* @param {boolean} [collides=true] - If true it will enable collision. If false it will clear collision.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer.
* @param {boolean} [recalculate=true] - Recalculates the tile faces after the update.
*/
setCollisionByExclusion: function (indexes, collides, layer, recalculate)
{
if (collides === undefined) { collides = true; }
if (recalculate === undefined) { recalculate = true; }
layer = this.getLayer(layer);
// Collide everything, except the IDs given in the indexes array
for (var i = 0, len = this.tiles.length; i < len; i++)
{
if (indexes.indexOf(i) === -1)
{
this.setCollisionByIndex(i, collides, layer, false);
}
}
if (recalculate)
{
// Now re-calculate interesting faces
this.calculateFaces(layer);
}
},
/**
* Sets collision values on a tile in the set.
* You shouldn't usually call this method directly, instead use setCollision, setCollisionBetween or setCollisionByExclusion.
*
* @method Phaser.Tilemap#setCollisionByIndex
* @protected
* @param {number} index - The index of the tile on the layer.
* @param {boolean} [collides=true] - If true it will enable collision on the tile. If false it will clear collision values from the tile.
* @param {number} [layer] - The layer to operate on. If not given will default to this.currentLayer.
* @param {boolean} [recalculate=true] - Recalculates the tile faces after the update.
*/
setCollisionByIndex: function (index, collides, layer, recalculate)
{
if (collides === undefined) { collides = true; }
if (layer === undefined) { layer = this.currentLayer; }
if (recalculate === undefined) { recalculate = true; }
if (collides)
{
this.collideIndexes.push(index);
}
else
{
var i = this.collideIndexes.indexOf(index);
if (i > -1)
{
this.collideIndexes.splice(i, 1);
}
}
for (var y = 0; y < this.layers[layer].height; y++)
{
for (var x = 0; x < this.layers[layer].width; x++)
{
var tile = this.layers[layer].data[y][x];
if (tile && tile.index === index)
{
if (collides)
{
tile.setCollision(true, true, true, true);
}
else
{
tile.resetCollision();
}
tile.faceTop = collides;
tile.faceBottom = collides;
tile.faceLeft = collides;
tile.faceRight = collides;
}
}
}
if (recalculate)
{
// Now re-calculate interesting faces
this.calculateFaces(layer);
}
return layer;
},
/**
* Gets the TilemapLayer index as used in the setCollision calls.
*
* @method Phaser.Tilemap#getLayer
* @protected
* @param {number|string|Phaser.TilemapLayer} layer - The layer to operate on. If not given will default to this.currentLayer.
* @return {number} The TilemapLayer index.
*/
getLayer: function (layer)
{
if (layer === undefined)
{
layer = this.currentLayer;
}
else if (typeof layer === 'string')
{
var layerArg = layer;
layer = this.getLayerIndex(layer);
if (layer === null)
{
console.warn('No such layer name: ' + layerArg);
}
}
else if (layer instanceof Phaser.TilemapLayer)
{
layer = layer.index;
}
return layer;
},
/**
* Turn off/on the recalculation of faces for tile or collision updates.
* `setPreventRecalculate(true)` puts recalculation on hold while `setPreventRecalculate(false)` recalculates all the changed layers.
*
* @method Phaser.Tilemap#setPreventRecalculate
* @param {boolean} value - If true it will put the recalculation on hold.
*/
setPreventRecalculate: function (value)
{
if (value === true && this.preventingRecalculate !== true)
{
this.preventingRecalculate = true;
this.needToRecalculate = {};
}
if (value === false && this.preventingRecalculate === true)
{
this.preventingRecalculate = false;
for (var i in this.needToRecalculate)
{
this.calculateFaces(i);
}
this.needToRecalculate = false;
}
},
/**
* Internal function.
*
* @method Phaser.Tilemap#calculateFaces
* @protected
* @param {number} layer - The index of the TilemapLayer to operate on.
*/
calculateFaces: function (layer)
{
if (this.preventingRecalculate)
{
this.needToRecalculate[layer] = true;
return;
}
var above = null;
var below = null;
var left = null;
var right = null;
for (var y = 0, h = this.layers[layer].height; y < h; y++)
{
for (var x = 0, w = this.layers[layer].width; x < w; x++)
{
var tile = this.layers[layer].data[y][x];
if (tile)
{
above = this.getTileAbove(layer, x, y);
below = this.getTileBelow(layer, x, y);
left = this.getTileLeft(layer, x, y);
right = this.getTileRight(layer, x, y);
if (tile.collides)
{
tile.faceTop = true;
tile.faceBottom = true;
tile.faceLeft = true;
tile.faceRight = true;
}
if (above && above.collides)
{
// There is a tile above this one that also collides, so the top of this tile is no longer interesting
tile.faceTop = false;
}
if (below && below.collides)
{
// There is a tile below this one that also collides, so the bottom of this tile is no longer interesting
tile.faceBottom = false;
}
if (left && left.collides)
{
// There is a tile left this one that also collides, so the left of this tile is no longer interesting
tile.faceLeft = false;
}
if (right && right.collides)
{
// There is a tile right this one that also collides, so the right of this tile is no longer interesting
tile.faceRight = false;
}
}
}
}
},
/**
* Gets the tile above the tile coordinates given.
* Mostly used as an internal function by calculateFaces.
*
* @method Phaser.Tilemap#getTileAbove
* @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer().
* @param {number} x - The x coordinate to get the tile from. In tiles, not pixels.
* @param {number} y - The y coordinate to get the tile from. In tiles, not pixels.
*/
getTileAbove: function (layer, x, y)
{
if (y > 0)
{
return this.layers[layer].data[y - 1][x];
}
return null;
},
/**
* Gets the tile below the tile coordinates given.
* Mostly used as an internal function by calculateFaces.
*
* @method Phaser.Tilemap#getTileBelow
* @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer().
* @param {number} x - The x coordinate to get the tile from. In tiles, not pixels.
* @param {number} y - The y coordinate to get the tile from. In tiles, not pixels.
*/
getTileBelow: function (layer, x, y)
{
if (y < this.layers[layer].height - 1)
{
return this.layers[layer].data[y + 1][x];
}
return null;
},
/**
* Gets the tile to the left of the tile coordinates given.
* Mostly used as an internal function by calculateFaces.
*
* @method Phaser.Tilemap#getTileLeft
* @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer().
* @param {number} x - The x coordinate to get the tile from. In tiles, not pixels.
* @param {number} y - The y coordinate to get the tile from. In tiles, not pixels.
*/
getTileLeft: function (layer, x, y)
{
if (x > 0)
{
return this.layers[layer].data[y][x - 1];
}
return null;
},
/**
* Gets the tile to the right of the tile coordinates given.
* Mostly used as an internal function by calculateFaces.
*
* @method Phaser.Tilemap#getTileRight
* @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer().
* @param {number} x - The x coordinate to get the tile from. In tiles, not pixels.
* @param {number} y - The y coordinate to get the tile from. In tiles, not pixels.
*/
getTileRight: function (layer, x, y)
{
if (x < this.layers[layer].width - 1)
{
return this.layers[layer].data[y][x + 1];
}
return null;
},
/**
* Sets the current layer to the given index.
*
* @method Phaser.Tilemap#setLayer
* @param {number|string|Phaser.TilemapLayer} layer - The layer to set as current.
*/
setLayer: function (layer)
{
layer = this.getLayer(layer);
if (this.layers[layer])
{
this.currentLayer = layer;
}
},
/**
* Checks if there is a tile at the given location.
*
* @method Phaser.Tilemap#hasTile
* @param {number} x - X position to check if a tile exists at (given in tile units, not pixels)
* @param {number} y - Y position to check if a tile exists at (given in tile units, not pixels)
* @param {number|string|Phaser.TilemapLayer} layer - The layer to set as current.
* @return {boolean} True if there is a tile at the given location, otherwise false.
*/
hasTile: function (x, y, layer)
{
layer = this.getLayer(layer);
if (this.layers[layer].data[y] === undefined || this.layers[layer].data[y][x] === undefined)
{
return false;
}
return (this.layers[layer].data[y][x].index > -1);
},
/**
* Removes the tile located at the given coordinates and updates the collision data.
*
* @method Phaser.Tilemap#removeTile
* @param {number} x - X position to place the tile (given in tile units, not pixels)
* @param {number} y - Y position to place the tile (given in tile units, not pixels)
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to modify.
* @return {Phaser.Tile} The Tile object that was removed from this map.
*/
removeTile: function (x, y, layer)
{
layer = this.getLayer(layer);
if (x >= 0 && x < this.layers[layer].width && y >= 0 && y < this.layers[layer].height)
{
if (this.hasTile(x, y, layer))
{
var tile = this.layers[layer].data[y][x];
this.layers[layer].data[y][x] = new Phaser.Tile(this.layers[layer], -1, x, y, this.tileWidth, this.tileHeight);
this.layers[layer].dirty = true;
this.calculateFaces(layer);
return tile;
}
}
},
/**
* Removes the tile located at the given coordinates and updates the collision data. The coordinates are given in pixel values.
*
* @method Phaser.Tilemap#removeTileWorldXY
* @param {number} x - X position to insert the tile (given in pixels)
* @param {number} y - Y position to insert the tile (given in pixels)
* @param {number} tileWidth - The width of the tile in pixels.
* @param {number} tileHeight - The height of the tile in pixels.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to modify.
* @return {Phaser.Tile} The Tile object that was removed from this map.
*/
removeTileWorldXY: function (x, y, tileWidth, tileHeight, layer)
{
layer = this.getLayer(layer);
x = this.game.math.snapToFloor(x, tileWidth) / tileWidth;
y = this.game.math.snapToFloor(y, tileHeight) / tileHeight;
return this.removeTile(x, y, layer);
},
/**
* Puts a tile of the given index value at the coordinate specified.
* If you pass `null` as the tile it will pass your call over to Tilemap.removeTile instead.
*
* @method Phaser.Tilemap#putTile
* @param {Phaser.Tile|number|null} tile - The index of this tile to set or a Phaser.Tile object. If a Tile object, all of its data will be copied. If null the tile is removed from the map.
* @param {number} x - X position to place the tile (given in tile units, not pixels)
* @param {number} y - Y position to place the tile (given in tile units, not pixels)
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to modify.
* @return {Phaser.Tile} The Tile object that was created or added to this map.
*/
putTile: function (tile, x, y, layer)
{
if (tile === null)
{
return this.removeTile(x, y, layer);
}
layer = this.getLayer(layer);
if (x >= 0 && x < this.layers[layer].width && y >= 0 && y < this.layers[layer].height)
{
var index;
if (tile instanceof Phaser.Tile)
{
index = tile.index;
if (this.hasTile(x, y, layer))
{
this.layers[layer].data[y][x].copy(tile);
}
else
{
this.layers[layer].data[y][x] = new Phaser.Tile(layer, index, x, y, tile.width, tile.height);
}
}
else
{
index = tile;
if (this.hasTile(x, y, layer))
{
this.layers[layer].data[y][x].index = index;
}
else
{
this.layers[layer].data[y][x] = new Phaser.Tile(this.layers[layer], index, x, y, this.tileWidth, this.tileHeight);
}
}
if (this.collideIndexes.indexOf(index) > -1)
{
this.layers[layer].data[y][x].setCollision(true, true, true, true);
}
else
{
this.layers[layer].data[y][x].resetCollision();
}
this.layers[layer].dirty = true;
this.calculateFaces(layer);
return this.layers[layer].data[y][x];
}
return null;
},
/**
* Puts a tile into the Tilemap layer. The coordinates are given in pixel values.
*
* @method Phaser.Tilemap#putTileWorldXY
* @param {Phaser.Tile|number} tile - The index of this tile to set or a Phaser.Tile object.
* @param {number} x - X position to insert the tile (given in pixels)
* @param {number} y - Y position to insert the tile (given in pixels)
* @param {number} tileWidth - The width of the tile in pixels.
* @param {number} tileHeight - The height of the tile in pixels.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to modify.
* @return {Phaser.Tile} The Tile object that was created or added to this map.
*/
putTileWorldXY: function (tile, x, y, tileWidth, tileHeight, layer)
{
layer = this.getLayer(layer);
x = this.game.math.snapToFloor(x, tileWidth) / tileWidth;
y = this.game.math.snapToFloor(y, tileHeight) / tileHeight;
return this.putTile(tile, x, y, layer);
},
/**
* Searches the entire map layer for the first tile matching the given index, then returns that Phaser.Tile object.
* If no match is found it returns null.
* The search starts from the top-left tile and continues horizontally until it hits the end of the row, then it drops down to the next column.
* If the reverse boolean is true, it scans starting from the bottom-right corner traveling up to the top-left.
*
* @method Phaser.Tilemap#searchTileIndex
* @param {number} index - The tile index value to search for.
* @param {number} [skip=0] - The number of times to skip a matching tile before returning.
* @param {number} [reverse=false] - If true it will scan the layer in reverse, starting at the bottom-right. Otherwise it scans from the top-left.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to get the tile from.
* @return {Phaser.Tile} The first (or n skipped) tile with the matching index.
*/
searchTileIndex: function (index, skip, reverse, layer)
{
if (skip === undefined) { skip = 0; }
if (reverse === undefined) { reverse = false; }
layer = this.getLayer(layer);
var c = 0;
if (reverse)
{
for (var y = this.layers[layer].height - 1; y >= 0; y--)
{
for (var x = this.layers[layer].width - 1; x >= 0; x--)
{
if (this.layers[layer].data[y][x].index === index)
{
if (c === skip)
{
return this.layers[layer].data[y][x];
}
else
{
c++;
}
}
}
}
}
else
{
for (var y = 0; y < this.layers[layer].height; y++)
{
for (var x = 0; x < this.layers[layer].width; x++)
{
if (this.layers[layer].data[y][x].index === index)
{
if (c === skip)
{
return this.layers[layer].data[y][x];
}
else
{
c++;
}
}
}
}
}
return null;
},
/**
* Gets a tile from the Tilemap Layer. The coordinates are given in tile values.
*
* @method Phaser.Tilemap#getTile
* @param {number} x - X position to get the tile from (given in tile units, not pixels)
* @param {number} y - Y position to get the tile from (given in tile units, not pixels)
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to get the tile from.
* @param {boolean} [nonNull=false] - If true getTile won't return null for empty tiles, but a Tile object with an index of -1.
* @return {Phaser.Tile} The tile at the given coordinates or null if no tile was found or the coordinates were invalid.
*/
getTile: function (x, y, layer, nonNull)
{
if (nonNull === undefined) { nonNull = false; }
layer = this.getLayer(layer);
if (x >= 0 && x < this.layers[layer].width && y >= 0 && y < this.layers[layer].height)
{
if (this.layers[layer].data[y][x].index === -1)
{
if (nonNull)
{
return this.layers[layer].data[y][x];
}
else
{
return null;
}
}
else
{
return this.layers[layer].data[y][x];
}
}
else
{
return null;
}
},
/**
* Gets a tile from the Tilemap layer. The coordinates are given in pixel values.
*
* @method Phaser.Tilemap#getTileWorldXY
* @param {number} x - X position to get the tile from (given in pixels)
* @param {number} y - Y position to get the tile from (given in pixels)
* @param {number} [tileWidth] - The width of the tiles. If not given the map default is used.
* @param {number} [tileHeight] - The height of the tiles. If not given the map default is used.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to get the tile from.
* @param {boolean} [nonNull=false] - If true getTile won't return null for empty tiles, but a Tile object with an index of -1.
* @return {Phaser.Tile} The tile at the given coordinates.
*/
getTileWorldXY: function (x, y, tileWidth, tileHeight, layer, nonNull)
{
if (tileWidth === undefined) { tileWidth = this.tileWidth; }
if (tileHeight === undefined) { tileHeight = this.tileHeight; }
layer = this.getLayer(layer);
x = this.game.math.snapToFloor(x, tileWidth) / tileWidth;
y = this.game.math.snapToFloor(y, tileHeight) / tileHeight;
return this.getTile(x, y, layer, nonNull);
},
/**
* Copies all of the tiles in the given rectangular block into the tilemap data buffer.
*
* @method Phaser.Tilemap#copy
* @param {integer} [x=0] - X position of the top left of the area to copy (given in tiles, not pixels)
* @param {integer} [y=0] - Y position of the top left of the area to copy (given in tiles, not pixels)
* @param {integer} [width] - The width of the area to copy (given in tiles, not pixels)
* @param {integer} [height] - The height of the area to copy (given in tiles, not pixels)
* @param {integer|string|Phaser.TilemapLayer} [layer] - The layer to copy the tiles from.
* @return {array} An array of the tiles that were copied.
*/
copy: function (x, y, width, height, layer)
{
layer = this.getLayer(layer);
if (!this.layers[layer])
{
this._results.length = 0;
return;
}
if (x === undefined) { x = 0; }
if (y === undefined) { y = 0; }
if (width === undefined) { width = this.layers[layer].width; }
if (height === undefined) { height = this.layers[layer].height; }
if (x < 0)
{
x = 0;
}
if (y < 0)
{
y = 0;
}
if (width > this.layers[layer].width)
{
width = this.layers[layer].width;
}
if (height > this.layers[layer].height)
{
height = this.layers[layer].height;
}
this._results.length = 0;
this._results.push({ x: x, y: y, width: width, height: height, layer: layer });
for (var ty = y; ty < y + height; ty++)
{
for (var tx = x; tx < x + width; tx++)
{
this._results.push(this.layers[layer].data[ty][tx]);
}
}
return this._results;
},
/**
* Pastes a previously copied block of tile data into the given x/y coordinates. Data should have been prepared with Tilemap.copy.
*
* @method Phaser.Tilemap#paste
* @param {number} x - X position of the top left of the area to paste to (given in tiles, not pixels)
* @param {number} y - Y position of the top left of the area to paste to (given in tiles, not pixels)
* @param {array} tileblock - The block of tiles to paste.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to paste the tiles into.
*/
paste: function (x, y, tileblock, layer)
{
if (x === undefined) { x = 0; }
if (y === undefined) { y = 0; }
layer = this.getLayer(layer);
if (!tileblock || tileblock.length < 2)
{
return;
}
// Find out the difference between tileblock[1].x/y and x/y and use it as an offset, as it's the top left of the block to paste
var diffX = x - tileblock[1].x;
var diffY = y - tileblock[1].y;
for (var i = 1; i < tileblock.length; i++)
{
this.layers[layer].data[diffY + tileblock[i].y][diffX + tileblock[i].x].copy(tileblock[i]);
}
this.layers[layer].dirty = true;
this.calculateFaces(layer);
},
/**
* Scans the given area for tiles with an index matching tileA and swaps them with tileB.
* Only the tile indexes are modified.
*
* @method Phaser.Tilemap#swap
* @param {number} tileA - First tile index.
* @param {number} tileB - Second tile index.
* @param {number} x - X position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} y - Y position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} width - The width in tiles of the area to operate on.
* @param {number} height - The height in tiles of the area to operate on.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on.
*/
swap: function (tileA, tileB, x, y, width, height, layer)
{
layer = this.getLayer(layer);
this.copy(x, y, width, height, layer);
if (this._results.length < 2)
{
return;
}
this._tempA = tileA;
this._tempB = tileB;
this._results.forEach(this.swapHandler, this);
this.paste(x, y, this._results, layer);
},
/**
* Internal function that handles the swapping of tiles.
*
* @method Phaser.Tilemap#swapHandler
* @private
* @param {number} value
*/
swapHandler: function (value)
{
if (value.index === this._tempA)
{
// Swap A with B
value.index = this._tempB;
}
else if (value.index === this._tempB)
{
// Swap B with A
value.index = this._tempA;
}
},
/**
* For each tile in the given area defined by x/y and width/height run the given callback.
*
* @method Phaser.Tilemap#forEach
* @param {number} callback - The callback. Each tile in the given area will be passed to this callback as the first and only parameter.
* @param {number} context - The context under which the callback should be run.
* @param {number} x - X position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} y - Y position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} width - The width in tiles of the area to operate on.
* @param {number} height - The height in tiles of the area to operate on.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on.
*/
forEach: function (callback, context, x, y, width, height, layer)
{
layer = this.getLayer(layer);
this.copy(x, y, width, height, layer);
if (this._results.length < 2)
{
return;
}
this._results.forEach(callback, context);
this.paste(x, y, this._results, layer);
},
/**
* Scans the given area for tiles with an index matching `source` and updates their index to match `dest`.
* Only the tile indexes are modified.
*
* @method Phaser.Tilemap#replace
* @param {number} source - The tile index value to scan for.
* @param {number} dest - The tile index value to replace found tiles with.
* @param {number} [x=0] - X position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} [y=0] - Y position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} [width] - The width in tiles of the area to operate on.
* @param {number} [height] - The height in tiles of the area to operate on.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on.
*/
replace: function (source, dest, x, y, width, height, layer)
{
layer = this.getLayer(layer);
this.copy(x, y, width, height, layer);
if (this._results.length < 2)
{
return;
}
for (var i = 1; i < this._results.length; i++)
{
if (this._results[i].index === source)
{
this._results[i].index = dest;
}
}
this.paste(x, y, this._results, layer);
},
/**
* Randomises a set of tiles in a given area.
* Only the tile indexes are modified.
*
* @method Phaser.Tilemap#random
* @param {number} x - X position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} y - Y position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} width - The width in tiles of the area to operate on.
* @param {number} height - The height in tiles of the area to operate on.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on.
*/
random: function (x, y, width, height, layer)
{
layer = this.getLayer(layer);
this.copy(x, y, width, height, layer);
if (this._results.length < 2)
{
return;
}
var indexes = [];
for (var t = 1; t < this._results.length; t++)
{
if (this._results[t].index)
{
var idx = this._results[t].index;
if (indexes.indexOf(idx) === -1)
{
indexes.push(idx);
}
}
}
for (var i = 1; i < this._results.length; i++)
{
this._results[i].index = this.game.rnd.pick(indexes);
}
this.paste(x, y, this._results, layer);
},
/**
* Shuffles a set of tiles in a given area. It will only randomise the tiles in that area, so if they're all the same nothing will appear to have changed!
* Only the tile indexes are modified.
*
* @method Phaser.Tilemap#shuffle
* @param {number} x - X position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} y - Y position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} width - The width in tiles of the area to operate on.
* @param {number} height - The height in tiles of the area to operate on.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on.
*/
shuffle: function (x, y, width, height, layer)
{
layer = this.getLayer(layer);
this.copy(x, y, width, height, layer);
if (this._results.length < 2)
{
return;
}
var indexes = [];
for (var t = 1; t < this._results.length; t++)
{
if (this._results[t].index)
{
indexes.push(this._results[t].index);
}
}
Phaser.ArrayUtils.shuffle(indexes);
for (var i = 1; i < this._results.length; i++)
{
this._results[i].index = indexes[i - 1];
}
this.paste(x, y, this._results, layer);
},
/**
* Fills the given area with the specified tile.
* Only the tile indexes are modified.
*
* @method Phaser.Tilemap#fill
* @param {number} index - The index of the tile that the area will be filled with.
* @param {number} x - X position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} y - Y position of the top left of the area to operate one, given in tiles, not pixels.
* @param {number} width - The width in tiles of the area to operate on.
* @param {number} height - The height in tiles of the area to operate on.
* @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on.
*/
fill: function (index, x, y, width, height, layer)
{
layer = this.getLayer(layer);
this.copy(x, y, width, height, layer);
if (this._results.length < 2)
{
return;
}
for (var i = 1; i < this._results.length; i++)
{
this._results[i].index = index;
}
this.paste(x, y, this._results, layer);
},
/**
* Removes all layers from this tile map.
*
* @method Phaser.Tilemap#removeAllLayers
*/
removeAllLayers: function ()
{
this.layers.length = 0;
this.currentLayer = 0;
},
/**
* Dumps the tilemap data out to the console.
*
* @method Phaser.Tilemap#dump
*/
dump: function ()
{
var txt = '';
var args = [ '' ];
for (var y = 0; y < this.layers[this.currentLayer].height; y++)
{
for (var x = 0; x < this.layers[this.currentLayer].width; x++)
{
txt += '%c ';
if (this.layers[this.currentLayer].data[y][x] > 1)
{
if (this.debugMap[this.layers[this.currentLayer].data[y][x]])
{
args.push('background: ' + this.debugMap[this.layers[this.currentLayer].data[y][x]]);
}
else
{
args.push('background: #ffffff');
}
}
else
{
args.push('background: rgb(0, 0, 0)');
}
}
txt += '\n';
}
args[0] = txt;
console.log.apply(console, args);
},
/**
* Removes all layer data from this tile map and nulls the game reference.
* Note: You are responsible for destroying any TilemapLayer objects you generated yourself, as Tilemap doesn't keep a reference to them.
*
* @method Phaser.Tilemap#destroy
*/
destroy: function ()
{
this.removeAllLayers();
this.data = [];
this.game = null;
}
};
Phaser.Tilemap.prototype.constructor = Phaser.Tilemap;
/**
* @name Phaser.Tilemap#layer
* @property {number|string|Phaser.TilemapLayer} layer - The current layer object.
*/
Object.defineProperty(Phaser.Tilemap.prototype, 'layer', {
get: function ()
{
return this.layers[this.currentLayer];
},
set: function (value)
{
if (value !== this.currentLayer)
{
this.setLayer(value);
}
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A TilemapLayer is a Phaser.Image/Sprite that renders a specific TileLayer of a Tilemap.
*
* Since a TilemapLayer is a Sprite it can be moved around the display, added to other groups or display objects, etc.
*
* By default TilemapLayers have fixedToCamera set to `true`. Changing this will break Camera follow and scrolling behavior.
*
* @class Phaser.TilemapLayer
* @extends Phaser.Sprite
* @constructor
* @param {Phaser.Game} game - Game reference to the currently running game.
* @param {Phaser.Tilemap} tilemap - The tilemap to which this layer belongs.
* @param {integer} index - The index of the TileLayer to render within the Tilemap.
* @param {integer} width - Width of the renderable area of the layer (in pixels).
* @param {integer} height - Height of the renderable area of the layer (in pixels).
*/
Phaser.TilemapLayer = function (game, tilemap, index, width, height)
{
width |= 0;
height |= 0;
Phaser.Sprite.call(this, game, 0, 0);
/**
* The Tilemap to which this layer is bound.
* @property {Phaser.Tilemap} map
* @protected
* @readonly
*/
this.map = tilemap;
/**
* The index of this layer within the Tilemap.
* @property {number} index
* @protected
* @readonly
*/
this.index = index;
/**
* The layer object within the Tilemap that this layer represents.
* @property {object} layer
* @protected
* @readonly
*/
this.layer = tilemap.layers[index];
/**
* The canvas to which this TilemapLayer draws.
* @property {HTMLCanvasElement} canvas
* @protected
*/
this.canvas = Phaser.CanvasPool.create(this, width, height);
/**
* The 2d context of the canvas.
* @property {CanvasRenderingContext2D} context
* @private
*/
this.context = this.canvas.getContext('2d');
this.setTexture(new PIXI.Texture(new PIXI.BaseTexture(this.canvas, null, this.game.resolution)));
/**
* The const type of this object.
* @property {number} type
* @readonly
* @protected
* @default Phaser.TILEMAPLAYER
*/
this.type = Phaser.TILEMAPLAYER;
/**
* @property {number} physicsType - The const physics body type of this object.
* @readonly
*/
this.physicsType = Phaser.TILEMAPLAYER;
/**
* Settings that control standard (non-diagnostic) rendering.
*
* @property {boolean} [enableScrollDelta=true] - Delta scroll rendering only draws tiles/edges as they come into view.
* This can greatly improve scrolling rendering performance, especially when there are many small tiles.
* It should only be disabled in rare cases.
*
* @property {?DOMCanvasElement} [copyCanvas=(auto)] - [Internal] If set, force using a separate (shared) copy canvas.
* Using a canvas bitblt/copy when the source and destinations region overlap produces unexpected behavior
* in some browsers, notably Safari.
*
* @default
*/
this.renderSettings = {
enableScrollDelta: true,
overdrawRatio: 0.20,
copyCanvas: null
};
/**
* Enable an additional "debug rendering" pass to display collision information.
*
* @property {boolean} debug
* @default
*/
this.debug = false;
/**
* @property {boolean} exists - Controls if the core game loop and physics update this game object or not.
*/
this.exists = true;
/**
* Settings used for debugging and diagnostics.
*
* @property {?string} missingImageFill - A tile is rendered as a rectangle using the following fill if a valid tileset/image cannot be found. A value of `null` prevents additional rendering for tiles without a valid tileset image. _This takes effect even when debug rendering for the layer is not enabled._
*
* @property {?string} debuggedTileOverfill - If a Tile has `Tile#debug` true then, after normal tile image rendering, a rectangle with the following fill is drawn above/over it. _This takes effect even when debug rendering for the layer is not enabled._
*
* @property {boolean} forceFullRedraw - When debug rendering (`debug` is true), and this option is enabled, the a full redraw is forced and rendering optimization is suppressed.
*
* @property {number} debugAlpha - When debug rendering (`debug` is true), the tileset is initially rendered with this alpha level. This can make the tile edges clearer.
*
* @property {?string} facingEdgeStroke - When debug rendering (`debug` is true), this color/stroke is used to draw "face" edges. A value of `null` disables coloring facing edges.
*
* @property {?string} collidingTileOverfill - When debug rendering (`debug` is true), this fill is used for tiles that are collidable. A value of `null` disables applying the additional overfill.
*
*/
this.debugSettings = {
missingImageFill: 'rgb(255,255,255)',
debuggedTileOverfill: 'rgba(0,255,0,0.4)',
forceFullRedraw: true,
debugAlpha: 0.5,
facingEdgeStroke: 'rgba(0,255,0,1)',
collidingTileOverfill: 'rgba(0,255,0,0.2)'
};
/**
* Speed at which this layer scrolls horizontally, relative to the camera (e.g. scrollFactorX of 0.5 scrolls half as quickly as the 'normal' camera-locked layers do).
* @property {number} scrollFactorX
* @public
* @default
*/
this.scrollFactorX = 1;
/**
* Speed at which this layer scrolls vertically, relative to the camera (e.g. scrollFactorY of 0.5 scrolls half as quickly as the 'normal' camera-locked layers do)
* @property {number} scrollFactorY
* @public
* @default
*/
this.scrollFactorY = 1;
/**
* If true tiles will be force rendered, even if such is not believed to be required.
* @property {boolean} dirty
* @protected
*/
this.dirty = true;
/**
* When ray-casting against tiles this is the number of steps it will jump. For larger tile sizes you can increase this to improve performance.
* @property {integer} rayStepRate
* @default
*/
this.rayStepRate = 4;
/**
* Flag controlling if the layer tiles wrap at the edges.
* @property {boolean} _wrap
* @private
*/
this._wrap = false;
/**
* Local map data and calculation cache.
* @property {object} _mc
* @private
*/
this._mc = {
// Used to bypass rendering without reliance on `dirty` and detect changes.
scrollX: 0,
scrollY: 0,
renderWidth: 0,
renderHeight: 0,
tileWidth: tilemap.tileWidth,
tileHeight: tilemap.tileHeight,
// Collision width/height (pixels)
// What purpose do these have? Most things use tile width/height directly.
// This also only extends collisions right and down.
cw: tilemap.tileWidth,
ch: tilemap.tileHeight,
// Cached tilesets from index -> Tileset
tilesets: []
};
/**
* The current canvas left after scroll is applied.
* @property {number} _scrollX
* @private
*/
this._scrollX = 0;
/**
* The current canvas top after scroll is applied.
* @property {number} _scrollY
* @private
*/
this._scrollY = 0;
/**
* The position offset of the layer's tiles.
* @property {Phaser.Point}
*/
this.tileOffset = new Phaser.Point(this.layer.offsetX || 0, this.layer.offsetY || 0);
/**
* Used for caching the tiles / array of tiles.
* @property {Phaser.Tile[]} _results
* @private
*/
this._results = [];
if (!game.device.canvasBitBltShift)
{
this.renderSettings.copyCanvas = Phaser.TilemapLayer.ensureSharedCopyCanvas();
}
this.fixedToCamera = true;
};
Phaser.TilemapLayer.prototype = Object.create(Phaser.Sprite.prototype);
Phaser.TilemapLayer.prototype.constructor = Phaser.TilemapLayer;
Phaser.TilemapLayer.prototype.preUpdateCore = Phaser.Component.Core.preUpdate;
/**
* The shared double-copy canvas, created as needed.
*
* @private
* @static
*/
Phaser.TilemapLayer.sharedCopyCanvas = null;
/**
* Create if needed (and return) a shared copy canvas that is shared across all TilemapLayers.
*
* Code that uses the canvas is responsible to ensure the dimensions and save/restore state as appropriate.
*
* @method Phaser.TilemapLayer#ensureSharedCopyCanvas
* @protected
* @static
*/
Phaser.TilemapLayer.ensureSharedCopyCanvas = function ()
{
if (!this.sharedCopyCanvas)
{
this.sharedCopyCanvas = Phaser.CanvasPool.create(this, 2, 2);
}
return this.sharedCopyCanvas;
};
/**
* Automatically called by World.preUpdate.
*
* @method Phaser.TilemapLayer#preUpdate
*/
Phaser.TilemapLayer.prototype.preUpdate = function ()
{
return this.preUpdateCore();
};
/**
* Automatically called by World.postUpdate. Handles cache updates.
*
* @method Phaser.TilemapLayer#postUpdate
* @protected
*/
Phaser.TilemapLayer.prototype.postUpdate = function ()
{
if (this.fixedToCamera)
{
this.position.x = (this.game.camera.view.x + this.cameraOffset.x) / this.game.camera.scale.x;
this.position.y = (this.game.camera.view.y + this.cameraOffset.y) / this.game.camera.scale.y;
}
this._scrollX = (this.game.camera.view.x - this.tileOffset.x) * this.scrollFactorX / this.scale.x;
this._scrollY = (this.game.camera.view.y - this.tileOffset.y) * this.scrollFactorY / this.scale.y;
};
/**
* Automatically called by the Canvas Renderer.
* Overrides the Sprite._renderCanvas function.
*
* @method Phaser.TilemapLayer#_renderCanvas
* @private
*/
Phaser.TilemapLayer.prototype._renderCanvas = function (renderSession)
{
if (this.fixedToCamera)
{
this.position.x = (this.game.camera.view.x + this.cameraOffset.x) / this.game.camera.scale.x;
this.position.y = (this.game.camera.view.y + this.cameraOffset.y) / this.game.camera.scale.y;
}
this._scrollX = (this.game.camera.view.x - this.tileOffset.x) * this.scrollFactorX / this.scale.x;
this._scrollY = (this.game.camera.view.y - this.tileOffset.y) * this.scrollFactorY / this.scale.y;
this.render();
PIXI.Sprite.prototype._renderCanvas.call(this, renderSession);
};
/**
* Automatically called by the Canvas Renderer.
* Overrides the Sprite._renderWebGL function.
*
* @method Phaser.TilemapLayer#_renderWebGL
* @private
*/
Phaser.TilemapLayer.prototype._renderWebGL = function (renderSession)
{
if (this.fixedToCamera)
{
this.position.x = (this.game.camera.view.x + this.cameraOffset.x) / this.game.camera.scale.x;
this.position.y = (this.game.camera.view.y + this.cameraOffset.y) / this.game.camera.scale.y;
}
this._scrollX = (this.game.camera.view.x - this.tileOffset.x) * this.scrollFactorX / this.scale.x;
this._scrollY = (this.game.camera.view.y - this.tileOffset.y) * this.scrollFactorY / this.scale.y;
this.render();
PIXI.Sprite.prototype._renderWebGL.call(this, renderSession);
};
/**
* Destroys this TilemapLayer.
*
* @method Phaser.TilemapLayer#destroy
*/
Phaser.TilemapLayer.prototype.destroy = function ()
{
Phaser.CanvasPool.remove(this);
Phaser.Component.Destroy.prototype.destroy.call(this);
};
/**
* Resizes the internal canvas and texture frame used by this TilemapLayer.
*
* This is an expensive call, so don't bind it to a window resize event! But instead call it at carefully
* selected times.
*
* Be aware that no validation of the new sizes takes place and the current map scroll coordinates are not
* modified either. You will have to handle both of these things from your game code if required.
*
* @method Phaser.TilemapLayer#resize
* @param {number} width - The new width of the TilemapLayer
* @param {number} height - The new height of the TilemapLayer
*/
Phaser.TilemapLayer.prototype.resize = function (width, height)
{
this.canvas.width = width;
this.canvas.height = height;
this.texture.frame.resize(width, height);
this.texture.width = width;
this.texture.height = height;
this.texture.crop.width = width;
this.texture.crop.height = height;
this.texture.baseTexture.width = width;
this.texture.baseTexture.height = height;
this.texture.baseTexture.dirty();
this.texture.requiresUpdate = true;
this.texture._updateUvs();
this.dirty = true;
};
/**
* Sets the world size to match the size of this layer.
*
* @method Phaser.TilemapLayer#resizeWorld
* @public
*/
Phaser.TilemapLayer.prototype.resizeWorld = function ()
{
this.game.world.setBounds(0, 0, this.layer.widthInPixels * this.scale.x, this.layer.heightInPixels * this.scale.y);
};
/**
* Get the X axis position offset of this layer's tiles.
*
* @method Phaser.TilemapLayer#getLayerOffsetX
* @public
* @return {number}
*/
Phaser.TilemapLayer.prototype.getTileOffsetX = function ()
{
return this.tileOffset.x || ((!this.fixedToCamera) ? this.position.x : 0);
};
/**
* Get the Y axis position offset of this layer's tiles.
*
* @method Phaser.TilemapLayer#getTileOffsetY
* @public
* @return {number}
*/
Phaser.TilemapLayer.prototype.getTileOffsetY = function ()
{
return this.tileOffset.y || ((!this.fixedToCamera) ? this.position.y : 0);
};
/**
* Take an x coordinate that doesn't account for scrollFactorX and 'fix' it into a scrolled local space.
*
* @method Phaser.TilemapLayer#_fixX
* @private
* @param {number} x - x coordinate in camera space
* @return {number} x coordinate in scrollFactor-adjusted dimensions
*/
Phaser.TilemapLayer.prototype._fixX = function (x)
{
if (this.scrollFactorX === 1 || (this.scrollFactorX === 0 && this.position.x === 0))
{
return x;
}
// This executes if the scrollFactorX is 0 and the x position of the tilemap is off from standard.
if (this.scrollFactorX === 0 && this.position.x !== 0)
{
return x - this.position.x;
}
return this._scrollX + (x - (this._scrollX / this.scrollFactorX));
};
/**
* Take an x coordinate that _does_ account for scrollFactorX and 'unfix' it back to camera space.
*
* @method Phaser.TilemapLayer#_unfixX
* @private
* @param {number} x - x coordinate in scrollFactor-adjusted dimensions
* @return {number} x coordinate in camera space
*/
Phaser.TilemapLayer.prototype._unfixX = function (x)
{
if (this.scrollFactorX === 1)
{
return x;
}
return (this._scrollX / this.scrollFactorX) + (x - this._scrollX);
};
/**
* Take a y coordinate that doesn't account for scrollFactorY and 'fix' it into a scrolled local space.
*
* @method Phaser.TilemapLayer#_fixY
* @private
* @param {number} y - y coordinate in camera space
* @return {number} y coordinate in scrollFactor-adjusted dimensions
*/
Phaser.TilemapLayer.prototype._fixY = function (y)
{
if (this.scrollFactorY === 1 || (this.scrollFactorY === 0 && this.position.y === 0))
{
return y;
}
// This executes if the scrollFactorY is 0 and the y position of the tilemap is off from standard.
if (this.scrollFactorY === 0 && this.position.y !== 0)
{
return y - this.position.y;
}
return this._scrollY + (y - (this._scrollY / this.scrollFactorY));
};
/**
* Take a y coordinate that _does_ account for scrollFactorY and 'unfix' it back to camera space.
*
* @method Phaser.TilemapLayer#_unfixY
* @private
* @param {number} y - y coordinate in scrollFactor-adjusted dimensions
* @return {number} y coordinate in camera space
*/
Phaser.TilemapLayer.prototype._unfixY = function (y)
{
if (this.scrollFactorY === 1)
{
return y;
}
return (this._scrollY / this.scrollFactorY) + (y - this._scrollY);
};
/**
* Convert a pixel value to a tile coordinate.
*
* @method Phaser.TilemapLayer#getTileX
* @public
* @param {number} x - X position of the point in target tile (in pixels).
* @return {integer} The X map location of the tile.
*/
Phaser.TilemapLayer.prototype.getTileX = function (x)
{
// var tileWidth = this.tileWidth * this.scale.x;
return Math.floor(this._fixX(x) / this._mc.tileWidth);
};
/**
* Convert a pixel value to a tile coordinate.
*
* @method Phaser.TilemapLayer#getTileY
* @public
* @param {number} y - Y position of the point in target tile (in pixels).
* @return {integer} The Y map location of the tile.
*/
Phaser.TilemapLayer.prototype.getTileY = function (y)
{
// var tileHeight = this.tileHeight * this.scale.y;
return Math.floor(this._fixY(y) / this._mc.tileHeight);
};
/**
* Convert a pixel coordinate to a tile coordinate.
*
* @method Phaser.TilemapLayer#getTileXY
* @public
* @param {number} x - X position of the point in target tile (in pixels).
* @param {number} y - Y position of the point in target tile (in pixels).
* @param {(Phaser.Point|object)} point - The Point/object to update.
* @return {(Phaser.Point|object)} A Point/object with its `x` and `y` properties set.
*/
Phaser.TilemapLayer.prototype.getTileXY = function (x, y, point)
{
point.x = this.getTileX(x);
point.y = this.getTileY(y);
return point;
};
/**
* Gets all tiles that intersect with the given line.
*
* @method Phaser.TilemapLayer#getRayCastTiles
* @public
* @param {Phaser.Line} line - The line used to determine which tiles to return.
* @param {integer} [stepRate=(rayStepRate)] - How many steps through the ray will we check? Defaults to `rayStepRate`.
* @param {boolean} [collides=false] - If true, _only_ return tiles that collide on one or more faces.
* @param {boolean} [interestingFace=false] - If true, _only_ return tiles that have interesting faces.
* @return {Phaser.Tile[]} An array of Phaser.Tiles.
*/
Phaser.TilemapLayer.prototype.getRayCastTiles = function (line, stepRate, collides, interestingFace)
{
if (!stepRate) { stepRate = this.rayStepRate; }
if (collides === undefined) { collides = false; }
if (interestingFace === undefined) { interestingFace = false; }
// First get all tiles that touch the bounds of the line
var tiles = this.getTiles(line.x, line.y, line.width, line.height, collides, interestingFace);
if (tiles.length === 0)
{
return [];
}
// Now we only want the tiles that intersect with the points on this line
var coords = line.coordinatesOnLine(stepRate);
var results = [];
for (var i = 0; i < tiles.length; i++)
{
for (var t = 0; t < coords.length; t++)
{
var tile = tiles[i];
var coord = coords[t];
if (tile.containsPoint(coord[0], coord[1]))
{
results.push(tile);
break;
}
}
}
return results;
};
/**
* Get all tiles that exist within the given area, defined by the top-left corner, width and height. Values given are in pixels, not tiles.
*
* @method Phaser.TilemapLayer#getTiles
* @public
* @param {number} x - X position of the top left corner (in pixels).
* @param {number} y - Y position of the top left corner (in pixels).
* @param {number} width - Width of the area to get (in pixels).
* @param {number} height - Height of the area to get (in pixels).
* @param {boolean} [collides=false] - If true, _only_ return tiles that collide on one or more faces.
* @param {boolean} [interestingFace=false] - If true, _only_ return tiles that have interesting faces.
* @return {array} An array of Tiles.
*/
Phaser.TilemapLayer.prototype.getTiles = function (x, y, width, height, collides, interestingFace)
{
// Should we only get tiles that have at least one of their collision flags set? (true = yes, false = no just get them all)
if (collides === undefined) { collides = false; }
if (interestingFace === undefined) { interestingFace = false; }
var fetchAll = !(collides || interestingFace);
// Adjust the x,y coordinates for scrollFactor
x = this._fixX(x);
y = this._fixY(y);
// Convert the pixel values into tile coordinates
var tx = Math.floor(x / (this._mc.cw * this.scale.x));
var ty = Math.floor(y / (this._mc.ch * this.scale.y));
// Don't just use ceil(width/cw) to allow account for x/y diff within cell
var tw = Math.ceil((x + width) / (this._mc.cw * this.scale.x)) - tx;
var th = Math.ceil((y + height) / (this._mc.ch * this.scale.y)) - ty;
while (this._results.length)
{
this._results.pop();
}
for (var wy = ty; wy < ty + th; wy++)
{
for (var wx = tx; wx < tx + tw; wx++)
{
var row = this.layer.data[wy];
if (row && row[wx])
{
if (fetchAll || row[wx].isInteresting(collides, interestingFace))
{
this._results.push(row[wx]);
}
}
}
}
return this._results.slice();
};
/**
* Returns the appropriate tileset for the index, updating the internal cache as required.
* This should only be called if `tilesets[index]` evaluates to undefined.
*
* @method Phaser.TilemapLayer#resolveTileset
* @private
* @param {integer} Tile index
* @return {Phaser.Tileset|null} Returns the associated tileset or null if there is no such mapping.
*/
Phaser.TilemapLayer.prototype.resolveTileset = function (tileIndex)
{
var tilesets = this._mc.tilesets;
// Try for dense array if reasonable
if (tileIndex < 2000)
{
while (tilesets.length < tileIndex)
{
tilesets.push(undefined);
}
}
var setIndex = this.map.tiles[tileIndex] && this.map.tiles[tileIndex][2];
if (setIndex !== null)
{
var tileset = this.map.tilesets[setIndex];
if (tileset && tileset.containsTileIndex(tileIndex))
{
return (tilesets[tileIndex] = tileset);
}
}
return (tilesets[tileIndex] = null);
};
/**
* The TilemapLayer caches tileset look-ups.
*
* Call this method of clear the cache if tilesets have been added or updated after the layer has been rendered.
*
* @method Phaser.TilemapLayer#resetTilesetCache
* @public
*/
Phaser.TilemapLayer.prototype.resetTilesetCache = function ()
{
var tilesets = this._mc.tilesets;
while (tilesets.length)
{
tilesets.pop();
}
};
/**
* This method will set the scale of the tilemap as well as update the underlying block data of this layer.
*
* @method Phaser.TilemapLayer#setScale
* @param {number} [xScale=1] - The scale factor along the X-plane
* @param {number} [yScale] - The scale factor along the Y-plane
*/
Phaser.TilemapLayer.prototype.setScale = function (xScale, yScale)
{
xScale = xScale || 1;
yScale = yScale || xScale;
for (var y = 0; y < this.layer.data.length; y++)
{
var row = this.layer.data[y];
for (var x = 0; x < row.length; x++)
{
var tile = row[x];
tile.width = this.map.tileWidth * xScale;
tile.height = this.map.tileHeight * yScale;
tile.worldX = tile.x * tile.width;
tile.worldY = tile.y * tile.height;
}
}
this.scale.setTo(xScale, yScale);
};
/**
* Shifts the contents of the canvas - does extra math so that different browsers agree on the result.
*
* The specified (x/y) will be shifted to (0,0) after the copy and the newly exposed canvas area will need to be filled in.
*
* @method Phaser.TilemapLayer#shiftCanvas
* @private
* @param {CanvasRenderingContext2D} context - The context to shift
* @param {integer} x
* @param {integer} y
*/
Phaser.TilemapLayer.prototype.shiftCanvas = function (context, x, y)
{
var canvas = context.canvas;
var copyW = canvas.width - Math.abs(x);
var copyH = canvas.height - Math.abs(y);
// When x/y non-negative
var dx = 0;
var dy = 0;
var sx = x;
var sy = y;
if (x < 0)
{
dx = -x;
sx = 0;
}
if (y < 0)
{
dy = -y;
sy = 0;
}
var copyCanvas = this.renderSettings.copyCanvas;
if (copyCanvas)
{
// Use a second copy buffer, without slice support, for Safari .. again.
// Ensure copy canvas is large enough
if (copyCanvas.width < copyW || copyCanvas.height < copyH)
{
copyCanvas.width = copyW;
copyCanvas.height = copyH;
}
var copyContext = copyCanvas.getContext('2d');
copyContext.clearRect(0, 0, copyW, copyH);
copyContext.drawImage(canvas, dx, dy, copyW, copyH, 0, 0, copyW, copyH);
// clear allows default 'source-over' semantics
context.clearRect(sx, sy, copyW, copyH);
context.drawImage(copyCanvas, 0, 0, copyW, copyH, sx, sy, copyW, copyH);
}
else
{
// Avoids a second copy but flickers in Safari / Safari Mobile
// Ref. https://github.com/photonstorm/phaser/issues/1439
context.save();
context.globalCompositeOperation = 'copy';
context.drawImage(canvas, dx, dy, copyW, copyH, sx, sy, copyW, copyH);
context.restore();
}
};
/**
* Render tiles in the given area given by the virtual tile coordinates biased by the given scroll factor.
* This will constrain the tile coordinates based on wrapping but not physical coordinates.
*
* @method Phaser.TilemapLayer#renderRegion
* @private
* @param {integer} scrollX - Render x offset/scroll.
* @param {integer} scrollY - Render y offset/scroll.
* @param {integer} left - Leftmost column to render.
* @param {integer} top - Topmost row to render.
* @param {integer} right - Rightmost column to render.
* @param {integer} bottom - Bottommost row to render.
*/
Phaser.TilemapLayer.prototype.renderRegion = function (scrollX, scrollY, left, top, right, bottom)
{
var context = this.context;
var width = this.layer.width;
var height = this.layer.height;
var tw = this._mc.tileWidth;
var th = this._mc.tileHeight;
var tilesets = this._mc.tilesets;
var lastAlpha = NaN;
if (!this._wrap)
{
if (left <= right) // Only adjust if going to render
{
left = Math.max(0, left);
right = Math.min(width - 1, right);
}
if (top <= bottom)
{
top = Math.max(0, top);
bottom = Math.min(height - 1, bottom);
}
}
// top-left pixel of top-left cell
var baseX = (left * tw) - scrollX;
var baseY = (top * th) - scrollY;
// Fix normStartX/normStartY such it is normalized [0..width/height). This allows a simple conditional and decrement to always keep in range [0..width/height) during the loop. The major offset bias is to take care of negative values.
var normStartX = (left + ((1 << 20) * width)) % width;
var normStartY = (top + ((1 << 20) * height)) % height;
// tx/ty - are pixel coordinates where tile is drawn
// x/y - is cell location, normalized [0..width/height) in loop
// xmax/ymax - remaining cells to render on column/row
var tx, ty, x, y, xmax, ymax;
for (y = normStartY, ymax = bottom - top, ty = baseY; ymax >= 0; y++, ymax--, ty += th)
{
if (y >= height)
{
y -= height;
}
var row = this.layer.data[y];
for (x = normStartX, xmax = right - left, tx = baseX; xmax >= 0; x++, xmax--, tx += tw)
{
if (x >= width)
{
x -= width;
}
var tile = row[x];
if (!tile || tile.index < 0)
{
continue;
}
var index = tile.index;
var set = tilesets[index];
if (set === undefined)
{
set = this.resolveTileset(index);
}
// Setting the globalAlpha is "surprisingly expensive" in Chrome (38)
if (tile.alpha !== lastAlpha && !this.debug)
{
context.globalAlpha = tile.alpha;
lastAlpha = tile.alpha;
}
if (set)
{
if (tile.rotation || tile.flipped)
{
context.save();
context.translate(tx + tile.centerX, ty + tile.centerY);
context.rotate(tile.rotation);
if (tile.flipped)
{
context.scale(-1, 1);
}
set.draw(context, -tile.centerX, -tile.centerY, index);
context.restore();
}
else
{
set.draw(context, tx, ty, index);
}
}
else if (this.debugSettings.missingImageFill)
{
context.fillStyle = this.debugSettings.missingImageFill;
context.fillRect(tx, ty, tw, th);
}
if (tile.debug && this.debugSettings.debuggedTileOverfill)
{
context.fillStyle = this.debugSettings.debuggedTileOverfill;
context.fillRect(tx, ty, tw, th);
}
}
}
};
/**
* Shifts the canvas and render damaged edge tiles.
*
* @method Phaser.TilemapLayer#renderDeltaScroll
* @private
*/
Phaser.TilemapLayer.prototype.renderDeltaScroll = function (shiftX, shiftY)
{
var scrollX = this._mc.scrollX;
var scrollY = this._mc.scrollY;
var renderW = this.canvas.width;
var renderH = this.canvas.height;
var tw = this._mc.tileWidth;
var th = this._mc.tileHeight;
// Only cells with coordinates in the "plus" formed by `left <= x <= right` OR `top <= y <= bottom` are drawn. These coordinates may be outside the layer bounds.
// Start in pixels
var left = 0;
var right = -tw;
var top = 0;
var bottom = -th;
if (shiftX < 0) // layer moving left, damage right
{
left = renderW + shiftX; // shiftX neg.
right = renderW - 1;
}
else if (shiftX > 0)
{
// left -> 0
right = shiftX;
}
if (shiftY < 0) // layer moving down, damage top
{
top = renderH + shiftY; // shiftY neg.
bottom = renderH - 1;
}
else if (shiftY > 0)
{
// top -> 0
bottom = shiftY;
}
this.shiftCanvas(this.context, shiftX, shiftY);
// Transform into tile-space
left = Math.floor((left + scrollX) / tw);
right = Math.floor((right + scrollX) / tw);
top = Math.floor((top + scrollY) / th);
bottom = Math.floor((bottom + scrollY) / th);
if (left <= right)
{
// Clear left or right edge
this.context.clearRect(((left * tw) - scrollX), 0, (right - left + 1) * tw, renderH);
var trueTop = Math.floor((0 + scrollY) / th);
var trueBottom = Math.floor((renderH - 1 + scrollY) / th);
this.renderRegion(scrollX, scrollY, left, trueTop, right, trueBottom);
}
if (top <= bottom)
{
// Clear top or bottom edge
this.context.clearRect(0, ((top * th) - scrollY), renderW, (bottom - top + 1) * th);
var trueLeft = Math.floor((0 + scrollX) / tw);
var trueRight = Math.floor((renderW - 1 + scrollX) / tw);
this.renderRegion(scrollX, scrollY, trueLeft, top, trueRight, bottom);
}
};
/**
* Clear and render the entire canvas.
*
* @method Phaser.TilemapLayer#renderFull
* @private
*/
Phaser.TilemapLayer.prototype.renderFull = function ()
{
var scrollX = this._mc.scrollX;
var scrollY = this._mc.scrollY;
var renderW = this.canvas.width;
var renderH = this.canvas.height;
var tw = this._mc.tileWidth;
var th = this._mc.tileHeight;
var left = Math.floor(scrollX / tw);
var right = Math.floor((renderW - 1 + scrollX) / tw);
var top = Math.floor(scrollY / th);
var bottom = Math.floor((renderH - 1 + scrollY) / th);
this.context.clearRect(0, 0, renderW, renderH);
this.renderRegion(scrollX, scrollY, left, top, right, bottom);
};
/**
* Renders the tiles to the layer canvas and pushes to the display.
*
* @method Phaser.TilemapLayer#render
* @protected
*/
Phaser.TilemapLayer.prototype.render = function ()
{
var redrawAll = false;
if (!this.visible)
{
return;
}
if (this.dirty || this.layer.dirty)
{
this.layer.dirty = false;
redrawAll = true;
}
var renderWidth = this.canvas.width; // Use Sprite.width/height?
var renderHeight = this.canvas.height;
// Scrolling bias; whole pixels only
var scrollX = this._scrollX | 0;
var scrollY = this._scrollY | 0;
var mc = this._mc;
var shiftX = mc.scrollX - scrollX; // Negative when scrolling right/down
var shiftY = mc.scrollY - scrollY;
if (!redrawAll &&
shiftX === 0 && shiftY === 0 &&
mc.renderWidth === renderWidth && mc.renderHeight === renderHeight)
{
// No reason to redraw map, looking at same thing and not invalidated.
return;
}
this.context.save();
mc.scrollX = scrollX;
mc.scrollY = scrollY;
if (mc.renderWidth !== renderWidth || mc.renderHeight !== renderHeight)
{
// Could support automatic canvas resizing
mc.renderWidth = renderWidth;
mc.renderHeight = renderHeight;
}
if (this.debug)
{
this.context.globalAlpha = this.debugSettings.debugAlpha;
if (this.debugSettings.forceFullRedraw)
{
redrawAll = true;
}
}
if (!redrawAll &&
this.renderSettings.enableScrollDelta &&
(Math.abs(shiftX) + Math.abs(shiftY)) < Math.min(renderWidth, renderHeight))
{
this.renderDeltaScroll(shiftX, shiftY);
}
else
{
// Too much change or otherwise requires full render
this.renderFull();
}
if (this.debug)
{
this.context.globalAlpha = 1;
this.renderDebug();
}
this.texture.baseTexture.dirty();
this.dirty = false;
this.context.restore();
return true;
};
/**
* Renders a debug overlay on-top of the canvas. Called automatically by render when `debug` is true.
*
* See `debugSettings` for assorted configuration options.
*
* @method Phaser.TilemapLayer#renderDebug
* @private
*/
Phaser.TilemapLayer.prototype.renderDebug = function ()
{
var scrollX = this._mc.scrollX;
var scrollY = this._mc.scrollY;
var context = this.context;
var renderW = this.canvas.width;
var renderH = this.canvas.height;
var width = this.layer.width;
var height = this.layer.height;
var tw = this._mc.tileWidth;
var th = this._mc.tileHeight;
var left = Math.floor(scrollX / tw);
var right = Math.floor((renderW - 1 + scrollX) / tw);
var top = Math.floor(scrollY / th);
var bottom = Math.floor((renderH - 1 + scrollY) / th);
var baseX = (left * tw) - scrollX;
var baseY = (top * th) - scrollY;
var normStartX = (left + ((1 << 20) * width)) % width;
var normStartY = (top + ((1 << 20) * height)) % height;
var tx, ty, x, y, xmax, ymax;
context.strokeStyle = this.debugSettings.facingEdgeStroke;
for (y = normStartY, ymax = bottom - top, ty = baseY; ymax >= 0; y++, ymax--, ty += th)
{
if (y >= height)
{
y -= height;
}
var row = this.layer.data[y];
for (x = normStartX, xmax = right - left, tx = baseX; xmax >= 0; x++, xmax--, tx += tw)
{
if (x >= width)
{
x -= width;
}
var tile = row[x];
if (!tile || tile.index < 0 || !tile.collides)
{
continue;
}
if (this.debugSettings.collidingTileOverfill)
{
context.fillStyle = this.debugSettings.collidingTileOverfill;
context.fillRect(tx, ty, this._mc.cw, this._mc.ch);
}
if (this.debugSettings.facingEdgeStroke)
{
context.beginPath();
if (tile.faceTop)
{
context.moveTo(tx, ty);
context.lineTo(tx + this._mc.cw, ty);
}
if (tile.faceBottom)
{
context.moveTo(tx, ty + this._mc.ch);
context.lineTo(tx + this._mc.cw, ty + this._mc.ch);
}
if (tile.faceLeft)
{
context.moveTo(tx, ty);
context.lineTo(tx, ty + this._mc.ch);
}
if (tile.faceRight)
{
context.moveTo(tx + this._mc.cw, ty);
context.lineTo(tx + this._mc.cw, ty + this._mc.ch);
}
context.closePath();
context.stroke();
}
}
}
};
/**
* Flag controlling if the layer tiles wrap at the edges. Only works if the World size matches the Map size.
*
* @property {boolean} wrap
* @memberof Phaser.TilemapLayer
* @public
* @default false
*/
Object.defineProperty(Phaser.TilemapLayer.prototype, 'wrap', {
get: function ()
{
return this._wrap;
},
set: function (value)
{
this._wrap = value;
this.dirty = true;
}
});
/**
* Scrolls the map horizontally or returns the current x position.
*
* @property {number} scrollX
* @memberof Phaser.TilemapLayer
* @public
*/
Object.defineProperty(Phaser.TilemapLayer.prototype, 'scrollX', {
get: function ()
{
return this._scrollX;
},
set: function (value)
{
this._scrollX = value;
}
});
/**
* Scrolls the map vertically or returns the current y position.
*
* @property {number} scrollY
* @memberof Phaser.TilemapLayer
* @public
*/
Object.defineProperty(Phaser.TilemapLayer.prototype, 'scrollY', {
get: function ()
{
return this._scrollY;
},
set: function (value)
{
this._scrollY = value;
}
});
/**
* The width of the collision tiles (in pixels).
*
* @property {integer} collisionWidth
* @memberof Phaser.TilemapLayer
* @public
*/
Object.defineProperty(Phaser.TilemapLayer.prototype, 'collisionWidth', {
get: function ()
{
return this._mc.cw;
},
set: function (value)
{
this._mc.cw = value | 0;
this.dirty = true;
}
});
/**
* The height of the collision tiles (in pixels).
*
* @property {integer} collisionHeight
* @memberof Phaser.TilemapLayer
* @public
*/
Object.defineProperty(Phaser.TilemapLayer.prototype, 'collisionHeight', {
get: function ()
{
return this._mc.ch;
},
set: function (value)
{
this._mc.ch = value | 0;
this.dirty = true;
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Phaser.TilemapParser parses data objects from Phaser.Loader that need more preparation before they can be inserted into a Tilemap.
*
* @class Phaser.TilemapParser
* @static
*/
Phaser.TilemapParser = {
/**
* When scanning the Tiled map data the TilemapParser can either insert a null value (true) or
* a Phaser.Tile instance with an index of -1 (false, the default). Depending on your game type
* depends how this should be configured. If you've a large sparsely populated map and the tile
* data doesn't need to change then setting this value to `true` will help with memory consumption.
* However if your map is small, or you need to update the tiles (perhaps the map dynamically changes
* during the game) then leave the default value set.
*
* @constant
* @type {boolean}
*/
INSERT_NULL: false,
/**
* Parse tilemap data from the cache and creates data for a Tilemap object.
*
* @method Phaser.TilemapParser.parse
* @param {Phaser.Game} game - Game reference to the currently running game.
* @param {string} key - The key of the tilemap in the Cache.
* @param {number} [tileWidth=32] - The pixel width of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data.
* @param {number} [tileHeight=32] - The pixel height of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data.
* @param {number} [width=10] - The width of the map in tiles. If this map is created from Tiled or CSV data you don't need to specify this.
* @param {number} [height=10] - The height of the map in tiles. If this map is created from Tiled or CSV data you don't need to specify this.
* @return {object} The parsed map object.
*/
parse: function (game, key, tileWidth, tileHeight, width, height)
{
if (tileWidth === undefined) { tileWidth = 32; }
if (tileHeight === undefined) { tileHeight = 32; }
if (width === undefined) { width = 10; }
if (height === undefined) { height = 10; }
if (key === undefined)
{
return this.getEmptyData();
}
if (key === null)
{
return this.getEmptyData(tileWidth, tileHeight, width, height);
}
var map = game.cache.getTilemapData(key);
if (map)
{
if (map.format === Phaser.Tilemap.CSV)
{
return this.parseCSV(key, map.data, tileWidth, tileHeight);
}
else if (!map.format || map.format === Phaser.Tilemap.TILED_JSON)
{
return this.parseTiledJSON(map.data);
}
}
else
{
console.warn('No map data found for key "%s"', key);
}
},
/**
* Parses a CSV file into valid map data.
*
* @method Phaser.TilemapParser.parseCSV
* @param {string} key - The name you want to give the map data.
* @param {string} data - The CSV file data.
* @param {number} [tileWidth=32] - The pixel width of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data.
* @param {number} [tileHeight=32] - The pixel height of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data.
* @return {object} Generated map data.
*/
parseCSV: function (key, data, tileWidth, tileHeight)
{
var map = this.getEmptyData();
// Trim any rogue whitespace from the data
data = data.trim();
var output = [];
var rows = data.split('\n');
var height = rows.length;
var width = 0;
for (var y = 0; y < rows.length; y++)
{
output[y] = [];
var column = rows[y].split(',');
for (var x = 0; x < column.length; x++)
{
output[y][x] = new Phaser.Tile(map.layers[0], parseInt(column[x], 10), x, y, tileWidth, tileHeight);
}
if (width === 0)
{
width = column.length;
}
}
map.format = Phaser.Tilemap.CSV;
map.name = key;
map.width = width;
map.height = height;
map.tileWidth = tileWidth;
map.tileHeight = tileHeight;
map.widthInPixels = width * tileWidth;
map.heightInPixels = height * tileHeight;
map.layers[0].width = width;
map.layers[0].height = height;
map.layers[0].widthInPixels = map.widthInPixels;
map.layers[0].heightInPixels = map.heightInPixels;
map.layers[0].data = output;
return map;
},
/**
* Returns an empty map data object.
*
* @method Phaser.TilemapParser.getEmptyData
* @return {object} Generated map data.
*/
getEmptyData: function (tileWidth, tileHeight, width, height)
{
return {
width: (width !== undefined && width !== null) ? width : 0,
height: (height !== undefined && height !== null) ? height : 0,
tileWidth: (tileWidth !== undefined && tileWidth !== null) ? tileWidth : 0,
tileHeight: (tileHeight !== undefined && tileHeight !== null) ? tileHeight : 0,
orientation: 'orthogonal',
version: '1',
properties: {},
widthInPixels: 0,
heightInPixels: 0,
layers: [
{
name: 'layer',
x: 0,
y: 0,
width: 0,
height: 0,
widthInPixels: 0,
heightInPixels: 0,
alpha: 1,
visible: true,
properties: {},
indexes: [],
callbacks: [],
bodies: [],
data: []
}
],
images: [],
objects: {},
collision: {},
tilesets: [],
tiles: []
};
},
_slice: function (obj, fields)
{
var sliced = {};
for (var k in fields)
{
var key = fields[k];
if (typeof obj[key] !== 'undefined')
{
sliced[key] = obj[key];
}
}
return sliced;
},
/**
* Parses an object group in Tiled JSON files. Object groups can be found in both layers and tilesets. Called internally in parseTiledJSON.
* @method Phaser.TilemapParser.parseObjectGroup
* @param {object} objectGroup - A JSON object group.
* @param {object} objectsCollection - An object into which new array of Tiled map objects will be added.
* @param {object} collisionCollection - An object into which new array of collision objects will be added. Currently only polylines are added.
* @param {string} [nameKey=objectGroup.name] - Key under which to store objects in collisions in objectsCollection and collisionCollection
* @param {object} [relativePosition={x: 0, y: 0}] - Coordinates the object group's position is relative to.
* @return {object} A object literal containing the objectsCollection and collisionCollection
*/
parseObjectGroup: function (objectGroup, objectsCollection, collisionCollection, nameKey, relativePosition)
{
var nameKey = nameKey || objectGroup.name;
var relativePosition = relativePosition || {x: 0, y: 0};
var slice = this._slice;
if (!nameKey)
{
console.warn('No name found for objectGroup', objectGroup);
}
if (relativePosition.x === undefined || relativePosition.y === undefined)
{
console.warn('Malformed xy properties in relativePosition', relativePosition);
}
objectsCollection[nameKey] = objectsCollection[nameKey] || [];
collisionCollection[nameKey] = collisionCollection[nameKey] || [];
for (var v = 0, len = objectGroup.objects.length; v < len; v++)
{
var o = objectGroup.objects[v];
// Object Tiles
if (o.gid)
{
var object = {
gid: o.gid,
name: o.name,
type: o.type || '',
x: o.x + relativePosition.x,
y: o.y + relativePosition.y,
width: o.width,
height: o.height,
visible: o.visible,
properties: o.properties
};
if (o.rotation)
{
object.rotation = o.rotation;
}
objectsCollection[nameKey].push(object);
}
else if (o.polyline)
{
var object = {
name: o.name,
type: o.type,
x: o.x + relativePosition.x,
y: o.y + relativePosition.y,
width: o.width,
height: o.height,
visible: o.visible,
properties: o.properties
};
if (o.rotation)
{
object.rotation = o.rotation;
}
object.polyline = [];
// Parse the polyline into an array
for (var p = 0; p < o.polyline.length; p++)
{
object.polyline.push([ o.polyline[p].x, o.polyline[p].y ]);
}
collisionCollection[nameKey].push(object);
objectsCollection[nameKey].push(object);
}
// polygon
else if (o.polygon)
{
var object = slice(o, [ 'name', 'type', 'x', 'y', 'visible', 'rotation', 'properties' ]);
object.x += relativePosition.x;
object.y += relativePosition.y;
// Parse the polygon into an array
object.polygon = [];
for (var p = 0; p < o.polygon.length; p++)
{
object.polygon.push([ o.polygon[p].x, o.polygon[p].y ]);
}
collisionCollection[nameKey].push(object);
objectsCollection[nameKey].push(object);
}
// ellipse
else if (o.ellipse)
{
var object = slice(o, [ 'name', 'type', 'ellipse', 'x', 'y', 'width', 'height', 'visible', 'rotation', 'properties' ]);
object.x += relativePosition.x;
object.y += relativePosition.y;
collisionCollection[nameKey].push(object);
objectsCollection[nameKey].push(object);
}
// otherwise it's a rectangle
else
{
var object = slice(o, [ 'name', 'type', 'x', 'y', 'width', 'height', 'visible', 'rotation', 'properties' ]);
object.x += relativePosition.x;
object.y += relativePosition.y;
object.rectangle = true;
collisionCollection[nameKey].push(object);
objectsCollection[nameKey].push(object);
}
}
return {
objectsCollection: objectsCollection,
collisionCollection: collisionCollection
};
},
/**
* Parses a Tiled JSON file into valid map data.
* @method Phaser.TilemapParser.parseTiledJSON
* @param {object} json - The JSON map data.
* @return {object} Generated and parsed map data.
*/
parseTiledJSON: function (json)
{
if (json.orientation !== 'orthogonal')
{
console.warn('Phaser CE supports only orthogonal maps. This map\'s orientation is "%s".', json.orientation);
return null;
}
if (json.version > 1.1)
{
console.warn('Some features in this Tiled JSON map (version %s) may not work in Phaser CE. Enable the json1 plugin and reexport the map in "Tiled 1.1" format. https://github.com/photonstorm/phaser-ce/issues/623', json.version);
}
// Map data will consist of: layers, objects, images, tilesets, sizes
var map = {
width: json.width,
height: json.height,
tileWidth: json.tilewidth,
tileHeight: json.tileheight,
orientation: json.orientation,
format: Phaser.Tilemap.TILED_JSON,
version: json.version,
properties: json.properties,
widthInPixels: json.width * json.tilewidth,
heightInPixels: json.height * json.tileheight
};
// Tile Layers
var layers = [];
for (var i = 0; i < json.layers.length; i++)
{
if (json.layers[i].type !== 'tilelayer')
{
continue;
}
var curl = json.layers[i];
// Base64 decode data if necessary
// NOTE: uncompressed base64 only.
if (!curl.compression && curl.encoding && curl.encoding === 'base64')
{
var binaryString = window.atob(curl.data);
var len = binaryString.length;
var bytes = new Array(len);
// Interpret binaryString as an array of bytes representing
// little-endian encoded uint32 values.
for (var j = 0; j < len; j += 4)
{
bytes[j / 4] = (
binaryString.charCodeAt(j) |
binaryString.charCodeAt(j + 1) << 8 |
binaryString.charCodeAt(j + 2) << 16 |
binaryString.charCodeAt(j + 3) << 24
) >>> 0;
}
curl.data = bytes;
delete curl.encoding;
}
else if (curl.compression)
{
console.warn('Layer compression is unsupported, skipping layer "%s".', curl.name);
continue;
}
var layer = {
name: curl.name,
x: curl.x,
y: curl.y,
width: curl.width,
height: curl.height,
widthInPixels: curl.width * json.tilewidth,
heightInPixels: curl.height * json.tileheight,
alpha: curl.opacity,
offsetX: curl.offsetx,
offsetY: curl.offsety,
visible: curl.visible,
properties: {},
indexes: [],
callbacks: [],
bodies: []
};
if (curl.properties)
{
layer.properties = curl.properties;
}
var x = 0;
var row = [];
var output = [];
var rotation, flipped, flippedVal, gid;
// Loop through the data field in the JSON.
// This is an array containing the tile indexes, one after the other. -1 = no tile, everything else = the tile index (starting at 1 for Tiled, 0 for CSV)
// If the map contains multiple tilesets then the indexes are relative to that which the set starts from.
// Need to set which tileset in the cache = which tileset in the JSON, if you do this manually it means you can use the same map data but a new tileset.
for (var t = 0, len = curl.data.length; t < len; t++)
{
rotation = 0;
flipped = false;
gid = curl.data[t];
flippedVal = 0;
// If true the current tile is flipped or rotated (Tiled TMX format)
if (gid > 0x20000000)
{
// FlippedX
if (gid > 0x80000000)
{
gid -= 0x80000000;
flippedVal += 4;
}
// FlippedY
if (gid > 0x40000000)
{
gid -= 0x40000000;
flippedVal += 2;
}
// FlippedAD (anti-diagonal = top-right is swapped with bottom-left corners)
if (gid > 0x20000000)
{
gid -= 0x20000000;
flippedVal += 1;
}
switch (flippedVal)
{
case 5:
rotation = Math.PI / 2;
break;
case 6:
rotation = Math.PI;
break;
case 3:
rotation = 3 * Math.PI / 2;
break;
case 4:
rotation = 0;
flipped = true;
break;
case 7:
rotation = Math.PI / 2;
flipped = true;
break;
case 2:
rotation = Math.PI;
flipped = true;
break;
case 1:
rotation = 3 * Math.PI / 2;
flipped = true;
break;
}
}
// index, x, y, width, height
if (gid > 0)
{
var tile = new Phaser.Tile(layer, gid, x, output.length, json.tilewidth, json.tileheight);
tile.rotation = rotation;
tile.flipped = flipped;
if (flippedVal !== 0)
{
// The WebGL renderer uses this to flip UV coordinates before drawing
tile.flippedVal = flippedVal;
}
row.push(tile);
}
else
if (Phaser.TilemapParser.INSERT_NULL)
{
row.push(null);
}
else
{
row.push(new Phaser.Tile(layer, -1, x, output.length, json.tilewidth, json.tileheight));
}
x++;
if (x === curl.width)
{
output.push(row);
x = 0;
row = [];
}
}
layer.data = output;
layers.push(layer);
}
map.layers = layers;
// Images
var images = [];
for (var i = 0; i < json.layers.length; i++)
{
if (json.layers[i].type !== 'imagelayer')
{
continue;
}
var curi = json.layers[i];
var image = {
name: curi.name,
image: curi.image,
x: curi.x,
y: curi.y,
alpha: curi.opacity,
visible: curi.visible,
properties: {}
};
if (curi.properties)
{
image.properties = curi.properties;
}
images.push(image);
}
map.images = images;
// Tilesets & Image Collections
var tilesets = [];
var tilesetGroupObjects = {};
var imagecollections = [];
var lastSet = null;
for (var i = 0; i < json.tilesets.length; i++)
{
// name, firstgid, width, height, margin, spacing, properties
var set = json.tilesets[i];
if (set.source)
{
console.warn('Phaser CE can\'t load external tilesets (%s). Embed the tileset and then export the map again. https://github.com/photonstorm/phaser-ce/issues/273', set.source);
}
else if (set.image)
{
var newSet = new Phaser.Tileset(set.name, set.firstgid, set.tilewidth, set.tileheight, set.margin, set.spacing, set.properties);
if (set.tileproperties)
{
newSet.tileProperties = set.tileproperties;
}
// For a normal sliced tileset the row/count/size information is computed when updated.
// This is done (again) after the image is set.
newSet.updateTileData(set.imagewidth, set.imageheight);
tilesets.push(newSet);
}
else if (set.tiles)
{
var newCollection = new Phaser.ImageCollection(set.name, set.firstgid, set.tilewidth, set.tileheight, set.margin, set.spacing, set.properties);
for (var ti in set.tiles)
{
var image = set.tiles[ti].image;
var gid = set.firstgid + parseInt(ti, 10);
newCollection.addImage(gid, image);
}
imagecollections.push(newCollection);
}
else
{
throw new Error('Tileset ' + set.name + ' has no `image` or `tiles` property.');
}
// build a temporary object for objectgroups found in the tileset's tiles
for (var ti in set.tiles)
{
var objectGroup = set.tiles[ti].objectgroup;
if (!objectGroup)
{
continue;
}
tilesetGroupObjects[parseInt(ti, 10) + set.firstgid] = objectGroup;
}
// We've got a new Tileset, so set the lastgid into the previous one
if (lastSet)
{
lastSet.lastgid = set.firstgid - 1;
}
lastSet = set;
}
if (tilesets.length === 0 && imagecollections.length === 0)
{
throw new Error('This tilemap has no tilesets.');
}
map.tilesets = tilesets;
map.imagecollections = imagecollections;
// Objects & Collision Data (polylines, etc)
var objects = {};
var collision = {};
for (var i = 0; i < json.layers.length; i++)
{
if (json.layers[i].type !== 'objectgroup')
{
continue;
}
var objectGroup = json.layers[i];
this.parseObjectGroup(objectGroup, objects, collision);
}
map.objects = objects;
map.collision = collision;
map.tiles = [];
// Finally lets build our super tileset index
for (var i = 0; i < map.tilesets.length; i++)
{
var set = map.tilesets[i];
var x = set.tileMargin;
var y = set.tileMargin;
var count = 0;
var countX = 0;
var countY = 0;
for (var t = set.firstgid; t < set.firstgid + set.total; t++)
{
// Can add extra properties here as needed
map.tiles[t] = [ x, y, i ];
x += set.tileWidth + set.tileSpacing;
count++;
if (count === set.total)
{
break;
}
countX++;
if (countX === set.columns)
{
x = set.tileMargin;
y += set.tileHeight + set.tileSpacing;
countX = 0;
countY++;
if (countY === set.rows)
{
break;
}
}
}
}
// assign tile properties
var layer;
var tile;
var sid;
var set;
// go through each of the map data layers
for (var i = 0; i < map.layers.length; i++)
{
layer = map.layers[i];
collision[layer.name] = [];
set = null;
// rows of tiles
for (var j = 0; j < layer.data.length; j++)
{
row = layer.data[j];
// individual tiles
for (var k = 0; k < row.length; k++)
{
tile = row[k];
if (tile === null || tile.index < 0)
{
continue;
}
// find the relevant tileset
sid = map.tiles[tile.index][2];
set = map.tilesets[sid];
// if that tile type has any properties, add them to the tile object
if (set.tileProperties && set.tileProperties[tile.index - set.firstgid])
{
tile.properties = Phaser.Utils.mixin(set.tileProperties[tile.index - set.firstgid], tile.properties);
}
var objectGroup = tilesetGroupObjects[tile.index];
if (objectGroup)
{
// build collisions and objects for objectgroups found in the tileset's tiles
this.parseObjectGroup(
objectGroup,
map.objects,
map.collision,
tile.layer.name,
{
x: tile.worldX + objectGroup.x,
y: tile.worldY + objectGroup.y
});
}
}
}
}
return map;
}
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Tile set is a combination of an image containing the tiles and collision data per tile.
*
* Tilesets are normally created automatically when Tiled data is loaded.
*
* @class Phaser.Tileset
* @constructor
* @param {string} name - The name of the tileset in the map data.
* @param {integer} firstgid - The first tile index this tileset contains.
* @param {integer} [width=32] - Width of each tile (in pixels).
* @param {integer} [height=32] - Height of each tile (in pixels).
* @param {integer} [margin=0] - The margin around all tiles in the sheet (in pixels).
* @param {integer} [spacing=0] - The spacing between each tile in the sheet (in pixels).
* @param {object} [properties={}] - Custom Tileset properties.
*/
Phaser.Tileset = function (name, firstgid, width, height, margin, spacing, properties)
{
if (width === undefined || width <= 0) { width = 32; }
if (height === undefined || height <= 0) { height = 32; }
if (margin === undefined) { margin = 0; }
if (spacing === undefined) { spacing = 0; }
/**
* The name of the Tileset.
* @property {string} name
*/
this.name = name;
/**
* The Tiled firstgid value.
* This is the starting index of the first tile index this Tileset contains.
* @property {integer} firstgid
*/
this.firstgid = firstgid | 0;
/**
* The width of each tile (in pixels).
* @property {integer} tileWidth
* @readonly
*/
this.tileWidth = width | 0;
/**
* The height of each tile (in pixels).
* @property {integer} tileHeight
* @readonly
*/
this.tileHeight = height | 0;
/**
* The margin around the tiles in the sheet (in pixels).
* Use `setSpacing` to change.
* @property {integer} tileMarge
* @readonly
*/
// Modified internally
this.tileMargin = margin | 0;
/**
* The spacing between each tile in the sheet (in pixels).
* Use `setSpacing` to change.
* @property {integer} tileSpacing
* @readonly
*/
this.tileSpacing = spacing | 0;
/**
* Tileset-specific properties that are typically defined in the Tiled editor.
* @property {object} properties
*/
this.properties = properties || {};
/**
* The cached image that contains the individual tiles. Use {@link Phaser.Tileset.setImage setImage} to set.
* @property {?object} image
* @readonly
*/
// Modified internally
this.image = null;
/**
* The number of tile rows in the the tileset.
* @property {integer}
* @readonly
*/
// Modified internally
this.rows = 0;
/**
* The number of tile columns in the tileset.
* @property {integer} columns
* @readonly
*/
// Modified internally
this.columns = 0;
/**
* The total number of tiles in the tileset.
* @property {integer} total
* @readonly
*/
// Modified internally
this.total = 0;
/**
* The look-up table to specific tile image offsets.
* The coordinates are interlaced such that it is [x0, y0, x1, y1 .. xN, yN] and the tile with the index of firstgid is found at indices 0/1.
* @property {integer[]} drawCoords
* @private
*/
this.drawCoords = [];
};
Phaser.Tileset.prototype = {
/**
* Draws a tile from this Tileset at the given coordinates on the context.
*
* @method Phaser.Tileset#draw
* @public
* @param {CanvasRenderingContext2D} context - The context to draw the tile onto.
* @param {number} x - The x coordinate to draw to.
* @param {number} y - The y coordinate to draw to.
* @param {integer} index - The index of the tile within the set to draw.
*/
draw: function (context, x, y, index)
{
// Correct the tile index for the set and bias for interlacing
var coordIndex = (index - this.firstgid) << 1;
if (coordIndex >= 0 && (coordIndex + 1) < this.drawCoords.length)
{
context.drawImage(
this.image,
this.drawCoords[coordIndex],
this.drawCoords[coordIndex + 1],
this.tileWidth,
this.tileHeight,
x,
y,
this.tileWidth,
this.tileHeight
);
}
},
/**
* Returns true if and only if this tileset contains the given tile index.
*
* @method Phaser.Tileset#containsTileIndex
* @public
* @param {number} tileIndex
* @return {boolean} True if this tileset contains the given index.
*/
containsTileIndex: function (tileIndex)
{
return (
tileIndex >= this.firstgid &&
tileIndex < (this.firstgid + this.total)
);
},
/**
* Set the image associated with this Tileset and update the tile data.
*
* @method Phaser.Tileset#setImage
* @public
* @param {Image} image - The image that contains the tiles.
*/
setImage: function (image)
{
this.image = image;
this.updateTileData(image.width, image.height);
},
/**
* Sets tile spacing and margins.
*
* @method Phaser.Tileset#setSpacing
* @public
* @param {integer} [margin=0] - The margin around the tiles in the sheet (in pixels).
* @param {integer} [spacing=0] - The spacing between the tiles in the sheet (in pixels).
*/
setSpacing: function (margin, spacing)
{
this.tileMargin = margin | 0;
this.tileSpacing = spacing | 0;
if (this.image)
{
this.updateTileData(this.image.width, this.image.height);
}
},
/**
* Updates tile coordinates and tileset data.
*
* @method Phaser.Tileset#updateTileData
* @private
* @param {integer} imageWidth - The (expected) width of the image to slice.
* @param {integer} imageHeight - The (expected) height of the image to slice.
*/
updateTileData: function (imageWidth, imageHeight)
{
// May be fractional values
var rowCount = (imageHeight - this.tileMargin * 2 + this.tileSpacing) / (this.tileHeight + this.tileSpacing);
var colCount = (imageWidth - this.tileMargin * 2 + this.tileSpacing) / (this.tileWidth + this.tileSpacing);
if (rowCount % 1 !== 0 || colCount % 1 !== 0)
{
console.warn(
'Phaser.Tileset - \'%s\' image tile area (%s x %s) is not a whole multiple of tile size (%s x %s + %s + %s)',
this.name, imageWidth, imageHeight, this.tileWidth, this.tileHeight, this.tileMargin, this.tileSpacing
);
}
// In Tiled a tileset image that is not an even multiple of the tile dimensions
// is truncated - hence the floor when calculating the rows/columns.
rowCount = Math.floor(rowCount);
colCount = Math.floor(colCount);
if ((this.rows && this.rows !== rowCount) || (this.columns && this.columns !== colCount))
{
console.warn(
'Phaser.Tileset - Tile layout from image \'%s\' (%s rows by %s columns) differs from tileset \'%s\' (%s rows by %s columns)',
this.image.name, colCount, rowCount, this.name, this.columns, this.rows
);
}
this.rows = rowCount;
this.columns = colCount;
this.total = rowCount * colCount;
this.drawCoords.length = 0;
var tx = this.tileMargin;
var ty = this.tileMargin;
for (var y = 0; y < this.rows; y++)
{
for (var x = 0; x < this.columns; x++)
{
this.drawCoords.push(tx);
this.drawCoords.push(ty);
tx += this.tileWidth + this.tileSpacing;
}
tx = this.tileMargin;
ty += this.tileHeight + this.tileSpacing;
}
}
};
Phaser.Tileset.prototype.constructor = Phaser.Tileset;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Create a new `Particle` object. Particles are extended Sprites that are emitted by a particle emitter such as Phaser.Particles.Arcade.Emitter.
*
* @class Phaser.Particle
* @constructor
* @extends Phaser.Sprite
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {number} x - The x coordinate (in world space) to position the Particle at.
* @param {number} y - The y coordinate (in world space) to position the Particle at.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|PIXI.Texture} key - This is the image or texture used by the Particle during rendering. It can be a string which is a reference to the Cache entry, or an instance of a RenderTexture or PIXI.Texture.
* @param {string|number} frame - If this Particle is using part of a sprite sheet or texture atlas you can specify the exact frame to use by giving a string or numeric index.
*/
Phaser.Particle = function (game, x, y, key, frame)
{
Phaser.Sprite.call(this, game, x, y, key, frame);
/**
* @property {boolean} autoScale - If this Particle automatically scales this is set to true by Particle.setScaleData.
* @protected
*/
this.autoScale = false;
/**
* @property {array} scaleData - A reference to the scaleData array owned by the Emitter that emitted this Particle.
* @protected
*/
this.scaleData = null;
/**
* @property {number} _s - Internal cache var for tracking auto scale.
* @private
*/
this._s = 0;
/**
* @property {boolean} autoAlpha - If this Particle automatically changes alpha this is set to true by Particle.setAlphaData.
* @protected
*/
this.autoAlpha = false;
/**
* @property {array} alphaData - A reference to the alphaData array owned by the Emitter that emitted this Particle.
* @protected
*/
this.alphaData = null;
/**
* @property {number} _a - Internal cache var for tracking auto alpha.
* @private
*/
this._a = 0;
};
Phaser.Particle.prototype = Object.create(Phaser.Sprite.prototype);
Phaser.Particle.prototype.constructor = Phaser.Particle;
/**
* Updates the Particle scale or alpha if autoScale and autoAlpha are set.
*
* @method Phaser.Particle#update
* @memberof Phaser.Particle
*/
Phaser.Particle.prototype.update = function ()
{
if (this.autoScale)
{
this._s--;
if (this._s)
{
this.scale.set(this.scaleData[this._s].x, this.scaleData[this._s].y);
}
else
{
this.autoScale = false;
}
}
if (this.autoAlpha)
{
this._a--;
if (this._a)
{
this.alpha = this.alphaData[this._a].v;
}
else
{
this.autoAlpha = false;
}
}
};
/**
* Called by the Emitter when this particle is emitted. Left empty for you to over-ride as required.
*
* @method Phaser.Particle#onEmit
* @memberof Phaser.Particle
*/
Phaser.Particle.prototype.onEmit = function ()
{
};
/**
* Called by the Emitter if autoAlpha has been enabled. Passes over the alpha ease data and resets the alpha counter.
*
* @method Phaser.Particle#setAlphaData
* @memberof Phaser.Particle
*/
Phaser.Particle.prototype.setAlphaData = function (data)
{
this.alphaData = data;
this._a = data.length - 1;
this.alpha = this.alphaData[this._a].v;
this.autoAlpha = true;
};
/**
* Called by the Emitter if autoScale has been enabled. Passes over the scale ease data and resets the scale counter.
*
* @method Phaser.Particle#setScaleData
* @memberof Phaser.Particle
*/
Phaser.Particle.prototype.setScaleData = function (data)
{
this.scaleData = data;
this._s = data.length - 1;
this.scale.set(this.scaleData[this._s].x, this.scaleData[this._s].y);
this.autoScale = true;
};
/**
* Resets the Particle. This places the Particle at the given x/y world coordinates and then
* sets alive, exists, visible and renderable all to true. Also resets the outOfBounds state and health values.
* If the Particle has a physics body that too is reset.
*
* @method Phaser.Particle#reset
* @memberof Phaser.Particle
* @param {number} x - The x coordinate (in world space) to position the Particle at.
* @param {number} y - The y coordinate (in world space) to position the Particle at.
* @param {number} [health=1] - The health to give the Particle.
* @return {Phaser.Particle} This instance.
*/
Phaser.Particle.prototype.reset = function (x, y, health)
{
Phaser.Component.Reset.prototype.reset.call(this, x, y, health);
this.alpha = 1;
this.scale.set(1);
this.autoScale = false;
this.autoAlpha = false;
return this;
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Phaser.Particles tracks any Emitters attached to it.
*
* @class Phaser.Particles
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.Particles = function (game)
{
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
/**
* @property {object} emitters - Internal emitters store.
*/
this.emitters = {};
/**
* @property {number} ID -
* @default
*/
this.ID = 0;
};
Phaser.Particles.prototype = {
/**
* Adds a new Particle Emitter to the Particle Manager.
* @method Phaser.Particles#add
* @param {Phaser.Emitter} emitter - The emitter to be added to the particle manager.
* @return {Phaser.Emitter} The emitter that was added.
*/
add: function (emitter)
{
this.emitters[emitter.id] = emitter;
return emitter;
},
/**
* Removes an existing Particle Emitter from the Particle Manager.
* @method Phaser.Particles#remove
* @param {Phaser.Emitter} emitter - The emitter to remove.
*/
remove: function (emitter)
{
delete this.emitters[emitter.id];
}
};
Phaser.Particles.prototype.constructor = Phaser.Particles;
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Arcade Particles is a Particle System integrated with Arcade Physics.
*
* @class Phaser.Particles.Arcade
*/
Phaser.Particles.Arcade = {};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Emitter is a lightweight particle emitter that uses Arcade Physics.
* It can be used for one-time explosions or for continuous effects like rain and fire.
* All it really does is launch Particle objects out at set intervals, and fixes their positions and velocities accordingly.
*
* @class Phaser.Particles.Arcade.Emitter
* @constructor
* @extends Phaser.Group
* @param {Phaser.Game} game - Current game instance.
* @param {number} [x=0] - The x coordinate within the Emitter that the particles are emitted from.
* @param {number} [y=0] - The y coordinate within the Emitter that the particles are emitted from.
* @param {number} [maxParticles=50] - The total number of particles in this emitter.
*/
Phaser.Particles.Arcade.Emitter = function (game, x, y, maxParticles)
{
/**
* @property {number} maxParticles - The total number of particles in this emitter.
* @default
*/
this.maxParticles = maxParticles || 50;
Phaser.Group.call(this, game);
/**
* @property {number} _id - Internal ID for this emitter -- only used by the Particle System in most cases
* @private
*/
this._id = this.game.particles.ID++;
/**
* @property {string} name - A handy string name for this emitter. Can be set to anything.
*/
this.name = 'emitter' + this.id;
/**
* @property {number} type - Internal Phaser Type value.
* @protected
*/
this.type = Phaser.EMITTER;
/**
* @property {number} physicsType - The const physics body type of this object.
* @readonly
*/
this.physicsType = Phaser.GROUP;
/**
* @property {Phaser.Rectangle} area - The {@link #setSize size} of the emitter's emit area. The **actual** emit area is a rectangle of this size centered on (emitX, emitY): `{x: this.left, y: this.top, width: this.area.width, height: this.area.height}`. Particles are generated at a random position within this area.
* @default
*/
this.area = new Phaser.Rectangle(x, y, 1, 1);
/**
* @property {?number} minAngle - The minimum angle of initial particle velocities, in degrees. When set to a non-null value (with {@link #maxAngle}), {@link #minSpeed} and {@link #maxSpeed} are used and {@link #minParticleSpeed} and {@link #maxParticleSpeed} are ignored.
* @default
*/
this.minAngle = null;
/**
* @property {?number} maxAngle - The maximum angle of initial particle velocities, in degrees. When set to a non-null value (with {@link #minAngle}), {@link #minSpeed} and {@link #maxSpeed} are used and {@link #minParticleSpeed} and {@link #maxParticleSpeed} are ignored.
* @default
*/
this.maxAngle = null;
/**
* @property {number} minSpeed - The minimum initial speed of particles released within {@link #minAngle} and {@link #maxAngle}.
* @default
*/
this.minSpeed = 0;
/**
* @property {number} maxSpeed - The maximum initial speed of particles released within {@link #minAngle} and {@link #maxAngle}.
* @default
*/
this.maxSpeed = 100;
/**
* @property {Phaser.Point} minParticleSpeed - The minimum possible velocity of a particle.
* @default
*/
this.minParticleSpeed = new Phaser.Point(-100, -100);
/**
* @property {Phaser.Point} maxParticleSpeed - The maximum possible velocity of a particle.
* @default
*/
this.maxParticleSpeed = new Phaser.Point(100, 100);
/**
* @property {number} minParticleScale - The minimum possible scale of a particle. This is applied to the X and Y axis. If you need to control each axis see minParticleScaleX.
* @default
*/
this.minParticleScale = 1;
/**
* @property {number} maxParticleScale - The maximum possible scale of a particle. This is applied to the X and Y axis. If you need to control each axis see maxParticleScaleX.
* @default
*/
this.maxParticleScale = 1;
/**
* @property {array} scaleData - An array of the calculated scale easing data applied to particles with scaleRates > 0.
*/
this.scaleData = null;
/**
* @property {number} minRotation - The minimum possible angular velocity of a particle.
* @default
*/
this.minRotation = -360;
/**
* @property {number} maxRotation - The maximum possible angular velocity of a particle.
* @default
*/
this.maxRotation = 360;
/**
* @property {number} minParticleAlpha - The minimum possible alpha value of a particle.
* @default
*/
this.minParticleAlpha = 1;
/**
* @property {number} maxParticleAlpha - The maximum possible alpha value of a particle.
* @default
*/
this.maxParticleAlpha = 1;
/**
* @property {array} alphaData - An array of the calculated alpha easing data applied to particles with alphaRates > 0.
*/
this.alphaData = null;
/**
* @property {function} particleClass - For emitting your own particle class types. They must extend Phaser.Particle.
* @default
*/
this.particleClass = Phaser.Particle;
/**
* @property {Phaser.Point} particleDrag - The X and Y drag component of particles launched from the emitter.
*/
this.particleDrag = new Phaser.Point();
/**
* @property {number} angularDrag - The angular drag component of particles launched from the emitter if they are rotating.
* @default
*/
this.angularDrag = 0;
/**
* @property {number} frequency - How often a particle is emitted in ms (if emitter is started with Explode === false).
* @default
*/
this.frequency = 100;
/**
* @property {number} lifespan - How long each particle lives once it is emitted in ms. Default is 2 seconds. Set lifespan to 'zero' for particles to live forever.
* @default
*/
this.lifespan = 2000;
/**
* @property {Phaser.Point} bounce - How much each particle should bounce on each axis. 1 = full bounce, 0 = no bounce.
*/
this.bounce = new Phaser.Point();
/**
* @property {boolean} on - Determines whether the emitter is currently emitting particles. It is totally safe to directly toggle this.
* @default
*/
this.on = false;
/**
* @property {Phaser.Point} particleAnchor - When a particle is created its anchor will be set to match this Point object (defaults to x/y: 0.5 to aid in rotation)
* @default
*/
this.particleAnchor = new Phaser.Point(0.5, 0.5);
/**
* @property {number} blendMode - The blendMode as set on the particle when emitted from the Emitter. Defaults to NORMAL. Needs browser capable of supporting canvas blend-modes (most not available in WebGL)
* @default
*/
this.blendMode = Phaser.blendModes.NORMAL;
/**
* The point the particles are emitted from.
* Emitter.x and Emitter.y control the containers location, which updates all current particles
* Emitter.emitX and Emitter.emitY control the emission location relative to the x/y position.
* @property {number} emitX
*/
this.emitX = x;
/**
* The point the particles are emitted from.
* Emitter.x and Emitter.y control the containers location, which updates all current particles
* Emitter.emitX and Emitter.emitY control the emission location relative to the x/y position.
* @property {number} emitY
*/
this.emitY = y;
/**
* @property {boolean} autoScale - When a new Particle is emitted this controls if it will automatically scale in size. Use Emitter.setScale to configure.
*/
this.autoScale = false;
/**
* @property {boolean} autoAlpha - When a new Particle is emitted this controls if it will automatically change alpha. Use Emitter.setAlpha to configure.
*/
this.autoAlpha = false;
/**
* @property {boolean} particleBringToTop - If this is `true` then when the Particle is emitted it will be bought to the top of the Emitters display list.
* @default
*/
this.particleBringToTop = false;
/**
* @property {boolean} particleSendToBack - If this is `true` then when the Particle is emitted it will be sent to the back of the Emitters display list.
* @default
*/
this.particleSendToBack = false;
/**
* @property {object} counts - Records emitter activity.
* @property {number} counts.emitted - How many particles were emitted during the last update.
* @property {number} counts.failed - How many particles could not be emitted during the last update (because no particles were available).
* @property {number} counts.totalEmitted - How many particles have been emitted.
* @property {number} counts.totalFailed - How many particles could not be emitted when they were due (because no particles were available).
*/
this.counts = {
emitted: 0,
failed: 0,
totalEmitted: 0,
totalFailed: 0
};
/**
* @property {Phaser.Point} _gravity - Internal gravity value.
* @private
*/
this._gravity = new Phaser.Point(0, 100);
/**
* @property {Phaser.Point} _minParticleScale - Internal particle scale var.
* @private
*/
this._minParticleScale = new Phaser.Point(1, 1);
/**
* @property {Phaser.Point} _maxParticleScale - Internal particle scale var.
* @private
*/
this._maxParticleScale = new Phaser.Point(1, 1);
/**
* @property {number} _total - Internal helper for deciding how many particles to launch (via {@link #start}).
* @private
*/
this._total = 0;
/**
* @property {number} _timer - Internal helper for deciding when to launch particles or kill them.
* @private
*/
this._timer = 0;
/**
* @property {number} _counter - Internal counter for figuring out how many particles to launch.
* @private
*/
this._counter = 0;
/**
* @property {number} _flowQuantity - Internal counter for figuring out how many particles to launch per flow update.
* @private
*/
this._flowQuantity = 0;
/**
* @property {number} _flowTotal - Internal counter for figuring out how many particles to launch in total (via {@link #flow}).
* @private
*/
this._flowTotal = 0;
/**
* @property {boolean} _explode - Internal helper for the style of particle emission (all at once, or one at a time).
* @private
*/
this._explode = true;
/**
* @property {any} _frames - Internal helper for the particle frame.
* @private
*/
this._frames = null;
};
Phaser.Particles.Arcade.Emitter.prototype = Object.create(Phaser.Group.prototype);
Phaser.Particles.Arcade.Emitter.prototype.constructor = Phaser.Particles.Arcade.Emitter;
/**
* Called automatically by the game loop, decides when to launch particles and when to "die".
*
* @method Phaser.Particles.Arcade.Emitter#update
*/
Phaser.Particles.Arcade.Emitter.prototype.update = function ()
{
this.counts.emitted = 0;
this.counts.failed = 0;
if (this.on && this.game.time.time >= this._timer)
{
this._timer = this.game.time.time + this.frequency * this.game.time.slowMotion;
if (this._flowTotal !== 0)
{
if (this._flowQuantity > 0)
{
for (var i = 0; i < this._flowQuantity; i++)
{
if (this.emitParticle())
{
this._counter++;
if (this._flowTotal !== -1 && this._counter >= this._flowTotal)
{
this.on = false;
break;
}
}
}
}
else
if (this.emitParticle())
{
this._counter++;
if (this._flowTotal !== -1 && this._counter >= this._flowTotal)
{
this.on = false;
}
}
}
else
if (this.emitParticle())
{
this._counter++;
if (this._total > 0 && this._counter >= this._total)
{
this.on = false;
}
}
}
var i = this.children.length;
while (i--)
{
if (this.children[i].exists)
{
this.children[i].update();
}
}
};
/**
* This function generates a new set of particles for use by this emitter.
* The particles are stored internally waiting to be emitted via Emitter.start.
*
* @method Phaser.Particles.Arcade.Emitter#makeParticles
* @param {array|string} keys - A string or an array of strings that the particle sprites will use as their texture. If an array one is picked at random.
* @param {array|number} [frames=0] - A frame number, or array of frames that the sprite will use. If an array one is picked at random.
* @param {number} [quantity] - The number of particles to generate. If not given it will use the value of Emitter.maxParticles. If the value is greater than Emitter.maxParticles it will use Emitter.maxParticles as the quantity.
* @param {boolean} [collide=false] - If you want the particles to be able to collide with other Arcade Physics bodies then set this to true.
* @param {boolean} [collideWorldBounds=false] - A particle can be set to collide against the World bounds automatically and rebound back into the World if this is set to true. Otherwise it will leave the World.
* @param {object} [particleArguments=null] - Custom arguments to pass to your particle class
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.makeParticles = function (keys, frames, quantity, collide, collideWorldBounds, particleArguments)
{
if (frames === undefined) { frames = 0; }
if (quantity === undefined) { quantity = this.maxParticles; }
if (collide === undefined) { collide = false; }
if (collideWorldBounds === undefined) { collideWorldBounds = false; }
if (particleArguments === undefined) { particleArguments = null; }
var particle;
var i = 0;
var rndKey = keys;
var rndFrame = frames;
this._frames = frames;
if (quantity > this.maxParticles)
{
this.maxParticles = quantity;
}
while (i < quantity)
{
if (Array.isArray(keys))
{
rndKey = this.game.rnd.pick(keys);
}
if (Array.isArray(frames))
{
rndFrame = this.game.rnd.pick(frames);
}
particle = new this.particleClass(this.game, 0, 0, rndKey, rndFrame, particleArguments);
this.game.physics.arcade.enable(particle, false);
particle.body.checkCollision.none = !collide;
particle.body.collideWorldBounds = collideWorldBounds;
particle.body.skipQuadTree = true;
particle.exists = false;
particle.visible = false;
particle.anchor.copyFrom(this.particleAnchor);
this.add(particle);
i++;
}
return this;
};
/**
* Call this function to turn off all the particles and the emitter.
*
* @method Phaser.Particles.Arcade.Emitter#kill
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.kill = function ()
{
this.on = false;
this.alive = false;
this.exists = false;
return this;
};
/**
* Handy for bringing game objects "back to life". Just sets alive and exists back to true.
*
* @method Phaser.Particles.Arcade.Emitter#revive
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.revive = function ()
{
this.alive = true;
this.exists = true;
return this;
};
/**
* Call this function to emit the given quantity of particles at all once (an explosion)
*
* @method Phaser.Particles.Arcade.Emitter#explode
* @param {number} [lifespan=0] - How long each particle lives once emitted in ms. 0 = forever.
* @param {number} [quantity=this.maxParticles] - How many particles to launch.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.explode = function (lifespan, quantity)
{
if (quantity === undefined)
{
quantity = this.maxParticles;
}
this._flowTotal = 0;
this.start(true, lifespan, 0, quantity, false);
return this;
};
/**
* Call this function to start emitting a flow of particles.
* `quantity` particles are released every interval of `frequency` ms until `total` particles have been released (or forever).
* If you set the total to be 20 and quantity to be 5 then flow will emit 4 times in total (4 × 5 = 20 total) and then turn {@link #on off}.
* If you set the total to be -1 then no quantity cap is used and it will keep emitting (as long as there are inactive particles available).
*
* {@link #output}, {@link #lifespanOutput}, and {@link #remainder} describe the particle flow rate.
* During a stable flow, the number of active particles approaches {@link #lifespanOutput} and the number of inactive particles approaches {@link #remainder}.
* If {@link #remainder} is less than 0, there will likely be no particles available for a portion of the flow (see {@link #count}).
*
* @method Phaser.Particles.Arcade.Emitter#flow
* @param {number} [lifespan=0] - How long each particle lives once emitted in ms. 0 = forever.
* @param {number} [frequency=250] - The interval between each release of particles, given in ms. Values between 0 and 16.66 will behave the same (60 releases per second).
* @param {number} [quantity=1] - How many particles to launch at each interval. Not larger than {@link #maxParticles}.
* @param {number} [total=-1] - Turn {@link #on off} after launching this many particles in total. If -1 it will carry on indefinitely.
* @param {boolean} [immediate=true] - Should the flow start immediately (true) or wait until the first frequency event? (false)
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.flow = function (lifespan, frequency, quantity, total, immediate)
{
if (frequency === undefined || frequency === null) { frequency = 250; }
if (quantity === undefined || quantity === 0) { quantity = 1; }
if (total === undefined) { total = -1; }
if (immediate === undefined) { immediate = true; }
if (quantity > this.maxParticles)
{
quantity = this.maxParticles;
}
this._counter = 0;
this._flowQuantity = quantity;
this._flowTotal = total;
if (immediate)
{
this.start(true, lifespan, frequency, quantity);
this._counter += quantity;
this.on = true;
this._timer = this.game.time.time + frequency * this.game.time.slowMotion;
}
else
{
this.start(false, lifespan, frequency, quantity);
}
return this;
};
/**
* Start emitting particles.
*
* {@link #explode} and {@link #flow} are simpler methods.
*
* There are two patterns, based on the `explode` argument:
*
* ##### explode=true
*
* start(true, lifespan=0, null, total)
*
* When `explode` is true or `forceQuantity` is true, `start` emits `total` particles immediately. You should pass a nonzero `total`.
*
* ##### explode=false
*
* start(false, lifespan=0, frequency=250, total=0)
*
* When `explode` is false and `forceQuantity` is false, `start` emits 1 particle every interval of `frequency` ms. If `total` is not zero, the emitter turns itself off after `total` particles have been released. If `total` is zero, the emitter keeps emitting particles as long as they are available. To emit more than 1 particle per flow interval, use {@link #flow} instead.
*
* `forceQuantity` seems equivalent to `explode` and can probably be avoided.
*
* @method Phaser.Particles.Arcade.Emitter#start
* @param {boolean} [explode=true] - Whether the particles should all burst out at once (true) or at the frequency given (false).
* @param {number} [lifespan=0] - How long each particle lives once emitted in ms. 0 = forever.
* @param {number} [frequency=250] - The interval between each release of 1 particle, when `explode` is false. Value given in ms. Ignored if `explode` is set to true.
* @param {number} [total=0] - Turn {@link #on off} after launching this many particles in total.
* @param {number} [forceQuantity=false] - Equivalent to `explodes`.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.start = function (explode, lifespan, frequency, total, forceQuantity)
{
if (explode === undefined) { explode = true; }
if (lifespan === undefined) { lifespan = 0; }
if (frequency === undefined || frequency === null) { frequency = 250; }
if (total === undefined) { total = 0; }
if (forceQuantity === undefined) { forceQuantity = false; }
if (total > this.maxParticles)
{
total = this.maxParticles;
}
this.revive();
this.visible = true;
this.lifespan = lifespan;
this.frequency = frequency;
if (explode || forceQuantity)
{
for (var i = 0; i < total; i++)
{
this.emitParticle();
}
}
else
{
this.on = true;
this._total = total;
this._counter = 0;
this._timer = this.game.time.time + frequency * this.game.time.slowMotion;
}
return this;
};
/**
* This function is used internally to emit the next particle in the queue.
*
* However it can also be called externally to emit a particle.
*
* When called externally you can use the arguments to override any defaults the Emitter has set.
*
* The newly emitted particle is available in {@link Phaser.Particles.Arcade.Emitter#cursor}.
*
* @method Phaser.Particles.Arcade.Emitter#emitParticle
* @param {number} [x] - The x coordinate to emit the particle from. If `null` or `undefined` it will use `Emitter.emitX` or if the Emitter has a width > 1 a random value between `Emitter.left` and `Emitter.right`.
* @param {number} [y] - The y coordinate to emit the particle from. If `null` or `undefined` it will use `Emitter.emitY` or if the Emitter has a height > 1 a random value between `Emitter.top` and `Emitter.bottom`.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - This is the image or texture used by the Particle during rendering. It can be a string which is a reference to the Cache Image entry, or an instance of a RenderTexture, BitmapData, Video or PIXI.Texture.
* @param {string|number} [frame] - If this Particle is using part of a sprite sheet or texture atlas you can specify the exact frame to use by giving a string or numeric index.
* @return {boolean} True if a particle was emitted, otherwise false.
*/
Phaser.Particles.Arcade.Emitter.prototype.emitParticle = function (x, y, key, frame)
{
if (x === undefined) { x = null; }
if (y === undefined) { y = null; }
var particle = this.getNextParticle();
if (particle === null)
{
this.counts.failed++;
this.counts.totalFailed++;
return false;
}
this.counts.emitted++;
this.counts.totalEmitted++;
var rnd = this.game.rnd;
if (key !== undefined && frame !== undefined)
{
particle.loadTexture(key, frame);
}
else if (key !== undefined)
{
particle.loadTexture(key);
particle.frame = Array.isArray(this._frames) ? rnd.pick(this._frames) : this._frames;
}
var emitX = this.emitX;
var emitY = this.emitY;
if (x !== null)
{
emitX = x;
}
else if (this.width > 1)
{
emitX = rnd.between(this.left, this.right);
}
if (y !== null)
{
emitY = y;
}
else if (this.height > 1)
{
emitY = rnd.between(this.top, this.bottom);
}
this.resetParticle(particle, emitX, emitY);
return true;
};
/**
* Helper for {@link #emitParticle}. Gets the next available particle.
*
* @private
* @return {?Phaser.Particle} The first particle with exists=false, or null
*/
Phaser.Particles.Arcade.Emitter.prototype.getNextParticle = function ()
{
var i = this.length;
while (i--)
{
var next = this.next();
if (!next.exists)
{
return next;
}
}
return null;
};
/**
* Helper for {@link #emitParticle}. Sets particle properties and calls {@link Particle#onEmit}.
*
* @private
* @param {Phaser.Particle} particle
* @param {number} x
* @param {number} y
*/
Phaser.Particles.Arcade.Emitter.prototype.resetParticle = function (particle, x, y)
{
var rnd = this.game.rnd;
particle.reset(x, y);
particle.angle = 0;
particle.lifespan = this.lifespan;
if (this.particleBringToTop)
{
this.bringToTop(particle);
}
else if (this.particleSendToBack)
{
this.sendToBack(particle);
}
if (this.autoScale)
{
particle.setScaleData(this.scaleData);
}
else if (this.minParticleScale !== 1 || this.maxParticleScale !== 1)
{
particle.scale.set(rnd.realInRange(this.minParticleScale, this.maxParticleScale));
}
else if ((this._minParticleScale.x !== this._maxParticleScale.x) || (this._minParticleScale.y !== this._maxParticleScale.y))
{
particle.scale.set(rnd.realInRange(this._minParticleScale.x, this._maxParticleScale.x), rnd.realInRange(this._minParticleScale.y, this._maxParticleScale.y));
}
else
{
particle.scale.set(this._minParticleScale.x, this._minParticleScale.y);
}
if (this.autoAlpha)
{
particle.setAlphaData(this.alphaData);
}
else
{
particle.alpha = rnd.realInRange(this.minParticleAlpha, this.maxParticleAlpha);
}
particle.blendMode = this.blendMode;
var body = particle.body;
body.updateBounds();
body.bounce.copyFrom(this.bounce);
body.drag.copyFrom(this.particleDrag);
if (this.minAngle != null && this.maxAngle != null)
{
this.game.physics.arcade.velocityFromAngle(
(this.minAngle === this.maxAngle) ? this.minAngle : rnd.between(this.minAngle, this.maxAngle),
(this.minSpeed === this.maxSpeed) ? this.minSpeed : rnd.between(this.minSpeed, this.maxSpeed),
body.velocity
);
}
else
{
body.velocity.x = rnd.between(this.minParticleSpeed.x, this.maxParticleSpeed.x);
body.velocity.y = rnd.between(this.minParticleSpeed.y, this.maxParticleSpeed.y);
}
body.angularVelocity = rnd.between(this.minRotation, this.maxRotation);
body.gravity.copyFrom(this.gravity);
body.angularDrag = this.angularDrag;
particle.onEmit();
};
/**
* Destroys this Emitter, all associated child Particles and then removes itself from the Particle Manager.
*
* @method Phaser.Particles.Arcade.Emitter#destroy
*/
Phaser.Particles.Arcade.Emitter.prototype.destroy = function ()
{
this.game.particles.remove(this);
Phaser.Group.prototype.destroy.call(this, true, false);
};
/**
* A more compact way of setting the width and height of the emitter.
*
* @method Phaser.Particles.Arcade.Emitter#setSize
* @param {number} width - The desired width of the emitter (particles are spawned randomly within these dimensions).
* @param {number} height - The desired height of the emitter.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.setSize = function (width, height)
{
this.area.width = width;
this.area.height = height;
return this;
};
/**
* A more compact way of setting the X velocity range of the emitter.
* @method Phaser.Particles.Arcade.Emitter#setXSpeed
* @param {number} [min=0] - The minimum value for this range.
* @param {number} [max=0] - The maximum value for this range.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.setXSpeed = function (min, max)
{
min = min || 0;
max = max || 0;
this.minParticleSpeed.x = min;
this.maxParticleSpeed.x = max;
return this;
};
/**
* A more compact way of setting the Y velocity range of the emitter.
* @method Phaser.Particles.Arcade.Emitter#setYSpeed
* @param {number} [min=0] - The minimum value for this range.
* @param {number} [max=0] - The maximum value for this range.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.setYSpeed = function (min, max)
{
min = min || 0;
max = max || 0;
this.minParticleSpeed.y = min;
this.maxParticleSpeed.y = max;
return this;
};
/**
* A more compact way of setting the angular velocity constraints of the particles.
*
* @method Phaser.Particles.Arcade.Emitter#setRotation
* @param {number} [min=0] - The minimum value for this range.
* @param {number} [max=0] - The maximum value for this range.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.setRotation = function (min, max)
{
min = min || 0;
max = max || 0;
this.minRotation = min;
this.maxRotation = max;
return this;
};
/**
* A more compact way of setting the alpha constraints of the particles.
* The rate parameter, if set to a value above zero, lets you set the speed at which the Particle change in alpha from min to max.
* If rate is zero, which is the default, the particle won't change alpha - instead it will pick a random alpha between min and max on emit.
*
* @method Phaser.Particles.Arcade.Emitter#setAlpha
* @param {number} [min=1] - The minimum value for this range.
* @param {number} [max=1] - The maximum value for this range.
* @param {number} [rate=0] - The rate (in ms) at which the particles will change in alpha from min to max, or set to zero to pick a random alpha between the two.
* @param {function} [ease=Phaser.Easing.Linear.None] - If you've set a rate > 0 this is the easing formula applied between the min and max values.
* @param {boolean} [yoyo=false] - If you've set a rate > 0 you can set if the ease will yoyo or not (i.e. ease back to its original values)
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.setAlpha = function (min, max, rate, ease, yoyo)
{
if (min === undefined) { min = 1; }
if (max === undefined) { max = 1; }
if (rate === undefined) { rate = 0; }
if (ease === undefined) { ease = Phaser.Easing.Linear.None; }
if (yoyo === undefined) { yoyo = false; }
this.minParticleAlpha = min;
this.maxParticleAlpha = max;
this.autoAlpha = false;
if (rate > 0 && min !== max)
{
var tweenData = { v: min };
var tween = this.game.make.tween(tweenData).to({ v: max }, rate, ease);
tween.yoyo(yoyo);
this.alphaData = tween.generateData(60);
// Inverse it so we don't have to do array length look-ups in Particle update loops
this.alphaData.reverse();
this.autoAlpha = true;
}
return this;
};
/**
* A more compact way of setting the scale constraints of the particles.
* The rate parameter, if set to a value above zero, lets you set the speed and ease which the Particle uses to change in scale from min to max across both axis.
* If rate is zero, which is the default, the particle won't change scale during update, instead it will pick a random scale between min and max on emit.
*
* @method Phaser.Particles.Arcade.Emitter#setScale
* @param {number} [minX=1] - The minimum value of Particle.scale.x.
* @param {number} [maxX=1] - The maximum value of Particle.scale.x.
* @param {number} [minY=1] - The minimum value of Particle.scale.y.
* @param {number} [maxY=1] - The maximum value of Particle.scale.y.
* @param {number} [rate=0] - The rate (in ms) at which the particles will change in scale from min to max, or set to zero to pick a random size between the two.
* @param {function} [ease=Phaser.Easing.Linear.None] - If you've set a rate > 0 this is the easing formula applied between the min and max values.
* @param {boolean} [yoyo=false] - If you've set a rate > 0 you can set if the ease will yoyo or not (i.e. ease back to its original values)
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.setScale = function (minX, maxX, minY, maxY, rate, ease, yoyo)
{
if (minX === undefined) { minX = 1; }
if (maxX === undefined) { maxX = 1; }
if (minY === undefined) { minY = 1; }
if (maxY === undefined) { maxY = 1; }
if (rate === undefined) { rate = 0; }
if (ease === undefined) { ease = Phaser.Easing.Linear.None; }
if (yoyo === undefined) { yoyo = false; }
// Reset these
this.minParticleScale = 1;
this.maxParticleScale = 1;
this._minParticleScale.set(minX, minY);
this._maxParticleScale.set(maxX, maxY);
this.autoScale = false;
if (rate > 0 && ((minX !== maxX) || (minY !== maxY)))
{
var tweenData = { x: minX, y: minY };
var tween = this.game.make.tween(tweenData).to({ x: maxX, y: maxY }, rate, ease);
tween.yoyo(yoyo);
this.scaleData = tween.generateData(60);
// Inverse it so we don't have to do array length look-ups in Particle update loops
this.scaleData.reverse();
this.autoScale = true;
}
return this;
};
/**
* Sets a radial pattern for emitting particles.
*
* This is a shorthand for setting {@link #minAngle}, {@link #maxAngle}, {@link #minSpeed}, and {@link #maxSpeed}.
*
* To remove the radial pattern, use `setAngle(null, null)`.
*
* @method Phaser.Particles.Arcade.Emitter#setAngle
* @param {?number} minAngle - The minimum angle of initial particle velocities, in degrees.
* @param {?number} maxAngle - The maximum angle of initial particle velocities, in degrees.
* @param {number} [minSpeed] - The minimum initial particle speed.
* @param {number} [maxSpeed] - The maximum initial particle speed.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.setAngle = function (minAngle, maxAngle, minSpeed, maxSpeed)
{
this.minAngle = minAngle;
this.maxAngle = maxAngle;
if (minSpeed != null) { this.minSpeed = minSpeed; }
if (maxSpeed != null) { this.maxSpeed = maxSpeed; }
return this;
};
/**
* Change the emitter's center to match the center of any object with a `center` property, such as an Arcade Body.
* If the object doesn't have a `center` property it will be set to the object's anchor-adjusted world position (`object.world`).
*
* @method Phaser.Particles.Arcade.Emitter#at
* @param {object|Phaser.Sprite|Phaser.Image|Phaser.TileSprite|Phaser.Text|PIXI.DisplayObject} object - The object that you wish to match the center with.
* @return {Phaser.Particles.Arcade.Emitter} This Emitter instance.
*/
Phaser.Particles.Arcade.Emitter.prototype.at = function (object)
{
if (object.center)
{
this.emitX = object.center.x;
this.emitY = object.center.y;
}
else
{
this.emitX = object.world.x + (object.anchor.x * object.width);
this.emitY = object.world.y + (object.anchor.y * object.height);
}
return this;
};
/**
* @name Phaser.Particles.Arcade.Emitter#gravity
* @property {Phaser.Point} gravity - Sets the `body.gravity` of each particle sprite to this on launch.
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'gravity', {
get: function ()
{
return this._gravity;
},
set: function (value)
{
if (typeof value === 'number')
{
this._gravity.y = value;
}
else
{
this._gravity = value;
}
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#id
* @property {number} id - Gets the internal ID that represents this emitter.
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'id', {
get: function ()
{
return this._id;
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#width
* @property {number} width - Gets or sets the width of the Emitter. This is the region in which a particle can be emitted.
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'width', {
get: function ()
{
return this.area.width;
},
set: function (value)
{
this.area.width = value;
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#height
* @property {number} height - Gets or sets the height of the Emitter. This is the region in which a particle can be emitted.
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'height', {
get: function ()
{
return this.area.height;
},
set: function (value)
{
this.area.height = value;
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#x
* @property {number} x - Gets or sets the x position of the Emitter.
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'x', {
get: function ()
{
return this.emitX;
},
set: function (value)
{
this.emitX = value;
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#y
* @property {number} y - Gets or sets the y position of the Emitter.
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'y', {
get: function ()
{
return this.emitY;
},
set: function (value)
{
this.emitY = value;
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#left
* @property {number} left - Gets the left position of the Emitter.
* @readonly
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'left', {
get: function ()
{
return Math.floor(this.x - (this.area.width / 2));
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#right
* @property {number} right - Gets the right position of the Emitter.
* @readonly
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'right', {
get: function ()
{
return Math.floor(this.x + (this.area.width / 2));
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#top
* @property {number} top - Gets the top position of the Emitter.
* @readonly
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'top', {
get: function ()
{
return Math.floor(this.y - (this.area.height / 2));
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#bottom
* @property {number} bottom - Gets the bottom position of the Emitter.
* @readonly
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'bottom', {
get: function ()
{
return Math.floor(this.y + (this.area.height / 2));
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#output
* @property {number} output - The number of particles released per second, after calling {@link #flow}.
* @readonly
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'output', {
get: function ()
{
return 1000 * this._flowQuantity / this.frequency;
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#lifespanOutput
* @property {number} lifespanOutput - The number of particles released during one particle's {@link #lifespan}, after calling {@link #flow}.
* @readonly
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'lifespanOutput', {
get: function ()
{
return (this.lifespan === 0 ? Infinity : this.lifespan) * this._flowQuantity / this.frequency;
}
});
/**
* @name Phaser.Particles.Arcade.Emitter#remainder
* @property {number} remainder - The expected number of unreleased particles after a flow interval of {@link #lifespan}, after calling {@link #flow}.
* @readonly
*/
Object.defineProperty(Phaser.Particles.Arcade.Emitter.prototype, 'remainder', {
get: function ()
{
return this.maxParticles - this.lifespanOutput;
}
});
/**
* The last particle released, if any.
*
* You should treat this as read-only (and also avoid {@link #next} and {@link #previous}) once the emitter is started. Phaser uses it internally to track particles.
*
* @name Phaser.Particles.Arcade.Emitter#cursor
* @property {?DisplayObject} cursor
* @readonly
*/
// Inherited from Phaser.Group#cursor
/**
* Advances the cursor to the next particle.
*
* @method Phaser.Particles.Arcade.Emitter#next
* @protected
* @return {any} The child the cursor now points to.
*/
// Inherited from Phaser.Group#next
/**
* Moves the group cursor to the previous particle.
*
* @method Phaser.Particles.Arcade.Emitter#previous
* @protected
* @return {any} The child the cursor now points to.
*/
// Inherited from Phaser.Group#previous
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Weapon Plugin provides the ability to easily create a bullet pool and manager.
*
* Weapons fire {@link Phaser.Bullet} objects, which are essentially Sprites with a few extra properties.
* The Bullets are enabled for {@link Phaser.Physics.Arcade Arcade Physics}. They do not currently work with P2 Physics.
*
* The Bullets are created inside of {@link #bullets weapon.bullets}, which is a {@link Phaser.Group} instance. Anything you
* can usually do with a Group, such as move it around the display list, iterate it, etc can be done
* to the bullets Group too.
*
* Bullets can have textures and even animations. You can control the speed at which they are fired,
* the firing rate, the firing angle, and even set things like gravity for them.
*
* A small example, using {@link Phaser.GameObjectFactory#weapon add.weapon}, assumed to be running from within a {@link Phaser.State#create} method:
*
* ```javascript
* var weapon = this.add.weapon(10, 'bullet');
* weapon.fireFrom.set(300, 300);
* this.input.onDown.add(weapon.fire, this);
* ```
*
* If you want to (re)create the bullet pool separately, you can use:
*
* ```javascript
* var weapon = this.game.plugins.add(Phaser.Weapon);
* // …
* weapon.createBullets(10, 'bullet');
* ```
*
* @class Phaser.Weapon
* @constructor
* @param {Phaser.Game} game - A reference to the current Phaser.Game instance.
* @param {Phaser.PluginManager} parent - The Phaser Plugin Manager which looks after this plugin.
*/
Phaser.Weapon = function (game, parent)
{
Phaser.Plugin.call(this, game, parent);
/**
* This is the Phaser.Group that contains all of the bullets managed by this plugin.
* @type {Phaser.Group}
*/
this.bullets = null;
/**
* Should the bullet pool run out of bullets (i.e. they are all in flight) then this
* boolean controls if the Group will create a brand new bullet object or not.
* @type {boolean}
* @default
*/
this.autoExpandBulletsGroup = false;
/**
* Will this weapon auto fire? If set to true then a new bullet will be fired
* based on the {@link #fireRate} value.
* @type {boolean}
* @default
*/
this.autofire = false;
/**
* The total number of bullets this Weapon has fired so far.
* You can limit the number of shots allowed (via {@link #fireLimit}), and reset
* this total via {@link #resetShots}.
* @type {number}
* @default
*/
this.shots = 0;
/**
* The maximum number of shots that this Weapon is allowed to fire before it stops.
* When the limit is his the {@link #onFireLimit} Signal is dispatched.
* You can reset the shot counter via {@link #resetShots}.
* @type {number}
* @default
*/
this.fireLimit = 0;
/**
* The minimum interval between shots, in milliseconds.
* @type {number}
* @default
*/
this.fireRate = 100;
/**
* This is a modifier that is added to the {@link #fireRate} each update to add variety
* to the firing rate of the Weapon. The value is given in milliseconds.
* If you've a `fireRate` of 200 and a `fireRateVariance` of 50 then the actual
* firing rate of the Weapon will be between 150 and 250.
* @type {number}
* @default
*/
this.fireRateVariance = 0;
/**
* This is a Rectangle from within which the bullets are fired. By default it's a 1x1
* rectangle, the equivalent of a Point. But you can change the width and height, and if
* larger than 1x1 it'll pick a random point within the rectangle to launch the bullet from.
* @type {Phaser.Rectangle}
*/
this.fireFrom = new Phaser.Rectangle(0, 0, 1, 1);
/**
* The angle at which the bullets are fired. This can be a const such as Phaser.ANGLE_UP
* or it can be any number from 0 to 360 inclusive, where 0 degrees is to the right.
* @type {integer}
* @default
*/
this.fireAngle = Phaser.ANGLE_UP;
/**
* When a Bullet is fired it can optionally inherit the velocity of the `trackedSprite` if set.
* @type {boolean}
* @default
*/
this.bulletInheritSpriteSpeed = false;
/**
* The string based name of the animation that the Bullet will be given on launch.
* This is set via {@link #addBulletAnimation}.
* @type {string}
* @default
*/
this.bulletAnimation = '';
/**
* If you've added a set of frames via {@link #setBulletFrames} then you can optionally
* chose for each Bullet fired to pick a random frame from the set.
* @type {boolean}
* @default
*/
this.bulletFrameRandom = false;
/**
* If you've added a set of frames via {@link #setBulletFrames} then you can optionally
* chose for each Bullet fired to use the next frame in the set. The frame index is then
* advanced one frame until it reaches the end of the set, then it starts from the start
* again. Cycling frames like this allows you to create varied bullet effects via
* sprite sheets.
* @type {boolean}
* @default
*/
this.bulletFrameCycle = false;
/**
* Should the Bullets wrap around the world bounds? This automatically calls
* `World.wrap` on the Bullet each frame. See the docs for that method for details.
* @type {boolean}
* @default
*/
this.bulletWorldWrap = false;
/**
* If `bulletWorldWrap` is true then you can provide an optional padding value with this
* property. It's added to the calculations determining when the Bullet should wrap around
* the world or not. The value is given in pixels.
* @type {integer}
* @default
*/
this.bulletWorldWrapPadding = 0;
/**
* An optional angle offset applied to the Bullets when they are launched.
* This is useful if for example your bullet sprites have been drawn facing up, instead of
* to the right, and you want to fire them at an angle. In which case you can set the
* angle offset to be 90 and they'll be properly rotated when fired.
* @type {number}
* @default
*/
this.bulletAngleOffset = 0;
/**
* This is a variance added to the angle of Bullets when they are fired.
* If you fire from an angle of 90 and have a `bulletAngleVariance` of 20 then the actual
* angle of the Bullets will be between 70 and 110 degrees. This is a quick way to add a
* great 'spread' effect to a Weapon.
* @type {number}
* @default
*/
this.bulletAngleVariance = 0;
/**
* The initial velocity of fired bullets, in pixels per second.
* @type {number}
* @default
*/
this.bulletSpeed = 200;
/**
* This is a variance added to the speed of Bullets when they are fired.
* If bullets have a {@link #bulletSpeed} value of 200, and a `bulletSpeedVariance` of 50
* then the actual speed of the Bullets will be between 150 and 250 pixels per second.
* @type {number}
* @default
*/
this.bulletSpeedVariance = 0;
/**
* If you've set {@link #bulletKillType} to `Phaser.Weapon.KILL_LIFESPAN` this controls the amount
* of lifespan the Bullets have set on launch. The value is given in milliseconds.
* When a Bullet hits its lifespan limit it will be automatically killed.
* @type {number}
* @default
*/
this.bulletLifespan = 0;
/**
* If you've set {@link #bulletKillType} to `Phaser.Weapon.KILL_DISTANCE` this controls the distance
* the Bullet can travel before it is automatically killed. The distance is given in pixels.
* @type {number}
* @default
*/
this.bulletKillDistance = 0;
/**
* This is the amount of {@link Phaser.Physics.Arcade.Body#gravity} added to the Bullets physics body when fired.
* Gravity is expressed in pixels / second / second.
* @type {Phaser.Point}
*/
this.bulletGravity = new Phaser.Point(0, 0);
/**
* Bullets can optionally adjust their rotation in-flight to match their velocity.
* This can create the effect of a bullet 'pointing' to the path it is following, for example
* an arrow being fired from a bow, and works especially well when added to {@link #bulletGravity}.
* @type {boolean}
* @default
*/
this.bulletRotateToVelocity = false;
/**
* The Texture Key that the Bullets use when rendering.
* Changing this has no effect on bullets in-flight, only on newly spawned bullets.
* @type {string}
* @default
*/
this.bulletKey = '';
/**
* The Texture Frame that the Bullets use when rendering.
* Changing this has no effect on bullets in-flight, only on newly spawned bullets.
* @type {string|integer}
* @default
*/
this.bulletFrame = '';
/**
* Private var that holds the public `bulletClass` property.
* @type {function}
* @private
*/
this._bulletClass = Phaser.Bullet;
/**
* Private var that holds the public `bulletCollideWorldBounds` property.
* @type {boolean}
* @private
*/
this._bulletCollideWorldBounds = false;
/**
* Private var that holds the public `bulletKillType` property.
* @type {integer}
* @private
*/
this._bulletKillType = Phaser.Weapon.KILL_WORLD_BOUNDS;
/**
* Holds internal data about custom bullet body sizes.
*
* @type {Object}
* @private
*/
this._data = {
customBody: false,
width: 0,
height: 0,
offsetX: 0,
offsetY: 0
};
/**
* This Rectangle defines the bounds that are used when determining if a Bullet should be killed or not.
* It's used in combination with {@link #bulletKillType} when that is set to either `Phaser.Weapon.KILL_WEAPON_BOUNDS`
* or `Phaser.Weapon.KILL_STATIC_BOUNDS`. If you are not using either of these kill types then the bounds are ignored.
* If you are tracking a Sprite or Point then the bounds are centered on that object every frame.
*
* @type {Phaser.Rectangle}
*/
this.bounds = new Phaser.Rectangle();
/**
* The Rectangle used to calculate the bullet bounds from.
*
* @type {Phaser.Rectangle}
* @private
*/
this.bulletBounds = game.world.bounds;
/**
* This array stores the frames added via @link #setBulletFrames.
*
* @type {Array}
* @protected
*/
this.bulletFrames = [];
/**
* The index of the frame within {@link #bulletFrames} that is currently being used.
* This value is only used if {@link #bulletFrameCycle} is set to `true`.
* @type {number}
* @private
*/
this.bulletFrameIndex = 0;
/**
* An internal object that stores the animation data added via {@link #addBulletAnimation}.
* @type {Object}
* @private
*/
this.anims = {};
/**
* The onFire Signal is dispatched each time {@link #fire} is called, and a Bullet is
* _successfully_ launched. The callback is set two arguments: a reference to the bullet sprite itself,
* and a reference to the Weapon that fired the bullet.
*
* @type {Phaser.Signal}
*/
this.onFire = new Phaser.Signal();
/**
* The onKill Signal is dispatched each time a Bullet that is in-flight is killed. This can be the result
* of leaving the Weapon bounds, an expiring lifespan, or exceeding a specified distance.
* The callback is sent one argument: A reference to the bullet sprite itself.
*
* @type {Phaser.Signal}
*/
this.onKill = new Phaser.Signal();
/**
* The onFireLimit Signal is dispatched if {@link #fireLimit} is > 0, and a bullet launch takes the number
* of shots fired to equal the fire limit.
* The callback is sent two arguments: A reference to this Weapon, and the value of
* {@link #fireLimit}.
*
* @type {Phaser.Signal}
*/
this.onFireLimit = new Phaser.Signal();
/**
* The Sprite currently being tracked by the Weapon, if any.
* This is set via the {@link #trackSprite} method.
*
* @type {Phaser.Sprite|Object}
*/
this.trackedSprite = null;
/**
* The Pointer currently being tracked by the Weapon, if any.
* This is set via the {@link #trackPointer} method.
*
* @type {Phaser.Pointer}
*/
this.trackedPointer = null;
/**
* If you want this Weapon to be able to fire more than 1 bullet in a single
* update, then set this property to `true`. When `true` the Weapon plugin won't
* set the shot / firing timers until the `postRender` phase of the game loop.
* This means you can call `fire` (and similar methods) as often as you like in one
* single game update.
*
* @type {boolean}
* @default
*/
this.multiFire = false;
/**
* Internal multiFire test flag.
*
* @type {boolean}
*/
this._hasFired = false;
/**
* If the Weapon is tracking a Sprite, should it also track the Sprites rotation?
* This is useful for a game such as Asteroids, where you want the weapon to fire based
* on the sprites rotation.
*
* @type {boolean}
* @default
*/
this.trackRotation = false;
/**
* The Track Offset is a Point object that allows you to specify a pixel offset that bullets use
* when launching from a tracked Sprite or Pointer. For example if you've got a bullet that is 2x2 pixels
* in size, but you're tracking a Sprite that is 32x32, then you can set `trackOffset.x = 16` to have
* the bullet launched from the center of the Sprite.
*
* @type {Phaser.Point}
*/
this.trackOffset = new Phaser.Point();
/**
* Internal firing rate time tracking variable.
*
* @type {number}
* @private
*/
this._nextFire = 0;
/**
* Internal firing rate time tracking variable used by multiFire.
*
* @type {number}
* @private
*/
this._tempNextFire = 0;
/**
* Internal firing rotation tracking point.
*
* @type {Phaser.Point}
* @private
*/
this._rotatedPoint = new Phaser.Point();
};
Phaser.Weapon.prototype = Object.create(Phaser.Plugin.prototype);
Phaser.Weapon.prototype.constructor = Phaser.Weapon;
/**
* A {@link #bulletKillType} constant that stops the bullets from ever being destroyed automatically.
* @constant
* @type {integer}
*/
Phaser.Weapon.KILL_NEVER = 0;
/**
* A {@link #bulletKillType} constant that automatically kills the bullets when their {@link #bulletLifespan} expires.
* @constant
* @type {integer}
*/
Phaser.Weapon.KILL_LIFESPAN = 1;
/**
* A {@link #bulletKillType} constant that automatically kills the bullets after they
* exceed the {@link #bulletKillDistance} from their original firing position.
* @constant
* @type {integer}
*/
Phaser.Weapon.KILL_DISTANCE = 2;
/**
* A {@link #bulletKillType} constant that automatically kills the bullets when they leave the {@link #bounds} rectangle.
* @constant
* @type {integer}
*/
Phaser.Weapon.KILL_WEAPON_BOUNDS = 3;
/**
* A {@link #bulletKillType} constant that automatically kills the bullets when they leave the {@link Phaser.Camera#bounds} rectangle.
* @constant
* @type {integer}
*/
Phaser.Weapon.KILL_CAMERA_BOUNDS = 4;
/**
* A {@link #bulletKillType} constant that automatically kills the bullets when they leave the {@link Phaser.World#bounds} rectangle.
* @constant
* @type {integer}
*/
Phaser.Weapon.KILL_WORLD_BOUNDS = 5;
/**
* A {@link #bulletKillType} constant that automatically kills the bullets when they leave the {@link #bounds} rectangle.
* @constant
* @type {integer}
*/
Phaser.Weapon.KILL_STATIC_BOUNDS = 6;
/**
* This method performs two actions: First it will check to see if the {@link #bullets} Group exists or not,
* and if not it creates it, adding it the `group` given as the 4th argument.
*
* Then it will seed the bullet pool with the `quantity` number of Bullets, using the texture key and frame
* provided (if any).
*
* If for example you set the quantity to be 10, then this Weapon will only ever be able to have 10 bullets
* in-flight simultaneously. If you try to fire an 11th bullet then nothing will happen until one, or more, of
* the in-flight bullets have been killed, freeing them up for use by the Weapon again.
*
* If you do not wish to have a limit set, then pass in -1 as the quantity. In this instance the Weapon will
* keep increasing the size of the bullet pool as needed. It will never reduce the size of the pool however,
* so be careful it doesn't grow too large.
*
* You can either set the texture key and frame here, or via the {@link #bulletKey} and {@link #bulletFrame}
* properties. You can also animate bullets, or set them to use random frames. All Bullets belonging to a
* single Weapon instance must share the same texture key however.
*
* @method Phaser.Weapon#createBullets
* @param {integer} [quantity=1] - The quantity of bullets to seed the Weapon with. If -1 it will set the pool to automatically expand.
* @param {string} [key] - The Game.cache key of the image that this Sprite will use.
* @param {integer|string} [frame] - If the Sprite image contains multiple frames you can specify which one to use here.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.createBullets = function (quantity, key, frame, group)
{
if (quantity === undefined) { quantity = 1; }
if (group === undefined) { group = this.game.world; }
if (this.bullets && !this.bullets.game)
{
this.bullets = null;
}
if (!this.bullets)
{
this.bullets = this.game.add.physicsGroup(Phaser.Physics.ARCADE, group);
this.bullets.classType = this._bulletClass;
}
if (quantity !== 0)
{
if (quantity === -1)
{
this.autoExpandBulletsGroup = true;
quantity = 1;
}
this.bullets.createMultiple(quantity, key, frame);
this.bullets.setAll('data.bulletManager', this);
this.bulletKey = key;
this.bulletFrame = frame;
}
return this;
};
/**
* Call a function on each in-flight bullet in this Weapon.
*
* See {@link Phaser.Group#forEachExists forEachExists} for more details.
*
* @method Phaser.Weapon#forEach
* @param {function} callback - The function that will be called for each applicable child. The child will be passed as the first argument.
* @param {object} callbackContext - The context in which the function should be called (usually 'this').
* @param {...any} [args=(none)] - Additional arguments to pass to the callback function, after the child item.
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.forEach = function (callback, callbackContext)
{
this.bullets.forEachExists(callback, callbackContext, arguments);
return this;
};
/**
* Sets {@link Phaser.Physics.Arcade.Body#enable} to `false` on each bullet in this Weapon.
* This has the effect of stopping them in-flight should they be moving.
* It also stops them being able to be checked for collision.
*
* @method Phaser.Weapon#pauseAll
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.pauseAll = function ()
{
this.bullets.setAll('body.enable', false);
return this;
};
/**
* Sets {@link Phaser.Physics.Arcade.Body#enable} to `true` on each bullet in this Weapon.
* This has the effect of resuming their motion should they be in-flight.
* It also enables them for collision checks again.
*
* @method Phaser.Weapon#resumeAll
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.resumeAll = function ()
{
this.bullets.setAll('body.enable', true);
return this;
};
/**
* Calls {@link Phaser.Bullet#kill} on every in-flight bullet in this Weapon.
* Also re-enables their physics bodies, should they have been disabled via {@link #pauseAll}.
*
* @method Phaser.Weapon#killAll
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.killAll = function ()
{
this.bullets.callAllExists('kill', true);
this.bullets.setAll('body.enable', true);
return this;
};
/**
* Resets the {@link #shots} counter back to zero. This is used when you've set
* {@link #fireLimit} and have hit (or just wish to reset) your limit.
*
* @method Phaser.Weapon#resetShots
* @param {integer} [newLimit] - Optionally set a new {@link #fireLimit}.
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.resetShots = function (newLimit)
{
this.shots = 0;
if (newLimit !== undefined)
{
this.fireLimit = newLimit;
}
return this;
};
/**
* Destroys this Weapon. It removes itself from the PluginManager, destroys
* the {@link #bullets} Group, and nulls internal references.
*
* @method Phaser.Weapon#destroy
*/
Phaser.Weapon.prototype.destroy = function ()
{
this.parent.remove(this, false);
this.bullets.destroy();
this.game = null;
this.parent = null;
this.active = false;
this.visible = false;
};
/**
* Internal update method, called by the PluginManager.
*
* @method Phaser.Weapon#update
* @protected
*/
Phaser.Weapon.prototype.update = function ()
{
if (this._bulletKillType === Phaser.Weapon.KILL_WEAPON_BOUNDS)
{
if (this.trackedSprite)
{
this.trackedSprite.updateTransform();
this.bounds.centerOn(this.trackedSprite.worldPosition.x, this.trackedSprite.worldPosition.y);
}
else if (this.trackedPointer)
{
this.bounds.centerOn(this.trackedPointer.worldX, this.trackedPointer.worldY);
}
}
if (this.autofire)
{
this.fire();
}
};
/**
* Internal update method, called by the PluginManager.
*
* @method Phaser.Weapon#postRender
* @protected
*/
Phaser.Weapon.prototype.postRender = function ()
{
if (!this.multiFire || !this._hasFired)
{
return;
}
this._hasFired = false;
this._nextFire = this._tempNextFire;
};
/**
* Sets this Weapon to track the given Sprite, or any Object with a public {@link Phaser.Component.Core#world world} Point object.
* When a Weapon tracks a Sprite it will automatically update its {@link #fireFrom} value to match the Sprite's
* position within the Game World, adjusting the coordinates based on the offset arguments.
*
* This allows you to lock a Weapon to a Sprite, so that bullets are always launched from its location.
*
* Calling `trackSprite` will reset {@link #trackedPointer} to null, should it have been set, as you can
* only track _either_ a Sprite, or a Pointer, at once, but not both.
*
* @method Phaser.Weapon#trackSprite
* @param {Phaser.Sprite|Object} sprite - The Sprite to track the position of.
* @param {integer} [offsetX=0] - The horizontal offset from the Sprites position to be applied to the Weapon.
* @param {integer} [offsetY=0] - The vertical offset from the Sprites position to be applied to the Weapon.
* @param {boolean} [trackRotation=false] - Should the Weapon also track the Sprites rotation?
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.trackSprite = function (sprite, offsetX, offsetY, trackRotation)
{
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
if (trackRotation === undefined) { trackRotation = false; }
this.trackedPointer = null;
this.trackedSprite = sprite;
this.trackRotation = trackRotation;
this.trackOffset.set(offsetX, offsetY);
return this;
};
/**
* Sets this Weapon to track the given Pointer.
* When a Weapon tracks a Pointer it will automatically update its {@link #fireFrom} value to match the Pointer's
* position within the Game World, adjusting the coordinates based on the offset arguments.
*
* This allows you to lock a Weapon to a Pointer, so that bullets are always launched from its location.
*
* Calling `trackPointer` will reset {@link #trackedSprite} to null, should it have been set, as you can
* only track _either_ a Pointer, or a Sprite, at once, but not both.
*
* @method Phaser.Weapon#trackPointer
* @param {Phaser.Pointer} [pointer] - The Pointer to track the position of. Defaults to `Input.activePointer` if not specified.
* @param {integer} [offsetX=0] - The horizontal offset from the Pointers position to be applied to the Weapon.
* @param {integer} [offsetY=0] - The vertical offset from the Pointers position to be applied to the Weapon.
* @return {Phaser.Weapon} This Weapon instance.
*/
Phaser.Weapon.prototype.trackPointer = function (pointer, offsetX, offsetY)
{
if (pointer === undefined) { pointer = this.game.input.activePointer; }
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
this.trackedPointer = pointer;
this.trackedSprite = null;
this.trackRotation = false;
this.trackOffset.set(offsetX, offsetY);
return this;
};
/**
* Attempts to fire multiple bullets from the positions defined in the given array.
*
* If you provide a `from` argument, or if there is a tracked Sprite or Pointer, then
* the positions are treated as __offsets__ from the given objects position.
*
* If `from` is undefined, and there is no tracked object, then the bullets are fired
* from the given positions, as they exist in the world.
*
* Calling this method sets {@link #multiFire} to `true`.
*
* If there are not enough bullets available in the pool, and the pool cannot be extended,
* then this method may not fire from all of the given positions.
*
* When the bullets are launched they have their texture and frame updated, as required.
* The velocity of the bullets are calculated based on Weapon properties like {@link #bulletSpeed}.
*
* @method Phaser.Weapon#fireMany
* @param {array} positions - An array of positions. Each position can be any Object, as long as it has public `x` and `y` properties, such as Phaser.Point, { x: 0, y: 0 }, Phaser.Sprite, etc.
* @param {Phaser.Sprite|Phaser.Point|Object|string} [from] - Optionally fires the bullets **from** the `x` and `y` properties of this object, _instead_ of any {@link #trackedSprite} or `trackedPointer` that is set.
* @return {array} An array containing all of the fired Phaser.Bullet objects, if a launch was successful, otherwise an empty array.
*/
Phaser.Weapon.prototype.fireMany = function (positions, from)
{
this.multiFire = true;
var bullets = [];
var _this = this;
if (from || this.trackedSprite || this.trackedPointer)
{
positions.forEach(function (offset)
{
bullets.push(_this.fire(from, null, null, offset.x, offset.y));
});
}
else
{
positions.forEach(function (position)
{
bullets.push(_this.fire(position));
});
}
return bullets;
};
/**
* Attempts to fire a single Bullet from a tracked Sprite or Pointer, but applies an offset
* to the position first. This is the same as calling {@link #fire} and passing in the offset arguments.
*
* If there are no more bullets available in the pool, and the pool cannot be extended,
* then this method returns `null`. It will also return `null` if not enough time has expired since the last time
* the Weapon was fired, as defined in the {@link #fireRate} property.
*
* Otherwise the first available bullet is selected, launched, and returned.
*
* When the bullet is launched it has its texture and frame updated, as required. The velocity of the bullet is
* calculated based on Weapon properties like {@link #bulletSpeed}.
*
* If you wish to fire multiple bullets in a single game update, then set {@link #multiFire} to `true`
* and you can call this method as many times as you like, per loop. See also {@link #fireMany}.
*
* @method Phaser.Weapon#fireOffset
* @param {number} [offsetX=0] - The horizontal offset from the position of the tracked Sprite or Pointer, as set with {@link #trackSprite}.
* @param {number} [offsetY=0] - The vertical offset from the position of the tracked Sprite or Pointer, as set with {@link #trackSprite}.
* @return {Phaser.Bullet} The fired bullet, if a launch was successful, otherwise `null`.
*/
Phaser.Weapon.prototype.fireOffset = function (offsetX, offsetY)
{
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
return this.fire(null, null, null, offsetX, offsetY);
};
/**
* Attempts to fire a single Bullet. If there are no more bullets available in the pool, and the pool cannot be extended,
* then this method returns `null`. It will also return `null` if not enough time has expired since the last time
* the Weapon was fired, as defined in the {@link #fireRate} property.
*
* Otherwise the first available bullet is selected, launched, and returned.
*
* The arguments are all optional, but allow you to control both where the bullet is launched from, and aimed at.
*
* If you don't provide any of the arguments then it uses those set via properties such as {@link #trackedSprite},
* {@link #bulletAngle} and so on.
*
* When the bullet is launched it has its texture and frame updated, as required. The velocity of the bullet is
* calculated based on Weapon properties like `bulletSpeed`.
*
* If you wish to fire multiple bullets in a single game update, then set `Weapon.multiFire = true`
* and you can call `fire` as many times as you like, per loop. Multiple fires in a single update
* only counts once towards the `shots` total, but you will still receive a Signal for each bullet.
*
* @method Phaser.Weapon#fire
* @param {Phaser.Sprite|Phaser.Point|Object|string} [from] - Optionally fires the bullet **from** the `x` and `y` properties of this object. If set this overrides {@link #trackedSprite} or `trackedPointer`. Pass `null` to ignore it.
* @param {number} [x] - The x coordinate, in world space, to fire the bullet **towards**. If left as `undefined`, or `null`, the bullet direction is based on its angle.
* @param {number} [y] - The y coordinate, in world space, to fire the bullet **towards**. If left as `undefined`, or `null`, the bullet direction is based on its angle.
* @param {number} [offsetX=0] - If the bullet is fired from a tracked Sprite or Pointer, or the `from` argument is set, this applies a horizontal offset from the launch position.
* @param {number} [offsetY=0] - If the bullet is fired from a tracked Sprite or Pointer, or the `from` argument is set, this applies a vertical offset from the launch position.
* @return {Phaser.Bullet} The fired bullet, if a launch was successful, otherwise `null`.
*/
Phaser.Weapon.prototype.fire = function (from, x, y, offsetX, offsetY)
{
if (x === undefined) { x = null; }
if (y === undefined) { y = null; }
if (this.game.time.now < this._nextFire || (this.fireLimit > 0 && this.shots === this.fireLimit))
{
return null;
}
var speed = this.bulletSpeed;
// Apply +- speed variance
if (this.bulletSpeedVariance !== 0)
{
speed += Phaser.Math.between(-this.bulletSpeedVariance, this.bulletSpeedVariance);
}
if (from)
{
if (this.fireFrom.width > 1)
{
this.fireFrom.centerOn(from.x, from.y);
}
else
{
this.fireFrom.x = from.x;
this.fireFrom.y = from.y;
}
}
else if (this.trackedSprite)
{
if (this.trackRotation)
{
this._rotatedPoint.set(this.trackedSprite.world.x + this.trackOffset.x, this.trackedSprite.world.y + this.trackOffset.y);
this._rotatedPoint.rotate(this.trackedSprite.world.x, this.trackedSprite.world.y, this.trackedSprite.worldRotation);
if (this.fireFrom.width > 1)
{
this.fireFrom.centerOn(this._rotatedPoint.x, this._rotatedPoint.y);
}
else
{
this.fireFrom.x = this._rotatedPoint.x;
this.fireFrom.y = this._rotatedPoint.y;
}
}
else
if (this.fireFrom.width > 1)
{
this.fireFrom.centerOn(this.trackedSprite.world.x + this.trackOffset.x, this.trackedSprite.world.y + this.trackOffset.y);
}
else
{
this.fireFrom.x = this.trackedSprite.world.x + this.trackOffset.x;
this.fireFrom.y = this.trackedSprite.world.y + this.trackOffset.y;
}
if (this.bulletInheritSpriteSpeed)
{
speed += this.trackedSprite.body.speed;
}
}
else if (this.trackedPointer)
{
if (this.fireFrom.width > 1)
{
this.fireFrom.centerOn(this.trackedPointer.world.x + this.trackOffset.x, this.trackedPointer.world.y + this.trackOffset.y);
}
else
{
this.fireFrom.x = this.trackedPointer.world.x + this.trackOffset.x;
this.fireFrom.y = this.trackedPointer.world.y + this.trackOffset.y;
}
}
if (offsetX !== undefined)
{
this.fireFrom.x += offsetX;
}
if (offsetY !== undefined)
{
this.fireFrom.y += offsetY;
}
var fromX = (this.fireFrom.width > 1) ? this.fireFrom.randomX : this.fireFrom.x;
var fromY = (this.fireFrom.height > 1) ? this.fireFrom.randomY : this.fireFrom.y;
var angle = (this.trackRotation) ? this.trackedSprite.angle : this.fireAngle;
// The position (in world space) to fire the bullet towards, if set
if (x !== null && y !== null)
{
angle = this.game.math.radToDeg(Math.atan2(y - fromY, x - fromX));
}
// Apply +- angle variance
if (this.bulletAngleVariance !== 0)
{
angle += Phaser.Math.between(-this.bulletAngleVariance, this.bulletAngleVariance);
}
var moveX = 0;
var moveY = 0;
// Avoid sin/cos for right-angled shots
if (angle === 0 || angle === 180)
{
moveX = Math.cos(this.game.math.degToRad(angle)) * speed;
}
else if (angle === 90 || angle === 270)
{
moveY = Math.sin(this.game.math.degToRad(angle)) * speed;
}
else
{
moveX = Math.cos(this.game.math.degToRad(angle)) * speed;
moveY = Math.sin(this.game.math.degToRad(angle)) * speed;
}
var bullet = null;
if (this.autoExpandBulletsGroup)
{
bullet = this.bullets.getFirstExists(false, true, fromX, fromY, this.bulletKey, this.bulletFrame);
bullet.data.bulletManager = this;
}
else
{
bullet = this.bullets.getFirstExists(false);
}
if (bullet)
{
bullet.reset(fromX, fromY);
bullet.data.fromX = fromX;
bullet.data.fromY = fromY;
bullet.data.killType = this.bulletKillType;
bullet.data.killDistance = this.bulletKillDistance;
bullet.data.rotateToVelocity = this.bulletRotateToVelocity;
if (this.bulletKillType === Phaser.Weapon.KILL_LIFESPAN)
{
bullet.lifespan = this.bulletLifespan;
}
bullet.angle = angle + this.bulletAngleOffset;
// Frames and Animations
if (this.bulletAnimation !== '')
{
if (bullet.animations.getAnimation(this.bulletAnimation) === null)
{
var anim = this.anims[this.bulletAnimation];
bullet.animations.add(anim.name, anim.frames, anim.frameRate, anim.loop, anim.useNumericIndex);
}
bullet.animations.play(this.bulletAnimation);
}
else
if (this.bulletFrameCycle)
{
bullet.frame = this.bulletFrames[this.bulletFrameIndex];
this.bulletFrameIndex++;
if (this.bulletFrameIndex >= this.bulletFrames.length)
{
this.bulletFrameIndex = 0;
}
}
else if (this.bulletFrameRandom)
{
bullet.frame = this.bulletFrames[Math.floor(Math.random() * this.bulletFrames.length)];
}
if (bullet.data.bodyDirty)
{
if (this._data.customBody)
{
bullet.body.setSize(this._data.width, this._data.height, this._data.offsetX, this._data.offsetY);
}
bullet.body.collideWorldBounds = this.bulletCollideWorldBounds;
bullet.data.bodyDirty = false;
}
bullet.body.velocity.set(moveX, moveY);
bullet.body.gravity.set(this.bulletGravity.x, this.bulletGravity.y);
var next = 0;
if (this.bulletSpeedVariance !== 0)
{
var rate = this.fireRate;
rate += Phaser.Math.between(-this.fireRateVariance, this.fireRateVariance);
if (rate < 0)
{
rate = 0;
}
next = this.game.time.now + rate;
}
else
{
next = this.game.time.now + this.fireRate;
}
if (this.multiFire)
{
if (!this._hasFired)
{
// We only add 1 to the 'shots' count for multiFire shots
this._hasFired = true;
this._tempNextFire = next;
this.shots++;
}
}
else
{
this._nextFire = next;
this.shots++;
}
this.onFire.dispatch(bullet, this, speed);
if (this.fireLimit > 0 && this.shots === this.fireLimit)
{
this.onFireLimit.dispatch(this, this.fireLimit);
}
}
return bullet;
};
/**
* Fires a bullet **at** the given Pointer. The bullet will be launched from the {@link #fireFrom} position,
* or from a Tracked Sprite or Pointer, if you have one set.
*
* @method Phaser.Weapon#fireAtPointer
* @param {Phaser.Pointer} [pointer] - The Pointer to fire the bullet towards.
* @return {Phaser.Bullet} The fired bullet if successful, null otherwise.
*/
Phaser.Weapon.prototype.fireAtPointer = function (pointer)
{
if (pointer === undefined) { pointer = this.game.input.activePointer; }
return this.fire(null, pointer.worldX, pointer.worldY);
};
/**
* Fires a bullet **at** the given Sprite. The bullet will be launched from the {@link #fireFrom} position,
* or from a Tracked Sprite or Pointer, if you have one set.
*
* @method Phaser.Weapon#fireAtSprite
* @param {Phaser.Sprite} [sprite] - The Sprite to fire the bullet towards.
* @return {Phaser.Bullet} The fired bullet if successful, null otherwise.
*/
Phaser.Weapon.prototype.fireAtSprite = function (sprite)
{
return this.fire(null, sprite.world.x, sprite.world.y);
};
/**
* Fires a bullet **at** the given coordinates. The bullet will be launched from the {@link #fireFrom} position,
* or from a Tracked Sprite or Pointer, if you have one set.
*
* @method Phaser.Weapon#fireAtXY
* @param {number} [x] - The x coordinate, in world space, to fire the bullet towards.
* @param {number} [y] - The y coordinate, in world space, to fire the bullet towards.
* @return {Phaser.Bullet} The fired bullet if successful, null otherwise.
*/
Phaser.Weapon.prototype.fireAtXY = function (x, y)
{
return this.fire(null, x, y);
};
/**
* You can modify the size of the physics Body the Bullets use to be any dimension you need.
* This allows you to make it smaller, or larger, than the parent Sprite.
* You can also control the x and y offset of the Body. This is the position of the
* Body relative to the top-left of the Sprite _texture_.
*
* For example: If you have a Sprite with a texture that is 80x100 in size,
* and you want the physics body to be 32x32 pixels in the middle of the texture, you would do:
*
* `setSize(32 / Math.abs(this.scale.x), 32 / Math.abs(this.scale.y), 24, 34)`
*
* Where the first two parameters are the new Body size (32x32 pixels) relative to the Sprite's scale.
* 24 is the horizontal offset of the Body from the top-left of the Sprites texture, and 34
* is the vertical offset.
*
* @method Phaser.Weapon#setBulletBodyOffset
* @param {number} width - The width of the Body.
* @param {number} height - The height of the Body.
* @param {number} [offsetX] - The X offset of the Body from the top-left of the Sprites texture.
* @param {number} [offsetY] - The Y offset of the Body from the top-left of the Sprites texture.
* @return {Phaser.Weapon} The Weapon Plugin.
*/
Phaser.Weapon.prototype.setBulletBodyOffset = function (width, height, offsetX, offsetY)
{
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
this._data.customBody = true;
this._data.width = width;
this._data.height = height;
this._data.offsetX = offsetX;
this._data.offsetY = offsetY;
// Update all bullets in the pool
this.bullets.callAll('body.setSize', 'body', width, height, offsetX, offsetY);
this.bullets.setAll('data.bodyDirty', false);
return this;
};
/**
* Sets the texture frames that the bullets can use when being launched.
*
* This is intended for use when you've got numeric based frames, such as those loaded via a Sprite Sheet.
*
* It works by calling `Phaser.ArrayUtils.numberArray` internally, using the min and max values
* provided. Then it sets the frame index to be zero.
*
* You can optionally set the cycle and random booleans, to allow bullets to cycle through the frames
* when they're fired, or pick one at random.
*
* @method Phaser.Weapon#setBulletFrames
* @param {integer} min - The minimum value the frame can be. Usually zero.
* @param {integer} max - The maximum value the frame can be.
* @param {boolean} [cycle=true] - Should the bullet frames cycle as they are fired?
* @param {boolean} [random=false] - Should the bullet frames be picked at random as they are fired?
* @return {Phaser.Weapon} The Weapon Plugin.
*/
Phaser.Weapon.prototype.setBulletFrames = function (min, max, cycle, random)
{
if (cycle === undefined) { cycle = true; }
if (random === undefined) { random = false; }
this.bulletFrames = Phaser.ArrayUtils.numberArray(min, max);
this.bulletFrameIndex = 0;
this.bulletFrameCycle = cycle;
this.bulletFrameRandom = random;
return this;
};
/**
* Adds a new animation under the given key. Optionally set the frames, frame rate and loop.
* The arguments are all the same as for `Animation.add`, and work in the same way.
*
* {@link #bulletAnimation} will be set to this animation after it's created. From that point on, all
* bullets fired will play using this animation. You can swap between animations by calling this method
* several times, and then just changing the {@link #bulletAnimation} property to the name of the animation
* you wish to play for the next launched bullet.
*
* If you wish to stop using animations at all, set {@link #bulletAnimation} to '' (an empty string).
*
* @method Phaser.Weapon#addBulletAnimation
* @param {string} name - The unique (within the Weapon instance) name for the animation, i.e. "fire", "blast".
* @param {Array} [frames=null] - An array of numbers/strings that correspond to the frames to add to this animation and in which order. e.g. [1, 2, 3] or ['run0', 'run1', run2]). If null then all frames will be used.
* @param {number} [frameRate=60] - The speed at which the animation should play. The speed is given in frames per second.
* @param {boolean} [loop=false] - Whether or not the animation is looped or just plays once.
* @param {boolean} [useNumericIndex=true] - Are the given frames using numeric indexes (default) or strings?
* @return {Phaser.Weapon} The Weapon Plugin.
*/
Phaser.Weapon.prototype.addBulletAnimation = function (name, frames, frameRate, loop, useNumericIndex)
{
this.anims[name] = {
name: name,
frames: frames,
frameRate: frameRate,
loop: loop,
useNumericIndex: useNumericIndex
};
// Add the animation to any existing bullets in the pool
this.bullets.callAll('animations.add', 'animations', name, frames, frameRate, loop, useNumericIndex);
this.bulletAnimation = name;
return this;
};
/**
* Uses `Game.Debug` to draw some useful information about this Weapon, including the number of bullets
* both in-flight, and available. And optionally the physics debug bodies of the bullets.
*
* @method Phaser.Weapon#debug
* @param {integer} [x=16] - The coordinate, in screen space, at which to draw the Weapon debug data.
* @param {integer} [y=32] - The coordinate, in screen space, at which to draw the Weapon debug data.
* @param {boolean} [debugBodies=false] - Optionally draw the physics body of every bullet in-flight.
*/
Phaser.Weapon.prototype.debug = function (x, y, debugBodies)
{
if (x === undefined) { x = 16; }
if (y === undefined) { y = 32; }
if (debugBodies === undefined) { debugBodies = false; }
this.game.debug.text('Weapon Plugin', x, y);
this.game.debug.text('Bullets Alive: ' + this.bullets.total + ' - Total: ' + this.bullets.length, x, y + 24);
if (debugBodies)
{
this.bullets.forEachExists(this.game.debug.body, this.game.debug, 'rgba(255, 0, 255, 0.8)');
}
};
/**
* The Class of the bullets that are launched by this Weapon. Defaults to {@link Phaser.Bullet}, but can be
* overridden before calling `createBullets` and set to your own class type.
*
* It should be a constructor function accepting `(game, x, y, key, frame)`.
*
* @name Phaser.Weapon#bulletClass
* @property {function} bulletClass
*/
Object.defineProperty(Phaser.Weapon.prototype, 'bulletClass', {
get: function ()
{
return this._bulletClass;
},
set: function (classType)
{
this._bulletClass = classType;
// `this.bullets` exists only after createBullets()
if (this.bullets)
{
this.bullets.classType = this._bulletClass;
}
}
});
/**
* This controls how the bullets will be killed. The default is `Phaser.Weapon.KILL_WORLD_BOUNDS`.
*
* There are 7 different "kill types" available:
*
* * `Phaser.Weapon.KILL_NEVER`
* The bullets are never destroyed by the Weapon. It's up to you to destroy them via your own code.
*
* * `Phaser.Weapon.KILL_LIFESPAN`
* The bullets are automatically killed when their {@link #bulletLifespan} amount expires.
*
* * `Phaser.Weapon.KILL_DISTANCE`
* The bullets are automatically killed when they exceed {@link #bulletKillDistance} pixels away from their original launch position.
*
* * `Phaser.Weapon.KILL_WEAPON_BOUNDS`
* The bullets are automatically killed when they no longer intersect with the {@link #bounds} rectangle.
*
* * `Phaser.Weapon.KILL_CAMERA_BOUNDS`
* The bullets are automatically killed when they no longer intersect with the {@link Phaser.Camera#bounds} rectangle.
*
* * `Phaser.Weapon.KILL_WORLD_BOUNDS`
* The bullets are automatically killed when they no longer intersect with the {@link Phaser.World#bounds} rectangle.
*
* * `Phaser.Weapon.KILL_STATIC_BOUNDS`
* The bullets are automatically killed when they no longer intersect with the {@link #bounds} rectangle.
* The difference between static bounds and weapon bounds, is that a static bounds will never be adjusted to
* match the position of a tracked sprite or pointer.
*
* @name Phaser.Weapon#bulletKillType
* @property {integer} bulletKillType
*/
Object.defineProperty(Phaser.Weapon.prototype, 'bulletKillType', {
get: function ()
{
return this._bulletKillType;
},
set: function (type)
{
switch (type)
{
case Phaser.Weapon.KILL_STATIC_BOUNDS:
case Phaser.Weapon.KILL_WEAPON_BOUNDS:
this.bulletBounds = this.bounds;
break;
case Phaser.Weapon.KILL_CAMERA_BOUNDS:
this.bulletBounds = this.game.camera.view;
break;
case Phaser.Weapon.KILL_WORLD_BOUNDS:
this.bulletBounds = this.game.world.bounds;
break;
}
this._bulletKillType = type;
}
});
/**
* Should bullets collide with the World bounds or not?
*
* @name Phaser.Weapon#bulletCollideWorldBounds
* @property {boolean} bulletCollideWorldBounds
*/
Object.defineProperty(Phaser.Weapon.prototype, 'bulletCollideWorldBounds', {
get: function ()
{
return this._bulletCollideWorldBounds;
},
set: function (value)
{
this._bulletCollideWorldBounds = value;
this.bullets.setAll('body.collideWorldBounds', value);
this.bullets.setAll('data.bodyDirty', false);
}
});
/**
* The x coordinate from which bullets are fired. This is the same as `Weapon.fireFrom.x`, and
* can be overridden by the {@link #fire} arguments.
*
* @name Phaser.Weapon#x
* @property {number} x
*/
Object.defineProperty(Phaser.Weapon.prototype, 'x', {
get: function ()
{
return this.fireFrom.x;
},
set: function (value)
{
this.fireFrom.x = value;
}
});
/**
* The y coordinate from which bullets are fired. This is the same as `Weapon.fireFrom.y`, and
* can be overridden by the {@link #fire} arguments.
*
* @name Phaser.Weapon#y
* @property {number} y
*/
Object.defineProperty(Phaser.Weapon.prototype, 'y', {
get: function ()
{
return this.fireFrom.y;
},
set: function (value)
{
this.fireFrom.y = value;
}
});
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Create a new `Bullet` object. Bullets are used by the `Phaser.Weapon` class, and are normal Sprites,
* with a few extra properties in the data object to handle Weapon specific features.
*
* @class Phaser.Bullet
* @constructor
* @extends Phaser.Sprite
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {number} x - The x coordinate (in world space) to position the Particle at.
* @param {number} y - The y coordinate (in world space) to position the Particle at.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|PIXI.Texture} key - This is the image or texture used by the Particle during rendering. It can be a string which is a reference to the Cache entry, or an instance of a RenderTexture or PIXI.Texture.
* @param {string|number} frame - If this Particle is using part of a sprite sheet or texture atlas you can specify the exact frame to use by giving a string or numeric index.
*/
Phaser.Bullet = function (game, x, y, key, frame)
{
Phaser.Sprite.call(this, game, x, y, key, frame);
this.anchor.set(0.5);
this.data = {
bulletManager: null,
fromX: 0,
fromY: 0,
bodyDirty: true,
rotateToVelocity: false,
killType: 0,
killDistance: 0
};
};
Phaser.Bullet.prototype = Object.create(Phaser.Sprite.prototype);
Phaser.Bullet.prototype.constructor = Phaser.Bullet;
/**
* Kills the Bullet, freeing it up for re-use by the Weapon bullet pool.
* Also dispatches the `Weapon.onKill` signal.
*
* @method Phaser.Bullet#kill
* @memberof Phaser.Bullet
*/
Phaser.Bullet.prototype.kill = function ()
{
this.alive = false;
this.exists = false;
this.visible = false;
this.data.bulletManager.onKill.dispatch(this);
return this;
};
/**
* Updates the Bullet, killing as required.
*
* @method Phaser.Bullet#update
* @memberof Phaser.Bullet
*/
Phaser.Bullet.prototype.update = function ()
{
if (!this.exists)
{
return;
}
if (this.data.killType > Phaser.Weapon.KILL_LIFESPAN)
{
if (this.data.killType === Phaser.Weapon.KILL_DISTANCE)
{
if (this.game.physics.arcade.distanceToXY(this, this.data.fromX, this.data.fromY, true) > this.data.killDistance)
{
this.kill();
}
}
else
if (!this.data.bulletManager.bulletBounds.intersects(this))
{
this.kill();
}
}
if (this.data.rotateToVelocity)
{
this.rotation = this.body.velocity.atan();
}
if (this.data.bulletManager.bulletWorldWrap)
{
this.game.world.wrap(this, this.data.bulletManager.bulletWorldWrapPadding);
}
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Video object that takes a previously loaded Video from the Phaser Cache and handles playback of it.
*
* Alternatively it takes a getUserMedia feed from an active webcam and streams the contents of that to
* the Video instead (see `startMediaStream` method)
*
* The video can then be applied to a Sprite as a texture. If multiple Sprites share the same Video texture and playback
* changes (i.e. you pause the video, or seek to a new time) then this change will be seen across all Sprites simultaneously.
*
* Due to a bug in IE11 you cannot play a video texture to a Sprite in WebGL. For IE11 force Canvas mode.
*
* If you need each Sprite to be able to play a video fully independently then you will need one Video object per Sprite.
* Please understand the obvious performance implications of doing this, and the memory required to hold videos in RAM.
*
* On some mobile browsers such as iOS Safari, you cannot play a video until the user has explicitly touched the screen.
* This works in the same way as audio unlocking. Phaser will handle the touch unlocking for you, however unlike with audio
* it's worth noting that every single Video needs to be touch unlocked, not just the first one. You can use the `changeSource`
* method to try and work around this limitation, but see the method help for details.
*
* Small screen devices, especially iPod and iPhone will launch the video in its own native video player,
* outside of the Safari browser. There is no way to avoid this, it's a device imposed limitation.
*
* Note: On iOS if you need to detect when the user presses the 'Done' button (before the video ends)
* then you need to add your own event listener
*
* @class Phaser.Video
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {string|null} [key=null] - The key of the video file in the Phaser.Cache that this Video object will play. Set to `null` or leave undefined if you wish to use a webcam as the source. See `startMediaStream` to start webcam capture.
* @param {string|null} [url=null] - If the video hasn't been loaded then you can provide a full URL to the file here (make sure to set key to null)
*/
Phaser.Video = function (game, key, url)
{
if (key === undefined) { key = null; }
if (url === undefined) { url = null; }
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {string} key - The key of the Video in the Cache, if stored there. Will be `null` if this Video is using the webcam instead.
* @default null
*/
this.key = key;
/**
* @property {number} width - The width of the video in pixels.
* @default
*/
this.width = 0;
/**
* @property {number} height - The height of the video in pixels.
* @default
*/
this.height = 0;
/**
* @property {number} type - The const type of this object.
* @default
*/
this.type = Phaser.VIDEO;
/**
* @property {boolean} disableTextureUpload - If true this video will never send its image data to the GPU when its dirty flag is true. This only applies in WebGL.
*/
this.disableTextureUpload = false;
/**
* @property {boolean} touchLocked - true if this video is currently locked awaiting a touch event. This happens on some mobile devices, such as iOS.
* @default
*/
this.touchLocked = false;
/**
* @property {Phaser.Signal} onPlay - This signal is dispatched when the Video starts to play. It sends 3 parameters: a reference to the Video object, if the video is set to loop or not and the playback rate.
*/
this.onPlay = new Phaser.Signal();
/**
* @property {Phaser.Signal} onChangeSource - This signal is dispatched if the Video source is changed. It sends 3 parameters: a reference to the Video object and the new width and height of the new video source.
*/
this.onChangeSource = new Phaser.Signal();
/**
* @property {Phaser.Signal} onComplete - This signal is dispatched when the Video completes playback, i.e. enters an 'ended' state. On iOS specifically it also fires if the user hits the 'Done' button at any point during playback. Videos set to loop will never dispatch this signal.
*/
this.onComplete = new Phaser.Signal();
/**
* @property {Phaser.Signal} onAccess - This signal is dispatched if the user allows access to their webcam.
*/
this.onAccess = new Phaser.Signal();
/**
* @property {Phaser.Signal} onError - This signal is dispatched if an error occurs either getting permission to use the webcam (for a Video Stream) or when trying to play back a video file.
*/
this.onError = new Phaser.Signal();
/**
* This signal is dispatched if when asking for permission to use the webcam no response is given within a the Video.timeout limit.
* This may be because the user has picked `Not now` in the permissions window, or there is a delay in establishing the LocalMediaStream.
* @property {Phaser.Signal} onTimeout
*/
this.onTimeout = new Phaser.Signal();
/**
* This signal is dispatched when the Video is unlocked.
* @property {Phaser.Signal} onTouchUnlock
*/
this.onTouchUnlock = new Phaser.Signal();
/**
* Start playing the video when it's unlocked.
* @property {boolean} playWhenUnlocked
* @default
*/
this.playWhenUnlocked = true;
/**
* @property {integer} timeout - The amount of ms allowed to elapsed before the Video.onTimeout signal is dispatched while waiting for webcam access.
* @default
*/
this.timeout = 15000;
/**
* @property {integer} _timeOutID - setTimeout ID.
* @private
*/
this._timeOutID = null;
/**
* @property {HTMLVideoElement} video - The HTML Video Element that is added to the document.
*/
this.video = null;
/**
* @property {MediaStream} videoStream - The Video Stream data. Only set if this Video is streaming from the webcam via `startMediaStream`.
*/
this.videoStream = null;
/**
* @property {boolean} isStreaming - Is there a streaming video source? I.e. from a webcam.
*/
this.isStreaming = false;
/**
* When starting playback of a video Phaser will monitor its readyState using a setTimeout call.
* The setTimeout happens once every `Video.retryInterval` ms. It will carry on monitoring the video
* state in this manner until the `retryLimit` is reached and then abort.
* @property {integer} retryLimit
* @default
*/
this.retryLimit = 20;
/**
* @property {integer} retry - The current retry attempt.
* @default
*/
this.retry = 0;
/**
* @property {integer} retryInterval - The number of ms between each retry at monitoring the status of a downloading video.
* @default
*/
this.retryInterval = 500;
/**
* @property {integer} _retryID - The callback ID of the retry setTimeout.
* @private
*/
this._retryID = null;
/**
* @property {boolean} _codeMuted - Internal mute tracking var.
* @private
* @default
*/
this._codeMuted = false;
/**
* @property {boolean} _muted - Internal mute tracking var.
* @private
* @default
*/
this._muted = false;
/**
* @property {boolean} _codePaused - Internal paused tracking var.
* @private
* @default
*/
this._codePaused = false;
/**
* @property {boolean} _paused - Internal paused tracking var.
* @private
* @default
*/
this._paused = false;
/**
* @property {boolean} _pending - Internal var tracking play pending.
* @private
* @default
*/
this._pending = false;
/**
* @property {boolean} _pendingChangeSource - Internal var tracking play pending.
* @private
* @default
*/
this._pendingChangeSource = false;
/**
* @property {boolean} _autoplay - Internal var tracking autoplay when changing source.
* @private
* @default
*/
this._autoplay = false;
/**
* @property {function} _endCallback - The addEventListener ended function.
* @private
*/
this._endCallback = null;
/**
* @property {function} _playCallback - The addEventListener playing function.
* @private
*/
this._playCallback = null;
if (key && this.game.cache.checkVideoKey(key))
{
var _video = this.game.cache.getVideo(key);
if (_video.isBlob)
{
this.createVideoFromBlob(_video.data);
}
else
{
this.video = _video.data;
}
this.width = this.video.videoWidth;
this.height = this.video.videoHeight;
}
else if (url)
{
this.createVideoFromURL(url, false);
}
/**
* @property {PIXI.BaseTexture} baseTexture - The PIXI.BaseTexture.
* @default
*/
if (this.video && !url)
{
this.baseTexture = new PIXI.BaseTexture(this.video, null, this.game.resolution);
this.baseTexture.forceLoaded(this.width, this.height);
}
else
{
this.baseTexture = new PIXI.BaseTexture(Phaser.Cache.DEFAULT.baseTexture.source, null, this.game.resolution);
this.baseTexture.forceLoaded(this.width, this.height);
}
/**
* @property {PIXI.Texture} texture - The PIXI.Texture.
* @default
*/
this.texture = new PIXI.Texture(this.baseTexture);
/**
* @property {Phaser.Frame} textureFrame - The Frame this video uses for rendering.
* @default
*/
this.textureFrame = new Phaser.Frame(0, 0, 0, this.width, this.height, 'video');
this.texture.setFrame(this.textureFrame);
this.texture.valid = false;
if (key !== null && this.video)
{
this.texture.valid = this.video.canplay;
}
/**
* A snapshot grabbed from the video. This is initially black. Populate it by calling Video.grab().
* When called the BitmapData is updated with a grab taken from the current video playing or active video stream.
* If Phaser has been compiled without BitmapData support this property will always be `null`.
*
* @property {Phaser.BitmapData} snapshot
* @readOnly
*/
this.snapshot = null;
if (Phaser.BitmapData)
{
this.snapshot = new Phaser.BitmapData(this.game, '', this.width, this.height);
}
if (this.game.device.needsTouchUnlock())
{
this.setTouchLock();
}
else
if (_video)
{
_video.locked = false;
}
};
Phaser.Video.prototype = {
/**
* Connects to an external media stream for the webcam, rather than using a local one.
*
* @method Phaser.Video#connectToMediaStream
* @param {HTMLVideoElement} video - The HTML Video Element that the stream uses.
* @param {MediaStream} stream - The Video Stream data.
* @return {Phaser.Video} This Video object for method chaining.
*/
connectToMediaStream: function (video, stream)
{
if (video && stream)
{
this.video = video;
this.videoStream = stream;
this.isStreaming = true;
this.baseTexture.source = this.video;
this.updateTexture(null, this.video.videoWidth, this.video.videoHeight);
this.onAccess.dispatch(this);
}
return this;
},
/**
* Instead of playing a video file this method allows you to stream video data from an attached webcam.
*
* As soon as this method is called the user will be prompted by their browser to "Allow" access to the webcam.
* If they allow it the webcam feed is directed to this Video. Call `Video.play` to start the stream.
*
* If they block the webcam the onError signal will be dispatched containing the NavigatorUserMediaError
* or MediaStreamError event.
*
* You can optionally set a width and height for the stream. If set the input will be cropped to these dimensions.
* If not given then as soon as the stream has enough data the video dimensions will be changed to match the webcam device.
* You can listen for this with the onChangeSource signal.
*
* @method Phaser.Video#startMediaStream
* @param {boolean} [captureAudio=false] - Controls if audio should be captured along with video in the video stream.
* @param {integer} [width] - The width is used to create the video stream. If not provided the video width will be set to the width of the webcam input source.
* @param {integer} [height] - The height is used to create the video stream. If not provided the video height will be set to the height of the webcam input source.
* @return {Phaser.Video} This Video object for method chaining or false if the device doesn't support getUserMedia.
*/
startMediaStream: function (captureAudio, width, height)
{
if (captureAudio === undefined) { captureAudio = false; }
if (width === undefined) { width = null; }
if (height === undefined) { height = null; }
if (!this.game.device.getUserMedia)
{
this.onError.dispatch(this, 'No getUserMedia');
return false;
}
if (this.videoStream !== null)
{
if (this.videoStream.active)
{
this.videoStream.active = false;
}
else
{
this.videoStream.stop();
}
}
this.removeVideoElement();
this.video = document.createElement('video');
this.video.setAttribute('autoplay', 'autoplay');
this.video.setAttribute('playsinline', 'playsinline');
if (width !== null)
{
this.video.width = width;
}
if (height !== null)
{
this.video.height = height;
}
// Request access to the webcam
this._timeOutID = window.setTimeout(this.getUserMediaTimeout.bind(this), this.timeout);
try
{
navigator.getUserMedia(
{ audio: captureAudio, video: true },
this.getUserMediaSuccess.bind(this),
this.getUserMediaError.bind(this)
);
}
catch (error)
{
this.getUserMediaError(error);
}
return this;
},
/**
* @method Phaser.Video#getUserMediaTimeout
* @private
*/
getUserMediaTimeout: function ()
{
clearTimeout(this._timeOutID);
this.onTimeout.dispatch(this);
},
/**
* @method Phaser.Video#getUserMediaError
* @private
*/
getUserMediaError: function (event)
{
clearTimeout(this._timeOutID);
this.onError.dispatch(this, event);
},
/**
* @method Phaser.Video#getUserMediaSuccess
* @private
*/
getUserMediaSuccess: function (stream)
{
clearTimeout(this._timeOutID);
// Attach the stream to the video
this.videoStream = stream;
// Set the source of the video element with the stream from the camera
if (this.video.mozSrcObject !== undefined)
{
this.video.mozSrcObject = stream;
}
else if (this.video.srcObject !== undefined)
{
this.video.srcObject = stream;
}
else
{
this.video.src = (window.URL && window.URL.createObjectURL(stream)) || stream;
}
var self = this;
this.video.onloadeddata = function ()
{
var retry = 10;
function checkStream ()
{
if (retry > 0)
{
if (self.video.videoWidth > 0)
{
// Patch for Firefox bug where the height can't be read from the video
var width = self.video.videoWidth;
var height = self.video.videoHeight;
if (isNaN(self.video.videoHeight))
{
height = width / (4 / 3);
}
self.video.play();
self.isStreaming = true;
self.baseTexture.source = self.video;
self.updateTexture(null, width, height);
self.onAccess.dispatch(self);
}
else
{
window.setTimeout(checkStream, 500);
}
}
else
{
console.warn('Unable to connect to video stream. Webcam error?');
}
retry--;
}
checkStream();
};
},
/**
* Creates a new Video element from the given Blob. The Blob must contain the video data in the correct encoded format.
* This method is typically called by the Phaser.Loader and Phaser.Cache for you, but is exposed publicly for convenience.
*
* @method Phaser.Video#createVideoFromBlob
* @param {Blob} blob - The Blob containing the video data.
* @return {Phaser.Video} This Video object for method chaining.
*/
createVideoFromBlob: function (blob)
{
var _this = this;
this.video = document.createElement('video');
this.video.controls = false;
this.video.setAttribute('autoplay', 'autoplay');
this.video.setAttribute('playsinline', 'playsinline');
this.video.addEventListener('loadeddata', function (event) { _this.updateTexture(event); }, true);
this.video.src = window.URL.createObjectURL(blob);
this.video.canplay = true;
return this;
},
/**
* Creates a new Video element from the given URL.
*
* @method Phaser.Video#createVideoFromURL
* @param {string} url - The URL of the video.
* @param {boolean} [autoplay=false] - Automatically start the video?
* @return {Phaser.Video} This Video object for method chaining.
*/
createVideoFromURL: function (url, autoplay)
{
if (autoplay === undefined) { autoplay = false; }
// Invalidate the texture while we wait for the new one to load (crashes IE11 otherwise)
if (this.texture)
{
this.texture.valid = false;
}
this.video = document.createElement('video');
this.video.controls = false;
if (autoplay)
{
this.video.setAttribute('autoplay', 'autoplay');
}
this.video.setAttribute('playsinline', 'playsinline');
this.video.src = url;
this.video.canplay = true;
this.video.load();
this.retry = this.retryLimit;
this._retryID = window.setTimeout(this.checkVideoProgress.bind(this), this.retryInterval);
this.key = url;
return this;
},
/**
* Called automatically if the video source changes and updates the internal texture dimensions.
* Then dispatches the onChangeSource signal.
*
* @method Phaser.Video#updateTexture
* @param {object} [event] - The event which triggered the texture update.
* @param {integer} [width] - The new width of the video. If undefined `video.videoWidth` is used.
* @param {integer} [height] - The new height of the video. If undefined `video.videoHeight` is used.
*/
updateTexture: function (event, width, height)
{
var change = false;
if (width === undefined || width === null) { width = this.video.videoWidth; change = true; }
if (height === undefined || height === null) { height = this.video.videoHeight; }
this.width = width;
this.height = height;
if (this.baseTexture.source !== this.video)
{
this.baseTexture.source = this.video;
}
this.baseTexture.forceLoaded(width, height);
this.texture.frame.resize(width, height);
this.texture.width = width;
this.texture.height = height;
this.texture.valid = true;
if (this.snapshot)
{
this.snapshot.resize(width, height);
}
if (change && this.key !== null)
{
this.onChangeSource.dispatch(this, width, height);
if (this._autoplay)
{
this.video.play();
this.onPlay.dispatch(this, this.loop, this.playbackRate);
}
}
},
/**
* Called when the video completes playback (reaches and ended state).
* Dispatches the Video.onComplete signal.
*
* @method Phaser.Video#complete
*/
complete: function ()
{
this.onComplete.dispatch(this);
},
/**
* Starts this video playing.
*
* If the video is already playing, or has been queued to play with `changeSource` then this method just returns.
*
* @method Phaser.Video#play
* @param {boolean} [loop=false] - Should the video loop automatically when it reaches the end? Please note that at present some browsers (i.e. Chrome) do not support *seamless* video looping.
* @param {number} [playbackRate=1] - The playback rate of the video. 1 is normal speed, 2 is x2 speed, and so on. You cannot set a negative playback rate.
* @return {Phaser.Video} This Video object for method chaining.
*/
play: function (loop, playbackRate)
{
if (this._pendingChangeSource)
{
return this;
}
if (loop === undefined) { loop = false; }
if (playbackRate === undefined) { playbackRate = 1; }
if (this.game.sound.onMute)
{
this.game.sound.onMute.add(this.setMute, this);
this.game.sound.onUnMute.add(this.unsetMute, this);
if (this.game.sound.mute)
{
this.setMute();
}
}
this.game.onPause.add(this.setPause, this);
this.game.onResume.add(this.setResume, this);
this._endCallback = this.complete.bind(this);
this.video.addEventListener('ended', this._endCallback, true);
this.video.addEventListener('webkitendfullscreen', this._endCallback, true);
if (loop)
{
this.video.loop = 'loop';
}
else
{
this.video.loop = '';
}
this.video.playbackRate = playbackRate;
if (this.touchLocked)
{
this._pending = true;
}
else
{
this._pending = false;
if (this.key !== null)
{
if (this.video.readyState !== 4)
{
this.retry = this.retryLimit;
this._retryID = window.setTimeout(this.checkVideoProgress.bind(this), this.retryInterval);
}
else
{
this._playCallback = this.playHandler.bind(this);
this.video.addEventListener('playing', this._playCallback, true);
}
}
this.video.play();
this.onPlay.dispatch(this, loop, playbackRate);
}
return this;
},
/**
* Called when the video starts to play. Updates the texture.
*
* @method Phaser.Video#playHandler
* @private
*/
playHandler: function ()
{
this.video.removeEventListener('playing', this._playCallback, true);
this.updateTexture();
},
/**
* Stops the video playing.
*
* This removes all locally set signals.
*
* If you only wish to pause playback of the video, to resume at a later time, use `Video.paused = true` instead.
* If the video hasn't finished downloading calling `Video.stop` will not abort the download. To do that you need to
* call `Video.destroy` instead.
*
* If you are using a video stream from a webcam then calling Stop will disconnect the MediaStream session and disable the webcam.
*
* @method Phaser.Video#stop
* @return {Phaser.Video} This Video object for method chaining.
*/
stop: function ()
{
if (this.game.sound.onMute)
{
this.game.sound.onMute.remove(this.setMute, this);
this.game.sound.onUnMute.remove(this.unsetMute, this);
}
this.game.onPause.remove(this.setPause, this);
this.game.onResume.remove(this.setResume, this);
// Stream or file?
if (this.isStreaming)
{
if (this.video.mozSrcObject)
{
this.video.mozSrcObject.stop();
this.video.src = null;
}
else if (this.video.srcObject)
{
this.video.srcObject.stop();
this.video.src = null;
}
else
{
this.video.src = '';
if (this.videoStream.active)
{
this.videoStream.active = false;
}
else
if (this.videoStream.getTracks)
{
this.videoStream.getTracks().forEach(function (track)
{
track.stop();
});
}
else
{
this.videoStream.stop();
}
}
this.videoStream = null;
this.isStreaming = false;
}
else
{
this.video.removeEventListener('ended', this._endCallback, true);
this.video.removeEventListener('webkitendfullscreen', this._endCallback, true);
this.video.removeEventListener('playing', this._playCallback, true);
if (this.touchLocked)
{
this._pending = false;
}
else
{
this.video.pause();
}
}
return this;
},
/**
* Updates the given Display Objects so they use this Video as their texture.
* This will replace any texture they will currently have set.
*
* @method Phaser.Video#add
* @param {Phaser.Sprite|Phaser.Sprite[]|Phaser.Image|Phaser.Image[]} object - Either a single Sprite/Image or an Array of Sprites/Images.
* @return {Phaser.Video} This Video object for method chaining.
*/
add: function (object)
{
if (Array.isArray(object))
{
for (var i = 0; i < object.length; i++)
{
if (object[i].loadTexture)
{
object[i].loadTexture(this);
}
}
}
else
{
object.loadTexture(this);
}
return this;
},
/**
* Creates a new Phaser.Image object, assigns this Video to be its texture, adds it to the world then returns it.
*
* @method Phaser.Video#addToWorld
* @param {number} [x=0] - The x coordinate to place the Image at.
* @param {number} [y=0] - The y coordinate to place the Image at.
* @param {number} [anchorX=0] - Set the x anchor point of the Image. A value between 0 and 1, where 0 is the top-left and 1 is bottom-right.
* @param {number} [anchorY=0] - Set the y anchor point of the Image. A value between 0 and 1, where 0 is the top-left and 1 is bottom-right.
* @param {number} [scaleX=1] - The horizontal scale factor of the Image. A value of 1 means no scaling. 2 would be twice the size, and so on.
* @param {number} [scaleY=1] - The vertical scale factor of the Image. A value of 1 means no scaling. 2 would be twice the size, and so on.
* @return {Phaser.Image} The newly added Image object.
*/
addToWorld: function (x, y, anchorX, anchorY, scaleX, scaleY)
{
scaleX = scaleX || 1;
scaleY = scaleY || 1;
var image = this.game.add.image(x, y, this);
image.anchor.set(anchorX, anchorY);
image.scale.set(scaleX, scaleY);
return image;
},
/**
* If the game is running in WebGL this will push the texture up to the GPU if it's dirty.
* This is called automatically if the Video is being used by a Sprite, otherwise you need to remember to call it in your render function.
* If you wish to suppress this functionality set Video.disableTextureUpload to `true`.
*
* @method Phaser.Video#render
*/
render: function ()
{
if (!this.disableTextureUpload && this.playing)
{
this.baseTexture.dirty();
}
},
/**
* Internal handler called automatically by the Video.mute setter.
*
* @method Phaser.Video#setMute
* @private
*/
setMute: function ()
{
if (this._muted)
{
return;
}
this._muted = true;
this.video.muted = true;
},
/**
* Internal handler called automatically by the Video.mute setter.
*
* @method Phaser.Video#unsetMute
* @private
*/
unsetMute: function ()
{
if (!this._muted || this._codeMuted)
{
return;
}
this._muted = false;
this.video.muted = false;
},
/**
* Internal handler called automatically by the Video.paused setter.
*
* @method Phaser.Video#setPause
* @private
*/
setPause: function ()
{
if (this._paused || this.touchLocked)
{
return;
}
this._paused = true;
this.video.pause();
},
/**
* Internal handler called automatically by the Video.paused setter.
*
* @method Phaser.Video#setResume
* @private
*/
setResume: function ()
{
if (!this._paused || this._codePaused || this.touchLocked)
{
return;
}
this._paused = false;
if (!this.video.ended)
{
this.video.play();
}
},
/**
* On some mobile browsers you cannot play a video until the user has explicitly touched the video to allow it.
* Phaser handles this via the `setTouchLock` method. However if you have 3 different videos, maybe an "Intro", "Start" and "Game Over"
* split into three different Video objects, then you will need the user to touch-unlock every single one of them.
*
* You can avoid this by using just one Video object and simply changing the video source. Once a Video element is unlocked it remains
* unlocked, even if the source changes. So you can use this to your benefit to avoid forcing the user to 'touch' the video yet again.
*
* As you'd expect there are limitations. So far we've found that the videos need to be in the same encoding format and bitrate.
* This method will automatically handle a change in video dimensions, but if you try swapping to a different bitrate we've found it
* cannot render the new video on iOS (desktop browsers cope better).
*
* When the video source is changed the video file is requested over the network. Listen for the `onChangeSource` signal to know
* when the new video has downloaded enough content to be able to be played. Previous settings such as the volume and loop state
* are adopted automatically by the new video.
*
* @method Phaser.Video#changeSource
* @param {string} src - The new URL to change the video.src to.
* @param {boolean} [autoplay=true] - Should the video play automatically after the source has been updated?
* @return {Phaser.Video} This Video object for method chaining.
*/
changeSource: function (src, autoplay)
{
if (autoplay === undefined) { autoplay = true; }
// Invalidate the texture while we wait for the new one to load (crashes IE11 otherwise)
this.texture.valid = false;
this.video.pause();
this._pendingChangeSource = true;
this.retry = this.retryLimit;
this._retryID = window.setTimeout(this.checkVideoProgress.bind(this), this.retryInterval);
this.video.src = src;
this.video.load();
this._autoplay = autoplay;
if (!autoplay)
{
this.paused = true;
}
return this;
},
/**
* Internal callback that monitors the download progress of a video after changing its source.
*
* @method Phaser.Video#checkVideoProgress
* @private
*/
checkVideoProgress: function ()
{
// if (this.video.readyState === 2 || this.video.readyState === 4)
if (this.video.readyState === 4)
{
this._pendingChangeSource = false;
// We've got enough data to update the texture for playback
this.updateTexture();
}
else
{
this.retry--;
if (this.retry > 0)
{
this._retryID = window.setTimeout(this.checkVideoProgress.bind(this), this.retryInterval);
}
else
{
console.warn('Phaser.Video: Unable to start downloading video in time', this.isStreaming);
}
}
},
/**
* Sets the Input Manager touch callback to be Video.unlock.
* Required for mobile video unlocking. Mostly just used internally.
*
* @method Phaser.Video#setTouchLock
*/
setTouchLock: function ()
{
this.game.input.addTouchLockCallback(this.unlock, this, true);
this.touchLocked = true;
},
/**
* Enables the video on mobile devices, usually after the first touch.
* If the SoundManager hasn't been unlocked then this will automatically unlock that as well.
* Only one video can be pending unlock at any one time.
*
* @method Phaser.Video#unlock
*/
unlock: function ()
{
this.touchLocked = false;
if (this.playWhenUnlocked)
{
this.video.play();
this.onPlay.dispatch(this, this.loop, this.playbackRate);
}
if (this.key)
{
var _video = this.game.cache.getVideo(this.key);
if (_video && !_video.isBlob)
{
_video.locked = false;
}
}
this.onTouchUnlock.dispatch(this);
return true;
},
/**
* Grabs the current frame from the Video or Video Stream and renders it to the Video.snapshot BitmapData.
*
* You can optionally set if the BitmapData should be cleared or not, the alpha and the blend mode of the draw.
*
* If you need more advanced control over the grabbing them call `Video.snapshot.copy` directly with the same parameters as BitmapData.copy.
*
* @method Phaser.Video#grab
* @param {boolean} [clear=false] - Should the BitmapData be cleared before the Video is grabbed? Unless you are using alpha or a blend mode you can usually leave this set to false.
* @param {number} [alpha=1] - The alpha that will be set on the video before drawing. A value between 0 (fully transparent) and 1, opaque.
* @param {string} [blendMode=null] - The composite blend mode that will be used when drawing. The default is no blend mode at all. This is a Canvas globalCompositeOperation value such as 'lighter' or 'xor'.
* @return {Phaser.BitmapData} A reference to the Video.snapshot BitmapData object for further method chaining.
*/
grab: function (clear, alpha, blendMode)
{
if (clear === undefined) { clear = false; }
if (alpha === undefined) { alpha = 1; }
if (blendMode === undefined) { blendMode = null; }
if (this.snapshot === null)
{
console.warn('Video.grab cannot run because Phaser.BitmapData is unavailable');
return;
}
if (clear)
{
this.snapshot.cls();
}
this.snapshot.copy(this.video, 0, 0, this.width, this.height, 0, 0, this.width, this.height, 0, 0, 0, 1, 1, alpha, blendMode);
return this.snapshot;
},
/**
* Removes the Video element from the DOM by calling parentNode.removeChild on itself.
* Also removes the autoplay and src attributes and nulls the reference.
*
* @method Phaser.Video#removeVideoElement
*/
removeVideoElement: function ()
{
if (!this.video)
{
return;
}
if (this.video.parentNode)
{
this.video.parentNode.removeChild(this.video);
}
while (this.video.hasChildNodes())
{
this.video.removeChild(this.video.firstChild);
}
this.video.removeAttribute('autoplay');
this.video.removeAttribute('src');
this.video = null;
},
/**
* Destroys the Video object. This calls `Video.stop` and then `Video.removeVideoElement`.
* If any Sprites are using this Video as their texture it is up to you to manage those.
*
* @method Phaser.Video#destroy
*/
destroy: function ()
{
this.stop();
this.removeVideoElement();
if (this.touchLocked)
{
this.game.input.removeTouchLockCallback(this.unlock, this);
}
if (this._retryID)
{
window.clearTimeout(this._retryID);
}
}
};
/**
* @name Phaser.Video#currentTime
* @property {number} currentTime - The current time of the video in seconds. If set the video will attempt to seek to that point in time.
*/
Object.defineProperty(Phaser.Video.prototype, 'currentTime', {
get: function ()
{
return (this.video) ? this.video.currentTime : 0;
},
set: function (value)
{
this.video.currentTime = value;
}
});
/**
* @name Phaser.Video#duration
* @property {number} duration - The duration of the video in seconds.
* @readOnly
*/
Object.defineProperty(Phaser.Video.prototype, 'duration', {
get: function ()
{
return (this.video) ? this.video.duration : 0;
}
});
/**
* @name Phaser.Video#progress
* @property {number} progress - The progress of this video. This is a value between 0 and 1, where 0 is the start and 1 is the end of the video.
* @readOnly
*/
Object.defineProperty(Phaser.Video.prototype, 'progress', {
get: function ()
{
return (this.video) ? (this.video.currentTime / this.video.duration) : 0;
}
});
/**
* @name Phaser.Video#mute
* @property {boolean} mute - Gets or sets the muted state of the Video.
*/
Object.defineProperty(Phaser.Video.prototype, 'mute', {
get: function ()
{
return this._muted;
},
set: function (value)
{
value = value || null;
if (value)
{
if (this._muted)
{
return;
}
this._codeMuted = true;
this.setMute();
}
else
{
if (!this._muted)
{
return;
}
this._codeMuted = false;
this.unsetMute();
}
}
});
/**
* Gets or sets the paused state of the Video.
* If the video is still touch locked (such as on iOS devices) this call has no effect.
*
* @name Phaser.Video#paused
* @property {boolean} paused
*/
Object.defineProperty(Phaser.Video.prototype, 'paused', {
get: function ()
{
return this._paused;
},
set: function (value)
{
value = value || null;
if (this.touchLocked)
{
return;
}
if (value)
{
if (this._paused)
{
return;
}
this._codePaused = true;
this.setPause();
}
else
{
if (!this._paused)
{
return;
}
this._codePaused = false;
this.setResume();
}
}
});
/**
* @name Phaser.Video#volume
* @property {number} volume - Gets or sets the volume of the Video, a value between 0 and 1. The value given is clamped to the range 0 to 1.
*/
Object.defineProperty(Phaser.Video.prototype, 'volume', {
get: function ()
{
return (this.video) ? this.video.volume : 1;
},
set: function (value)
{
if (value < 0)
{
value = 0;
}
else if (value > 1)
{
value = 1;
}
if (this.video)
{
this.video.volume = value;
}
}
});
/**
* @name Phaser.Video#playbackRate
* @property {number} playbackRate - Gets or sets the playback rate of the Video. This is the speed at which the video is playing.
*/
Object.defineProperty(Phaser.Video.prototype, 'playbackRate', {
get: function ()
{
return (this.video) ? this.video.playbackRate : 1;
},
set: function (value)
{
if (this.video)
{
this.video.playbackRate = value;
}
}
});
/**
* Gets or sets if the Video is set to loop.
* Please note that at present some browsers (i.e. Chrome) do not support *seamless* video looping.
* If the video isn't yet set this will always return false.
*
* @name Phaser.Video#loop
* @property {boolean} loop
*/
Object.defineProperty(Phaser.Video.prototype, 'loop', {
get: function ()
{
return (this.video) ? this.video.loop : false;
},
set: function (value)
{
if (value && this.video)
{
this.video.loop = 'loop';
}
else if (this.video)
{
this.video.loop = '';
}
}
});
/**
* @name Phaser.Video#playing
* @property {boolean} playing - True if the video is currently playing (and not paused or ended), otherwise false.
* @readOnly
*/
Object.defineProperty(Phaser.Video.prototype, 'playing', {
get: function ()
{
return (this.video) ? !(this.video.paused && this.video.ended) : false;
}
});
Phaser.Video.prototype.constructor = Phaser.Video;
/* global Phaser:true */
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
// Pixi expects these globals to exist
if (PIXI.blendModes === undefined)
{
PIXI.blendModes = Phaser.blendModes;
}
if (PIXI.scaleModes === undefined)
{
PIXI.scaleModes = Phaser.scaleModes;
}
if (PIXI.Texture.emptyTexture === undefined)
{
PIXI.Texture.emptyTexture = new PIXI.Texture(new PIXI.BaseTexture());
}
if (PIXI.DisplayObject._tempMatrix === undefined)
{
PIXI.DisplayObject._tempMatrix = new Phaser.Matrix();
}
PIXI.TextureSilentFail = true;
// Required by Particle Storm
PIXI.canUseNewCanvasBlendModes = function ()
{
return Phaser.Device.canUseMultiply;
};
/**
* @author Richard Davey
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = Phaser;
}
exports.Phaser = Phaser;
} else if (typeof define !== 'undefined' && define.amd) {
define('Phaser', (function() { return root.Phaser = Phaser; })() );
} else {
root.Phaser = Phaser;
}
return Phaser;
}).call(this);
/*
* "What matters in this life is not what we do but what we do for others, the legacy we leave and the imprint we make." - Eric Meyer
*/