phaser/v2-community/build/custom/phaser-minimum.js
2016-11-23 00:17:46 +00:00

52776 lines
1.5 MiB
Executable file

/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*
* @overview
*
* Phaser - http://phaser.io
*
* v3.0.0 "Shadow Coast" - Built: Mon Nov 14 2016 16:26:09
*
* By Richard Davey http://www.photonstorm.com @photonstorm
*
* Phaser is a fun, free and fast 2D game framework for making HTML5 games
* for desktop and mobile web browsers, supporting Canvas and WebGL rendering.
*
* Phaser uses Pixi.js for rendering, created by Mat Groves http://matgroves.com @Doormat23
* Phaser uses p2.js for full-body physics, created by Stefan Hedman https://github.com/schteppe/p2.js @schteppe
* Phaser contains a port of N+ Physics, converted by Richard Davey, original by http://www.metanetsoftware.com
*
* Many thanks to Adam Saltsman (@ADAMATOMIC) for releasing Flixel, from which both Phaser and my love of framework development originate.
*
* Follow development at http://phaser.io and on our forum
*
* "If you want your children to be intelligent, read them fairy tales."
* "If you want them to be more intelligent, read them more fairy tales."
* -- Albert Einstein
*/
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
(function(){
var root = this;
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* The [pixi.js](http://www.pixijs.com/) module/namespace.
*
* @module PIXI
*/
/**
* Namespace-class for [pixi.js](http://www.pixijs.com/).
*
* Contains assorted static properties and enumerations.
*
* @class PIXI
* @static
*/
var PIXI = PIXI || {};
/**
* @author Mat Groves http://matgroves.com @Doormat23
* @author Richard Davey <rich@photonstorm.com>
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The base class for all objects that are rendered. Contains properties for position, scaling,
* rotation, masks and cache handling.
*
* This is an abstract class and should not be used on its own, rather it should be extended.
*
* It is used internally by the likes of PIXI.Sprite.
*
* @class PIXI.DisplayObject
* @constructor
*/
PIXI.DisplayObject = function () {
/**
* The coordinates, in pixels, of this DisplayObject, relative to its parent container.
*
* The value of this property does not reflect any positioning happening further up the display list.
* To obtain that value please see the `worldPosition` property.
*
* @property {PIXI.Point} position
* @default
*/
this.position = new PIXI.Point(0, 0);
/**
* The scale of this DisplayObject. A scale of 1:1 represents the DisplayObject
* at its default size. A value of 0.5 would scale this DisplayObject by half, and so on.
*
* The value of this property does not reflect any scaling happening further up the display list.
* To obtain that value please see the `worldScale` property.
*
* @property {PIXI.Point} scale
* @default
*/
this.scale = new PIXI.Point(1, 1);
/**
* The pivot point of this DisplayObject that it rotates around. The values are expressed
* in pixel values.
* @property {PIXI.Point} pivot
* @default
*/
this.pivot = new PIXI.Point(0, 0);
/**
* The rotation of this DisplayObject. The value is given, and expressed, in radians, and is based on
* a right-handed orientation.
*
* The value of this property does not reflect any rotation happening further up the display list.
* To obtain that value please see the `worldRotation` property.
*
* @property {number} rotation
* @default
*/
this.rotation = 0;
/**
* The alpha value of this DisplayObject. A value of 1 is fully opaque. A value of 0 is transparent.
* Please note that an object with an alpha value of 0 is skipped during the render pass.
*
* The value of this property does not reflect any alpha values set further up the display list.
* To obtain that value please see the `worldAlpha` property.
*
* @property {number} alpha
* @default
*/
this.alpha = 1;
/**
* The visibility of this DisplayObject. A value of `false` makes the object invisible.
* A value of `true` makes it visible. Please note that an object with a visible value of
* `false` is skipped during the render pass. Equally a DisplayObject with visible false will
* not render any of its children.
*
* The value of this property does not reflect any visible values set further up the display list.
* To obtain that value please see the `worldVisible` property.
*
* @property {boolean} visible
* @default
*/
this.visible = true;
/**
* This is the defined area that will pick up mouse / touch events. It is null by default.
* Setting it is a neat way of optimising the hitTest function that the interactionManager will use (as it will not need to hit test all the children)
*
* @property hitArea
* @type Rectangle|Circle|Ellipse|Polygon
*/
this.hitArea = null;
/**
* Should this DisplayObject be rendered by the renderer? An object with a renderable value of
* `false` is skipped during the render pass.
*
* @property {boolean} renderable
* @default
*/
this.renderable = false;
/**
* The parent DisplayObjectContainer that this DisplayObject is a child of.
* All DisplayObjects must belong to a parent in order to be rendered.
* The root parent is the Stage object. This property is set automatically when the
* DisplayObject is added to, or removed from, a DisplayObjectContainer.
*
* @property {PIXI.DisplayObjectContainer} parent
* @default
* @readOnly
*/
this.parent = null;
/**
* The multiplied alpha value of this DisplayObject. A value of 1 is fully opaque. A value of 0 is transparent.
* This value is the calculated total, based on the alpha values of all parents of this DisplayObjects
* in the display list.
*
* To obtain, and set, the local alpha value, see the `alpha` property.
*
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
* that happens this property will contain values based on the previous frame. Be mindful of this if
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
*
* @property {number} worldAlpha
* @readOnly
*/
this.worldAlpha = 1;
/**
* The current transform of this DisplayObject.
*
* This property contains the calculated total, based on the transforms of all parents of this
* DisplayObject in the display list.
*
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
* that happens this property will contain values based on the previous frame. Be mindful of this if
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
*
* @property {PIXI.Matrix} worldTransform
* @readOnly
*/
this.worldTransform = new PIXI.Matrix();
/**
* The coordinates, in pixels, of this DisplayObject within the world.
*
* This property contains the calculated total, based on the positions of all parents of this
* DisplayObject in the display list.
*
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
* that happens this property will contain values based on the previous frame. Be mindful of this if
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
*
* @property {PIXI.Point} worldPosition
* @readOnly
*/
this.worldPosition = new PIXI.Point(0, 0);
/**
* The global scale of this DisplayObject.
*
* This property contains the calculated total, based on the scales of all parents of this
* DisplayObject in the display list.
*
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
* that happens this property will contain values based on the previous frame. Be mindful of this if
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
*
* @property {PIXI.Point} worldScale
* @readOnly
*/
this.worldScale = new PIXI.Point(1, 1);
/**
* The rotation, in radians, of this DisplayObject.
*
* This property contains the calculated total, based on the rotations of all parents of this
* DisplayObject in the display list.
*
* Note: This property is only updated at the end of the `updateTransform` call, once per render. Until
* that happens this property will contain values based on the previous frame. Be mindful of this if
* accessing this property outside of the normal game flow, i.e. from an asynchronous event callback.
*
* @property {number} worldRotation
* @readOnly
*/
this.worldRotation = 0;
/**
* The rectangular area used by filters when rendering a shader for this DisplayObject.
*
* @property {PIXI.Rectangle} filterArea
* @type Rectangle
* @default
*/
this.filterArea = null;
/**
* @property {number} _sr - Cached rotation value.
* @private
*/
this._sr = 0;
/**
* @property {number} _cr - Cached rotation value.
* @private
*/
this._cr = 1;
/**
* @property {PIXI.Rectangle} _bounds - The cached bounds of this object.
* @private
*/
this._bounds = new PIXI.Rectangle(0, 0, 0, 0);
/**
* @property {PIXI.Rectangle} _currentBounds - The most recently calculated bounds of this object.
* @private
*/
this._currentBounds = null;
/**
* @property {PIXI.Rectangle} _mask - The cached mask of this object.
* @private
*/
this._mask = null;
/**
* @property {boolean} _cacheAsBitmap - Internal cache as bitmap flag.
* @private
*/
this._cacheAsBitmap = false;
/**
* @property {boolean} _cacheIsDirty - Internal dirty cache flag.
* @private
*/
this._cacheIsDirty = false;
};
PIXI.DisplayObject.prototype.constructor = PIXI.DisplayObject;
PIXI.DisplayObject.prototype = {
/**
* Destroy this DisplayObject.
*
* Removes any cached sprites, sets renderable flag to false, and nulls filters, bounds and mask.
*
* Also iteratively calls `destroy` on any children.
*
* @method PIXI.DisplayObject#destroy
*/
destroy: function () {
if (this.children)
{
var i = this.children.length;
while (i--)
{
this.children[i].destroy();
}
this.children = [];
}
this.hitArea = null;
this.parent = null;
this.worldTransform = null;
this.filterArea = null;
this.renderable = false;
this._bounds = null;
this._currentBounds = null;
this._mask = null;
this._destroyCachedSprite();
},
/*
* Updates the transform matrix this DisplayObject uses for rendering.
*
* If the object has no parent, and no parent parameter is provided, it will default to
* Phaser.Game.World as the parent transform to use. If that is unavailable the transform fails to take place.
*
* The `parent` parameter has priority over the actual parent. Use it as a parent override.
* Setting it does **not** change the actual parent of this DisplayObject.
*
* Calling this method updates the `worldTransform`, `worldAlpha`, `worldPosition`, `worldScale`
* and `worldRotation` properties.
*
* If a `transformCallback` has been specified, it is called at the end of this method, and is passed
* the new, updated, worldTransform property, along with the parent transform used.
*
* @method PIXI.DisplayObject#updateTransform
* @param {PIXI.DisplayObjectContainer} [parent] - Optional parent to calculate this DisplayObjects transform from.
* @return {PIXI.DisplayObject} - A reference to this DisplayObject.
*/
updateTransform: function (parent) {
if (!parent && !this.parent && !this.game)
{
return this;
}
var p = this.parent;
if (parent)
{
p = parent;
}
else if (!this.parent)
{
p = this.game.world;
}
// create some matrix refs for easy access
var pt = p.worldTransform;
var wt = this.worldTransform;
// temporary matrix variables
var a, b, c, d, tx, ty;
// so if rotation is between 0 then we can simplify the multiplication process..
if (this.rotation % Phaser.Math.PI_2)
{
// check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes
if (this.rotation !== this.rotationCache)
{
this.rotationCache = this.rotation;
this._sr = Math.sin(this.rotation);
this._cr = Math.cos(this.rotation);
}
// get the matrix values of the displayobject based on its transform properties..
a = this._cr * this.scale.x;
b = this._sr * this.scale.x;
c = -this._sr * this.scale.y;
d = this._cr * this.scale.y;
tx = this.position.x;
ty = this.position.y;
// check for pivot.. not often used so geared towards that fact!
if (this.pivot.x || this.pivot.y)
{
tx -= this.pivot.x * a + this.pivot.y * c;
ty -= this.pivot.x * b + this.pivot.y * d;
}
// concat the parent matrix with the objects transform.
wt.a = a * pt.a + b * pt.c;
wt.b = a * pt.b + b * pt.d;
wt.c = c * pt.a + d * pt.c;
wt.d = c * pt.b + d * pt.d;
wt.tx = tx * pt.a + ty * pt.c + pt.tx;
wt.ty = tx * pt.b + ty * pt.d + pt.ty;
}
else
{
// lets do the fast version as we know there is no rotation..
a = this.scale.x;
d = this.scale.y;
tx = this.position.x - this.pivot.x * a;
ty = this.position.y - this.pivot.y * d;
wt.a = a * pt.a;
wt.b = a * pt.b;
wt.c = d * pt.c;
wt.d = d * pt.d;
wt.tx = tx * pt.a + ty * pt.c + pt.tx;
wt.ty = tx * pt.b + ty * pt.d + pt.ty;
}
// Set the World values
this.worldAlpha = this.alpha * p.worldAlpha;
this.worldPosition.set(wt.tx, wt.ty);
this.worldScale.set(this.scale.x * Math.sqrt(wt.a * wt.a + wt.c * wt.c), this.scale.y * Math.sqrt(wt.b * wt.b + wt.d * wt.d));
this.worldRotation = Math.atan2(-wt.c, wt.d);
// reset the bounds each time this is called!
this._currentBounds = null;
// Custom callback?
if (this.transformCallback)
{
this.transformCallback.call(this.transformCallbackContext, wt, pt);
}
return this;
},
/**
* To be overridden by classes that require it.
*
* @method PIXI.DisplayObject#preUpdate
*/
preUpdate: function () {
},
/**
* Generates a RenderTexture based on this DisplayObject, which can they be used to texture other Sprites.
* This can be useful if your DisplayObject is static, or complicated, and needs to be reused multiple times.
*
* Please note that no garbage collection takes place on old textures. It is up to you to destroy old textures,
* and references to them, so they don't linger in memory.
*
* @method PIXI.DisplayObject#generateTexture
* @param {number} [resolution=1] - The resolution of the texture being generated.
* @param {number} [scaleMode=PIXI.scaleModes.DEFAULT] - See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values.
* @param {PIXI.CanvasRenderer|PIXI.WebGLRenderer} renderer - The renderer used to generate the texture.
* @return {Phaser.RenderTexture} - A RenderTexture containing an image of this DisplayObject at the time it was invoked.
*/
generateTexture: function (resolution, scaleMode, renderer) {
var bounds = this.getLocalBounds();
var renderTexture = new Phaser.RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution);
PIXI.DisplayObject._tempMatrix.tx = -bounds.x;
PIXI.DisplayObject._tempMatrix.ty = -bounds.y;
renderTexture.render(this, PIXI.DisplayObject._tempMatrix);
return renderTexture;
},
/**
* If this DisplayObject has a cached Sprite, this method generates and updates it.
*
* @method PIXI.DisplayObject#updateCache
* @return {PIXI.DisplayObject} - A reference to this DisplayObject.
*/
updateCache: function () {
this._generateCachedSprite();
return this;
},
/**
* Calculates the global position of this DisplayObject, based on the position given.
*
* @method PIXI.DisplayObject#toGlobal
* @param {PIXI.Point} position - The global position to calculate from.
* @return {PIXI.Point} - A point object representing the position of this DisplayObject based on the global position given.
*/
toGlobal: function (position) {
this.updateTransform();
return this.worldTransform.apply(position);
},
/**
* Calculates the local position of this DisplayObject, relative to another point.
*
* @method PIXI.DisplayObject#toLocal
* @param {PIXI.Point} position - The world origin to calculate from.
* @param {PIXI.DisplayObject} [from] - An optional DisplayObject to calculate the global position from.
* @return {PIXI.Point} - A point object representing the position of this DisplayObject based on the global position given.
*/
toLocal: function (position, from) {
if (from)
{
position = from.toGlobal(position);
}
this.updateTransform();
return this.worldTransform.applyInverse(position);
},
/**
* Internal method.
*
* @method PIXI.DisplayObject#_renderCachedSprite
* @private
* @param {Object} renderSession - The render session
*/
_renderCachedSprite: function (renderSession) {
this._cachedSprite.worldAlpha = this.worldAlpha;
if (renderSession.gl)
{
PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession);
}
else
{
PIXI.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession);
}
},
/**
* Internal method.
*
* @method PIXI.DisplayObject#_generateCachedSprite
* @private
*/
_generateCachedSprite: function () {
this._cacheAsBitmap = false;
var bounds = this.getLocalBounds();
// Round it off and force non-zero dimensions
bounds.width = Math.max(1, Math.ceil(bounds.width));
bounds.height = Math.max(1, Math.ceil(bounds.height));
this.updateTransform();
if (!this._cachedSprite)
{
var textureUnit = 0;
if (this.texture && this.texture.baseTexture && PIXI._enableMultiTextureToggle)
textureUnit = this.texture.baseTexture.textureIndex;
var renderTexture = new Phaser.RenderTexture(bounds.width, bounds.height, null, null, null, textureUnit);
this._cachedSprite = new PIXI.Sprite(renderTexture);
this._cachedSprite.worldTransform = this.worldTransform;
}
else
{
this._cachedSprite.texture.resize(bounds.width, bounds.height);
}
// Remove filters
var tempFilters = this._filters;
this._filters = null;
this._cachedSprite.filters = tempFilters;
PIXI.DisplayObject._tempMatrix.tx = -bounds.x;
PIXI.DisplayObject._tempMatrix.ty = -bounds.y;
this._cachedSprite.texture.render(this, PIXI.DisplayObject._tempMatrix, true);
this._cachedSprite.anchor.x = -(bounds.x / bounds.width);
this._cachedSprite.anchor.y = -(bounds.y / bounds.height);
this._filters = tempFilters;
this._cacheAsBitmap = true;
},
/**
* Destroys a cached Sprite.
*
* @method PIXI.DisplayObject#_destroyCachedSprite
* @private
*/
_destroyCachedSprite: function () {
if (!this._cachedSprite)
{
return;
}
this._cachedSprite.texture.destroy(true);
this._cachedSprite = null;
}
};
// Alias for updateTransform. As used in DisplayObject container, etc.
PIXI.DisplayObject.prototype.displayObjectUpdateTransform = PIXI.DisplayObject.prototype.updateTransform;
Object.defineProperties(PIXI.DisplayObject.prototype, {
/**
* The horizontal position of the DisplayObject, in pixels, relative to its parent.
* If you need the world position of the DisplayObject, use `DisplayObject.worldPosition` instead.
* @name PIXI.DisplayObject#x
* @property {number} x - The horizontal position of the DisplayObject, in pixels, relative to its parent.
*/
'x': {
get: function () {
return this.position.x;
},
set: function (value) {
this.position.x = value;
}
},
/**
* The vertical position of the DisplayObject, in pixels, relative to its parent.
* If you need the world position of the DisplayObject, use `DisplayObject.worldPosition` instead.
* @name PIXI.DisplayObject#y
* @property {number} y - The vertical position of the DisplayObject, in pixels, relative to its parent.
*/
'y': {
get: function () {
return this.position.y;
},
set: function (value) {
this.position.y = value;
}
},
/**
* Indicates if this DisplayObject is visible, based on it, and all of its parents, `visible` property values.
* @name PIXI.DisplayObject#worldVisible
* @property {boolean} worldVisible - Indicates if this DisplayObject is visible, based on it, and all of its parents, `visible` property values.
*/
'worldVisible': {
get: function () {
if (!this.visible)
{
return false;
}
else
{
var item = this.parent;
if (!item)
{
return this.visible;
}
else
{
do
{
if (!item.visible)
{
return false;
}
item = item.parent;
}
while (item);
}
return true;
}
}
},
/**
* Sets a mask for this DisplayObject. A mask is an instance of a Graphics object.
* When applied it limits the visible area of this DisplayObject to the shape of the mask.
* Under a Canvas renderer it uses shape clipping. Under a WebGL renderer it uses a Stencil Buffer.
* To remove a mask, set this property to `null`.
*
* @name PIXI.DisplayObject#mask
* @property {PIXI.Graphics} mask - The mask applied to this DisplayObject. Set to `null` to remove an existing mask.
*/
'mask': {
get: function () {
return this._mask;
},
set: function (value) {
if (this._mask)
{
this._mask.isMask = false;
}
this._mask = value;
if (value)
{
this._mask.isMask = true;
}
}
},
/**
* Sets the filters for this DisplayObject. This is a WebGL only feature, and is ignored by the Canvas
* Renderer. A filter is a shader applied to this DisplayObject. You can modify the placement of the filter
* using `DisplayObject.filterArea`.
*
* To remove filters, set this property to `null`.
*
* Note: You cannot have a filter set, and a MULTIPLY Blend Mode active, at the same time. Setting a
* filter will reset this DisplayObjects blend mode to NORMAL.
*
* @name PIXI.DisplayObject#filters
* @property {Array} filters - An Array of Phaser.Filter objects, or objects that extend them.
*/
'filters': {
get: function () {
return this._filters;
},
set: function (value) {
if (Array.isArray(value))
{
// Put all the passes in one place.
var passes = [];
for (var i = 0; i < value.length; i++)
{
var filterPasses = value[i].passes;
for (var j = 0; j < filterPasses.length; j++)
{
passes.push(filterPasses[j]);
}
}
// Needed any more?
this._filterBlock = { target: this, filterPasses: passes };
}
this._filters = value;
if (this.blendMode && this.blendMode === PIXI.blendModes.MULTIPLY)
{
this.blendMode = PIXI.blendModes.NORMAL;
}
}
},
/**
* Sets if this DisplayObject should be cached as a bitmap.
*
* When invoked it will take a snapshot of the DisplayObject, as it is at that moment, and store it
* in a RenderTexture. This is then used whenever this DisplayObject is rendered. It can provide a
* performance benefit for complex, but static, DisplayObjects. I.e. those with lots of children.
*
* Cached Bitmaps do not track their parents. If you update a property of this DisplayObject, it will not
* re-generate the cached bitmap automatically. To do that you need to call `DisplayObject.updateCache`.
*
* To remove a cached bitmap, set this property to `null`.
*
* @name PIXI.DisplayObject#cacheAsBitmap
* @property {boolean} cacheAsBitmap - Cache this DisplayObject as a Bitmap. Set to `null` to remove an existing cached bitmap.
*/
'cacheAsBitmap': {
get: function () {
return this._cacheAsBitmap;
},
set: function (value) {
if (this._cacheAsBitmap === value)
{
return;
}
if (value)
{
this._generateCachedSprite();
}
else
{
this._destroyCachedSprite();
}
this._cacheAsBitmap = value;
}
}
});
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* A DisplayObjectContainer represents a collection of display objects.
* It is the base class of all display objects that act as a container for other objects.
*
* @class DisplayObjectContainer
* @extends DisplayObject
* @constructor
*/
PIXI.DisplayObjectContainer = function () {
PIXI.DisplayObject.call(this);
/**
* [read-only] The array of children of this container.
*
* @property children
* @type Array(DisplayObject)
* @readOnly
*/
this.children = [];
/**
* If `ignoreChildInput` is `false` it will allow this objects _children_ to be considered as valid for Input events.
*
* If this property is `true` then the children will _not_ be considered as valid for Input events.
*
* Note that this property isn't recursive: only immediate children are influenced, it doesn't scan further down.
* @property {boolean} ignoreChildInput
* @default
*/
this.ignoreChildInput = false;
};
PIXI.DisplayObjectContainer.prototype = Object.create( PIXI.DisplayObject.prototype );
PIXI.DisplayObjectContainer.prototype.constructor = PIXI.DisplayObjectContainer;
/**
* Adds a child to the container.
*
* @method addChild
* @param child {DisplayObject} The DisplayObject to add to the container
* @return {DisplayObject} The child that was added.
*/
PIXI.DisplayObjectContainer.prototype.addChild = function (child) {
return this.addChildAt(child, this.children.length);
};
/**
* Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown
*
* @method addChildAt
* @param child {DisplayObject} The child to add
* @param index {Number} The index to place the child in
* @return {DisplayObject} The child that was added.
*/
PIXI.DisplayObjectContainer.prototype.addChildAt = function (child, index) {
if (index >= 0 && index <= this.children.length)
{
if (child.parent)
{
child.parent.removeChild(child);
}
child.parent = this;
this.children.splice(index, 0, child);
return child;
}
else
{
throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length);
}
};
/**
* Swaps the position of 2 Display Objects within this container.
*
* @method swapChildren
* @param child {DisplayObject}
* @param child2 {DisplayObject}
*/
PIXI.DisplayObjectContainer.prototype.swapChildren = function (child, child2) {
if (child === child2)
{
return;
}
var index1 = this.getChildIndex(child);
var index2 = this.getChildIndex(child2);
if (index1 < 0 || index2 < 0)
{
throw new Error('swapChildren: Both the supplied DisplayObjects must be a child of the caller.');
}
this.children[index1] = child2;
this.children[index2] = child;
};
/**
* Returns the index position of a child DisplayObject instance
*
* @method getChildIndex
* @param child {DisplayObject} The DisplayObject instance to identify
* @return {Number} The index position of the child display object to identify
*/
PIXI.DisplayObjectContainer.prototype.getChildIndex = function (child) {
var index = this.children.indexOf(child);
if (index === -1)
{
throw new Error('The supplied DisplayObject must be a child of the caller');
}
return index;
};
/**
* Changes the position of an existing child in the display object container
*
* @method setChildIndex
* @param child {DisplayObject} The child DisplayObject instance for which you want to change the index number
* @param index {Number} The resulting index number for the child display object
*/
PIXI.DisplayObjectContainer.prototype.setChildIndex = function (child, index) {
if (index < 0 || index >= this.children.length)
{
throw new Error('The supplied index is out of bounds');
}
var currentIndex = this.getChildIndex(child);
this.children.splice(currentIndex, 1); //remove from old position
this.children.splice(index, 0, child); //add at new position
};
/**
* Returns the child at the specified index
*
* @method getChildAt
* @param index {Number} The index to get the child from
* @return {DisplayObject} The child at the given index, if any.
*/
PIXI.DisplayObjectContainer.prototype.getChildAt = function (index) {
if (index < 0 || index >= this.children.length)
{
throw new Error('getChildAt: Supplied index '+ index +' does not exist in the child list, or the supplied DisplayObject must be a child of the caller');
}
return this.children[index];
};
/**
* Removes a child from the container.
*
* @method removeChild
* @param child {DisplayObject} The DisplayObject to remove
* @return {DisplayObject} The child that was removed.
*/
PIXI.DisplayObjectContainer.prototype.removeChild = function (child) {
var index = this.children.indexOf(child);
if (index === -1)
{
return;
}
return this.removeChildAt(index);
};
/**
* Removes a child from the specified index position.
*
* @method removeChildAt
* @param index {Number} The index to get the child from
* @return {DisplayObject} The child that was removed.
*/
PIXI.DisplayObjectContainer.prototype.removeChildAt = function (index) {
var child = this.getChildAt(index);
if (child)
{
child.parent = undefined;
this.children.splice(index, 1);
}
return child;
};
/**
* Removes all children from this container that are within the begin and end indexes.
*
* @method removeChildren
* @param beginIndex {Number} The beginning position. Default value is 0.
* @param endIndex {Number} The ending position. Default value is size of the container.
*/
PIXI.DisplayObjectContainer.prototype.removeChildren = function (beginIndex, endIndex) {
if (beginIndex === undefined) { beginIndex = 0; }
if (endIndex === undefined) { endIndex = this.children.length; }
var range = endIndex - beginIndex;
if (range > 0 && range <= endIndex)
{
var removed = this.children.splice(beginIndex, range);
for (var i = 0; i < removed.length; i++)
{
var child = removed[i];
child.parent = undefined;
}
return removed;
}
else if (range === 0 && this.children.length === 0)
{
return [];
}
else
{
throw new Error( 'removeChildren: Range Error, numeric values are outside the acceptable range' );
}
};
/*
* Updates the transform on all children of this container for rendering
*
* @method updateTransform
* @private
*/
PIXI.DisplayObjectContainer.prototype.updateTransform = function () {
if (!this.visible)
{
return;
}
this.displayObjectUpdateTransform();
if (this._cacheAsBitmap)
{
return;
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i].updateTransform();
}
};
// performance increase to avoid using call.. (10x faster)
PIXI.DisplayObjectContainer.prototype.displayObjectContainerUpdateTransform = PIXI.DisplayObjectContainer.prototype.updateTransform;
/**
* Retrieves the global bounds of the displayObjectContainer as a rectangle. The bounds calculation takes all visible children into consideration.
*
* @method getBounds
* @param {PIXI.DisplayObject|PIXI.Matrix} [targetCoordinateSpace] Returns a rectangle that defines the area of the display object relative to the coordinate system of the targetCoordinateSpace object.
* @return {Rectangle} The rectangular bounding area
*/
PIXI.DisplayObjectContainer.prototype.getBounds = function (targetCoordinateSpace) {
var isTargetCoordinateSpaceDisplayObject = (targetCoordinateSpace && targetCoordinateSpace instanceof PIXI.DisplayObject);
var isTargetCoordinateSpaceThisOrParent = true;
if (!isTargetCoordinateSpaceDisplayObject)
{
targetCoordinateSpace = this;
}
else if (targetCoordinateSpace instanceof PIXI.DisplayObjectContainer)
{
isTargetCoordinateSpaceThisOrParent = targetCoordinateSpace.contains(this);
}
else
{
isTargetCoordinateSpaceThisOrParent = false;
}
var i;
if (isTargetCoordinateSpaceDisplayObject)
{
var matrixCache = targetCoordinateSpace.worldTransform;
targetCoordinateSpace.worldTransform = PIXI.identityMatrix;
for (i = 0; i < targetCoordinateSpace.children.length; i++)
{
targetCoordinateSpace.children[i].updateTransform();
}
}
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
var childBounds;
var childMaxX;
var childMaxY;
var childVisible = false;
for (i = 0; i < this.children.length; i++)
{
var child = this.children[i];
if (!child.visible)
{
continue;
}
childVisible = true;
childBounds = this.children[i].getBounds();
minX = (minX < childBounds.x) ? minX : childBounds.x;
minY = (minY < childBounds.y) ? minY : childBounds.y;
childMaxX = childBounds.width + childBounds.x;
childMaxY = childBounds.height + childBounds.y;
maxX = (maxX > childMaxX) ? maxX : childMaxX;
maxY = (maxY > childMaxY) ? maxY : childMaxY;
}
var bounds = this._bounds;
if (!childVisible)
{
bounds = new PIXI.Rectangle();
var w0 = bounds.x;
var w1 = bounds.width + bounds.x;
var h0 = bounds.y;
var h1 = bounds.height + bounds.y;
var worldTransform = this.worldTransform;
var a = worldTransform.a;
var b = worldTransform.b;
var c = worldTransform.c;
var d = worldTransform.d;
var tx = worldTransform.tx;
var ty = worldTransform.ty;
var x1 = a * w1 + c * h1 + tx;
var y1 = d * h1 + b * w1 + ty;
var x2 = a * w0 + c * h1 + tx;
var y2 = d * h1 + b * w0 + ty;
var x3 = a * w0 + c * h0 + tx;
var y3 = d * h0 + b * w0 + ty;
var x4 = a * w1 + c * h0 + tx;
var y4 = d * h0 + b * w1 + ty;
maxX = x1;
maxY = y1;
minX = x1;
minY = y1;
minX = x2 < minX ? x2 : minX;
minX = x3 < minX ? x3 : minX;
minX = x4 < minX ? x4 : minX;
minY = y2 < minY ? y2 : minY;
minY = y3 < minY ? y3 : minY;
minY = y4 < minY ? y4 : minY;
maxX = x2 > maxX ? x2 : maxX;
maxX = x3 > maxX ? x3 : maxX;
maxX = x4 > maxX ? x4 : maxX;
maxY = y2 > maxY ? y2 : maxY;
maxY = y3 > maxY ? y3 : maxY;
maxY = y4 > maxY ? y4 : maxY;
}
bounds.x = minX;
bounds.y = minY;
bounds.width = maxX - minX;
bounds.height = maxY - minY;
if (isTargetCoordinateSpaceDisplayObject)
{
targetCoordinateSpace.worldTransform = matrixCache;
for (i = 0; i < targetCoordinateSpace.children.length; i++)
{
targetCoordinateSpace.children[i].updateTransform();
}
}
if (!isTargetCoordinateSpaceThisOrParent)
{
var targetCoordinateSpaceBounds = targetCoordinateSpace.getBounds();
bounds.x -= targetCoordinateSpaceBounds.x;
bounds.y -= targetCoordinateSpaceBounds.y;
}
return bounds;
};
/**
* Retrieves the non-global local bounds of the displayObjectContainer as a rectangle without any transformations. The calculation takes all visible children into consideration.
*
* @method getLocalBounds
* @return {Rectangle} The rectangular bounding area
*/
PIXI.DisplayObjectContainer.prototype.getLocalBounds = function () {
return this.getBounds(this);
};
/**
* Determines whether the specified display object is a child of the DisplayObjectContainer instance or the instance itself.
*
* @method contains
* @param {DisplayObject} child
* @returns {boolean}
*/
PIXI.DisplayObjectContainer.prototype.contains = function (child) {
if (!child)
{
return false;
}
else if (child === this)
{
return true;
}
else
{
return this.contains(child.parent);
}
};
/**
* Renders the object using the WebGL renderer
*
* @method _renderWebGL
* @param renderSession {RenderSession}
* @private
*/
PIXI.DisplayObjectContainer.prototype._renderWebGL = function (renderSession) {
if (!this.visible || this.alpha <= 0)
{
return;
}
if (this._cacheAsBitmap)
{
this._renderCachedSprite(renderSession);
return;
}
var i;
if (this._mask || this._filters)
{
// push filter first as we need to ensure the stencil buffer is correct for any masking
if (this._filters)
{
renderSession.spriteBatch.flush();
renderSession.filterManager.pushFilter(this._filterBlock);
}
if (this._mask)
{
renderSession.spriteBatch.stop();
renderSession.maskManager.pushMask(this.mask, renderSession);
renderSession.spriteBatch.start();
}
// simple render children!
for (i = 0; i < this.children.length; i++)
{
this.children[i]._renderWebGL(renderSession);
}
renderSession.spriteBatch.stop();
if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession);
if (this._filters) renderSession.filterManager.popFilter();
renderSession.spriteBatch.start();
}
else
{
// simple render children!
for (i = 0; i < this.children.length; i++)
{
this.children[i]._renderWebGL(renderSession);
}
}
};
/**
* Renders the object using the Canvas renderer
*
* @method _renderCanvas
* @param renderSession {RenderSession}
* @private
*/
PIXI.DisplayObjectContainer.prototype._renderCanvas = function (renderSession) {
if (this.visible === false || this.alpha === 0)
{
return;
}
if (this._cacheAsBitmap)
{
this._renderCachedSprite(renderSession);
return;
}
if (this._mask)
{
renderSession.maskManager.pushMask(this._mask, renderSession);
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._renderCanvas(renderSession);
}
if (this._mask)
{
renderSession.maskManager.popMask(renderSession);
}
};
/**
* The width of the displayObjectContainer, setting this will actually modify the scale to achieve the value set
*
* @property width
* @type Number
*/
Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'width', {
get: function() {
return this.getLocalBounds().width * this.scale.x;
},
set: function(value) {
var width = this.getLocalBounds().width;
if (width !== 0)
{
this.scale.x = value / width;
}
else
{
this.scale.x = 1;
}
this._width = value;
}
});
/**
* The height of the displayObjectContainer, setting this will actually modify the scale to achieve the value set
*
* @property height
* @type Number
*/
Object.defineProperty(PIXI.DisplayObjectContainer.prototype, 'height', {
get: function() {
return this.getLocalBounds().height * this.scale.y;
},
set: function(value) {
var height = this.getLocalBounds().height;
if (height !== 0)
{
this.scale.y = value / height;
}
else
{
this.scale.y = 1;
}
this._height = value;
}
});
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* The Sprite object is the base for all textured objects that are rendered to the screen
*
* @class Sprite
* @extends DisplayObjectContainer
* @constructor
* @param texture {Texture} The texture for this sprite
*/
PIXI.Sprite = function (texture) {
PIXI.DisplayObjectContainer.call(this);
/**
* The anchor sets the origin point of the texture.
* The default is 0,0 this means the texture's origin is the top left
* Setting than anchor to 0.5,0.5 means the textures origin is centered
* Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner
*
* @property anchor
* @type Point
*/
this.anchor = new PIXI.Point();
/**
* The texture that the sprite is using
*
* @property texture
* @type Texture
*/
this.texture = texture || PIXI.Texture.emptyTexture;
/**
* The width of the sprite (this is initially set by the texture)
*
* @property _width
* @type Number
* @private
*/
this._width = 0;
/**
* The height of the sprite (this is initially set by the texture)
*
* @property _height
* @type Number
* @private
*/
this._height = 0;
/**
* The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect.
*
* @property tint
* @type Number
* @default 0xFFFFFF
*/
this.tint = 0xFFFFFF;
/**
* The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect.
*
* @property cachedTint
* @private
* @type Number
* @default -1
*/
this.cachedTint = -1;
/**
* A canvas that contains the tinted version of the Sprite (in Canvas mode, WebGL doesn't populate this)
*
* @property tintedTexture
* @type Canvas
* @default null
*/
this.tintedTexture = null;
/**
* The blend mode to be applied to the sprite. Set to PIXI.blendModes.NORMAL to remove any blend mode.
*
* Warning: You cannot have a blend mode and a filter active on the same Sprite. Doing so will render the sprite invisible.
*
* @property blendMode
* @type Number
* @default PIXI.blendModes.NORMAL;
*/
this.blendMode = PIXI.blendModes.NORMAL;
/**
* The shader that will be used to render this Sprite.
* Set to null to remove a current shader.
*
* @property shader
* @type Phaser.Filter
* @default null
*/
this.shader = null;
/**
* Controls if this Sprite is processed by the core Phaser game loops and Group loops.
*
* @property exists
* @type Boolean
* @default true
*/
this.exists = true;
if (this.texture.baseTexture.hasLoaded)
{
this.onTextureUpdate();
}
this.renderable = true;
};
// constructor
PIXI.Sprite.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
PIXI.Sprite.prototype.constructor = PIXI.Sprite;
/**
* The width of the sprite, setting this will actually modify the scale to achieve the value set
*
* @property width
* @type Number
*/
Object.defineProperty(PIXI.Sprite.prototype, 'width', {
get: function() {
return this.scale.x * this.texture.frame.width;
},
set: function(value) {
this.scale.x = value / this.texture.frame.width;
this._width = value;
}
});
/**
* The height of the sprite, setting this will actually modify the scale to achieve the value set
*
* @property height
* @type Number
*/
Object.defineProperty(PIXI.Sprite.prototype, 'height', {
get: function() {
return this.scale.y * this.texture.frame.height;
},
set: function(value) {
this.scale.y = value / this.texture.frame.height;
this._height = value;
}
});
/**
* Sets the texture of the sprite. Be warned that this doesn't remove or destroy the previous
* texture this Sprite was using.
*
* @method setTexture
* @param texture {Texture} The PIXI texture that is displayed by the sprite
* @param [destroy=false] {boolean} Call Texture.destroy on the current texture before replacing it with the new one?
*/
PIXI.Sprite.prototype.setTexture = function(texture, destroyBase)
{
if (destroyBase !== undefined)
{
this.texture.baseTexture.destroy();
}
// Over-ridden by loadTexture as needed
this.texture.baseTexture.skipRender = false;
this.texture = texture;
this.texture.valid = true;
this.cachedTint = -1;
};
/**
* When the texture is updated, this event will fire to update the scale and frame
*
* @method onTextureUpdate
* @param event
* @private
*/
PIXI.Sprite.prototype.onTextureUpdate = function()
{
// so if _width is 0 then width was not set..
if (this._width) this.scale.x = this._width / this.texture.frame.width;
if (this._height) this.scale.y = this._height / this.texture.frame.height;
};
/**
* Returns the bounds of the Sprite as a rectangle.
* The bounds calculation takes the worldTransform into account.
*
* It is important to note that the transform is not updated when you call this method.
* So if this Sprite is the child of a Display Object which has had its transform
* updated since the last render pass, those changes will not yet have been applied
* to this Sprites worldTransform. If you need to ensure that all parent transforms
* are factored into this getBounds operation then you should call `updateTransform`
* on the root most object in this Sprites display list first.
*
* @method getBounds
* @param matrix {Matrix} the transformation matrix of the sprite
* @return {Rectangle} the framing rectangle
*/
PIXI.Sprite.prototype.getBounds = function(matrix)
{
var width = this.texture.frame.width;
var height = this.texture.frame.height;
var w0 = width * (1-this.anchor.x);
var w1 = width * -this.anchor.x;
var h0 = height * (1-this.anchor.y);
var h1 = height * -this.anchor.y;
var worldTransform = matrix || this.worldTransform;
var a = worldTransform.a;
var b = worldTransform.b;
var c = worldTransform.c;
var d = worldTransform.d;
var tx = worldTransform.tx;
var ty = worldTransform.ty;
var maxX = -Infinity;
var maxY = -Infinity;
var minX = Infinity;
var minY = Infinity;
if (b === 0 && c === 0)
{
// scale may be negative!
if (a < 0)
{
a *= -1;
var temp = w0;
w0 = -w1;
w1 = -temp;
}
if (d < 0)
{
d *= -1;
var temp = h0;
h0 = -h1;
h1 = -temp;
}
// this means there is no rotation going on right? RIGHT?
// if thats the case then we can avoid checking the bound values! yay
minX = a * w1 + tx;
maxX = a * w0 + tx;
minY = d * h1 + ty;
maxY = d * h0 + ty;
}
else
{
var x1 = a * w1 + c * h1 + tx;
var y1 = d * h1 + b * w1 + ty;
var x2 = a * w0 + c * h1 + tx;
var y2 = d * h1 + b * w0 + ty;
var x3 = a * w0 + c * h0 + tx;
var y3 = d * h0 + b * w0 + ty;
var x4 = a * w1 + c * h0 + tx;
var y4 = d * h0 + b * w1 + ty;
minX = x1 < minX ? x1 : minX;
minX = x2 < minX ? x2 : minX;
minX = x3 < minX ? x3 : minX;
minX = x4 < minX ? x4 : minX;
minY = y1 < minY ? y1 : minY;
minY = y2 < minY ? y2 : minY;
minY = y3 < minY ? y3 : minY;
minY = y4 < minY ? y4 : minY;
maxX = x1 > maxX ? x1 : maxX;
maxX = x2 > maxX ? x2 : maxX;
maxX = x3 > maxX ? x3 : maxX;
maxX = x4 > maxX ? x4 : maxX;
maxY = y1 > maxY ? y1 : maxY;
maxY = y2 > maxY ? y2 : maxY;
maxY = y3 > maxY ? y3 : maxY;
maxY = y4 > maxY ? y4 : maxY;
}
var bounds = this._bounds;
bounds.x = minX;
bounds.width = maxX - minX;
bounds.y = minY;
bounds.height = maxY - minY;
// store a reference so that if this function gets called again in the render cycle we do not have to recalculate
this._currentBounds = bounds;
return bounds;
};
/**
* Retrieves the non-global local bounds of the Sprite as a rectangle. The calculation takes all visible children into consideration.
*
* @method getLocalBounds
* @return {Rectangle} The rectangular bounding area
*/
PIXI.Sprite.prototype.getLocalBounds = function () {
var matrixCache = this.worldTransform;
this.worldTransform = PIXI.identityMatrix;
for (var i = 0; i < this.children.length; i++)
{
this.children[i].updateTransform();
}
var bounds = this.getBounds();
this.worldTransform = matrixCache;
for (i = 0; i < this.children.length; i++)
{
this.children[i].updateTransform();
}
return bounds;
};
/**
* Renders the object using the WebGL renderer
*
* @method _renderWebGL
* @param renderSession {RenderSession}
* @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform.
* @private
*/
PIXI.Sprite.prototype._renderWebGL = function(renderSession, matrix)
{
// if the sprite is not visible or the alpha is 0 then no need to render this element
if (!this.visible || this.alpha <= 0 || !this.renderable) return;
// They provided an alternative rendering matrix, so use it
var wt = this.worldTransform;
if (matrix)
{
wt = matrix;
}
// A quick check to see if this element has a mask or a filter.
if (this._mask || this._filters)
{
var spriteBatch = renderSession.spriteBatch;
// push filter first as we need to ensure the stencil buffer is correct for any masking
if (this._filters)
{
spriteBatch.flush();
renderSession.filterManager.pushFilter(this._filterBlock);
}
if (this._mask)
{
spriteBatch.stop();
renderSession.maskManager.pushMask(this.mask, renderSession);
spriteBatch.start();
}
// add this sprite to the batch
spriteBatch.render(this);
// now loop through the children and make sure they get rendered
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._renderWebGL(renderSession);
}
// time to stop the sprite batch as either a mask element or a filter draw will happen next
spriteBatch.stop();
if (this._mask) renderSession.maskManager.popMask(this._mask, renderSession);
if (this._filters) renderSession.filterManager.popFilter();
spriteBatch.start();
}
else
{
renderSession.spriteBatch.render(this);
// Render children!
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._renderWebGL(renderSession, wt);
}
}
};
/**
* Renders the object using the Canvas renderer
*
* @method _renderCanvas
* @param renderSession {RenderSession}
* @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform.
* @private
*/
PIXI.Sprite.prototype._renderCanvas = function(renderSession, matrix)
{
// If the sprite is not visible or the alpha is 0 then no need to render this element
if (!this.visible || this.alpha === 0 || !this.renderable || this.texture.crop.width <= 0 || this.texture.crop.height <= 0)
{
return;
}
var wt = this.worldTransform;
// If they provided an alternative rendering matrix then use it
if (matrix)
{
wt = matrix;
}
if (this.blendMode !== renderSession.currentBlendMode)
{
renderSession.currentBlendMode = this.blendMode;
renderSession.context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode];
}
if (this._mask)
{
renderSession.maskManager.pushMask(this._mask, renderSession);
}
// Ignore null sources
if (!this.texture.valid)
{
// Update the children and leave
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._renderCanvas(renderSession);
}
if (this._mask)
{
renderSession.maskManager.popMask(renderSession);
}
return;
}
var resolution = this.texture.baseTexture.resolution / renderSession.resolution;
renderSession.context.globalAlpha = this.worldAlpha;
// If smoothingEnabled is supported and we need to change the smoothing property for this texture
if (renderSession.smoothProperty && renderSession.scaleMode !== this.texture.baseTexture.scaleMode)
{
renderSession.scaleMode = this.texture.baseTexture.scaleMode;
renderSession.context[renderSession.smoothProperty] = (renderSession.scaleMode === PIXI.scaleModes.LINEAR);
}
// If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions
var dx = (this.texture.trim) ? this.texture.trim.x - this.anchor.x * this.texture.trim.width : this.anchor.x * -this.texture.frame.width;
var dy = (this.texture.trim) ? this.texture.trim.y - this.anchor.y * this.texture.trim.height : this.anchor.y * -this.texture.frame.height;
var tx = (wt.tx * renderSession.resolution) + renderSession.shakeX;
var ty = (wt.ty * renderSession.resolution) + renderSession.shakeY;
var cw = this.texture.crop.width;
var ch = this.texture.crop.height;
if (this.texture.rotated)
{
var a = wt.a;
var b = wt.b;
var c = wt.c;
var d = wt.d;
var e = cw;
// Offset before rotating
tx = wt.c * ch + tx;
ty = wt.d * ch + ty;
// Rotate matrix by 90 degrees
// We use precalculated values for sine and cosine of rad(90)
wt.a = a * 6.123233995736766e-17 + -c;
wt.b = b * 6.123233995736766e-17 + -d;
wt.c = a + c * 6.123233995736766e-17;
wt.d = b + d * 6.123233995736766e-17;
// Update cropping dimensions.
cw = ch;
ch = e;
}
// Allow for pixel rounding
if (renderSession.roundPixels)
{
renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx | 0, ty | 0);
dx |= 0;
dy |= 0;
}
else
{
renderSession.context.setTransform(wt.a, wt.b, wt.c, wt.d, tx, ty);
}
dx /= resolution;
dy /= resolution;
if (this.tint !== 0xFFFFFF)
{
if (this.texture.requiresReTint || this.cachedTint !== this.tint)
{
this.tintedTexture = PIXI.CanvasTinter.getTintedTexture(this, this.tint);
this.cachedTint = this.tint;
this.texture.requiresReTint = false;
}
renderSession.context.drawImage(this.tintedTexture, 0, 0, cw, ch, dx, dy, cw / resolution, ch / resolution);
}
else
{
var cx = this.texture.crop.x;
var cy = this.texture.crop.y;
renderSession.context.drawImage(this.texture.baseTexture.source, cx, cy, cw, ch, dx, dy, cw / resolution, ch / resolution);
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._renderCanvas(renderSession);
}
if (this._mask)
{
renderSession.maskManager.popMask(renderSession);
}
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @method initDefaultShaders
* @static
* @private
*/
PIXI.initDefaultShaders = function()
{
};
/**
* @method CompileVertexShader
* @static
* @param gl {WebGLContext} the current WebGL drawing context
* @param shaderSrc {Array}
* @return {Any}
*/
PIXI.CompileVertexShader = function(gl, shaderSrc)
{
return PIXI._CompileShader(gl, shaderSrc, gl.VERTEX_SHADER);
};
/**
* @method CompileFragmentShader
* @static
* @param gl {WebGLContext} the current WebGL drawing context
* @param shaderSrc {Array}
* @return {Any}
*/
PIXI.CompileFragmentShader = function(gl, shaderSrc)
{
return PIXI._CompileShader(gl, shaderSrc, gl.FRAGMENT_SHADER);
};
/**
* @method _CompileShader
* @static
* @private
* @param gl {WebGLContext} the current WebGL drawing context
* @param shaderSrc {Array}
* @param shaderType {Number}
* @return {Any}
*/
PIXI._CompileShader = function(gl, shaderSrc, shaderType)
{
var src = shaderSrc;
if (Array.isArray(shaderSrc))
{
src = shaderSrc.join("\n");
}
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, src);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
{
window.console.log(gl.getShaderInfoLog(shader));
return null;
}
return shader;
};
/**
* @method compileProgram
* @static
* @param gl {WebGLContext} the current WebGL drawing context
* @param vertexSrc {Array}
* @param fragmentSrc {Array}
* @return {Any}
*/
PIXI.compileProgram = function(gl, vertexSrc, fragmentSrc)
{
var fragmentShader = PIXI.CompileFragmentShader(gl, fragmentSrc);
var vertexShader = PIXI.CompileVertexShader(gl, vertexSrc);
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))
{
window.console.log(gl.getProgramInfoLog(shaderProgram));
window.console.log("Could not initialise shaders");
}
return shaderProgram;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
* @author Richard Davey http://www.photonstorm.com @photonstorm
*/
/**
* @class PixiShader
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.PixiShader = function(gl)
{
/**
* @property _UID
* @type Number
* @private
*/
this._UID = Phaser._UID++;
/**
* @property gl
* @type WebGLContext
*/
this.gl = gl;
/**
* The WebGL program.
* @property program
* @type Any
*/
this.program = null;
/**
* The fragment shader.
* @property fragmentSrc
* @type Array
*/
this.fragmentSrc = null;
/**
* A local texture counter for multi-texture shaders.
* @property textureCount
* @type Number
*/
this.textureCount = 0;
/**
* A local flag
* @property firstRun
* @type Boolean
* @private
*/
this.firstRun = true;
/**
* A dirty flag
* @property dirty
* @type Boolean
*/
this.dirty = true;
/**
* Uniform attributes cache.
* @property attributes
* @type Array
* @private
*/
this.attributes = [];
this.init();
};
PIXI.PixiShader.prototype.constructor = PIXI.PixiShader;
PIXI.PixiShader.prototype.initMultitexShader = function () {
var gl = this.gl;
this.MAX_TEXTURES = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
var dynamicIfs = '\tif (vTextureIndex == 0.0) gl_FragColor = texture2D(uSamplerArray[0], vTextureCoord) * vColor;\n'
for (var index = 1; index < this.MAX_TEXTURES; ++index)
{
dynamicIfs += '\telse if (vTextureIndex == ' +
index + '.0) gl_FragColor = texture2D(uSamplerArray[' +
index + '], vTextureCoord) * vColor;\n'
}
this.fragmentSrc = [
'// PixiShader Fragment Shader.',
'precision lowp float;',
'varying vec2 vTextureCoord;',
'varying vec4 vColor;',
'varying float vTextureIndex;',
'uniform sampler2D uSamplerArray[' + this.MAX_TEXTURES + '];',
'const vec4 PINK = vec4(1.0, 0.0, 1.0, 1.0);',
'const vec4 GREEN = vec4(0.0, 1.0, 0.0, 1.0);',
'void main(void) {',
dynamicIfs,
'else gl_FragColor = PINK;',
'}'
];
var program = PIXI.compileProgram(gl, this.vertexSrc || PIXI.PixiShader.defaultVertexSrc, this.fragmentSrc);
gl.useProgram(program);
// get and store the uniforms for the shader
//this.uSampler = gl.getUniformLocation(program, 'uSampler');
this.uSamplerArray = gl.getUniformLocation(program, 'uSamplerArray[0]');
this.projectionVector = gl.getUniformLocation(program, 'projectionVector');
this.offsetVector = gl.getUniformLocation(program, 'offsetVector');
this.dimensions = gl.getUniformLocation(program, 'dimensions');
// get and store the attributes
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord');
this.colorAttribute = gl.getAttribLocation(program, 'aColor');
this.aTextureIndex = gl.getAttribLocation(program, 'aTextureIndex');
var indices = [];
// HACK: we bind an empty texture to avoid WebGL warning spam.
var tempTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, null);
for (var i = 0; i < this.MAX_TEXTURES; ++i) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
indices.push(i);
}
gl.activeTexture(gl.TEXTURE0);
gl.uniform1iv(this.uSamplerArray, indices);
// Begin worst hack eva //
// WHY??? ONLY on my chrome pixel the line above returns -1 when using filters?
// maybe its something to do with the current state of the gl context.
// I'm convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel
// If theres any webGL people that know why could happen please help :)
if(this.colorAttribute === -1)
{
this.colorAttribute = 2;
}
this.attributes = [this.aVertexPosition, this.aTextureCoord, this.colorAttribute, this.aTextureIndex];
// End worst hack eva //
// add those custom shaders!
for (var key in this.uniforms)
{
// get the uniform locations..
this.uniforms[key].uniformLocation = gl.getUniformLocation(program, key);
}
this.initUniforms();
this.program = program;
};
PIXI.PixiShader.prototype.initDefaultShader = function () {
if (this.fragmentSrc === null) {
this.fragmentSrc = [
'precision lowp float;',
'varying vec2 vTextureCoord;',
'varying vec4 vColor;',
'varying float vTextureIndex;',
'uniform sampler2D uSampler;',
'void main(void) {',
' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;',
'}'
];
}
var gl = this.gl;
var program = PIXI.compileProgram(gl, this.vertexSrc || PIXI.PixiShader.defaultVertexSrc, this.fragmentSrc);
gl.useProgram(program);
// get and store the uniforms for the shader
this.uSampler = gl.getUniformLocation(program, 'uSampler');
this.projectionVector = gl.getUniformLocation(program, 'projectionVector');
this.offsetVector = gl.getUniformLocation(program, 'offsetVector');
this.dimensions = gl.getUniformLocation(program, 'dimensions');
// get and store the attributes
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord');
this.colorAttribute = gl.getAttribLocation(program, 'aColor');
this.aTextureIndex = gl.getAttribLocation(program, 'aTextureIndex');
// Begin worst hack eva //
// WHY??? ONLY on my chrome pixel the line above returns -1 when using filters?
// maybe its something to do with the current state of the gl context.
// I'm convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel
// If theres any webGL people that know why could happen please help :)
if(this.colorAttribute === -1)
{
this.colorAttribute = 2;
}
this.attributes = [this.aVertexPosition, this.aTextureCoord, this.colorAttribute, this.aTextureIndex];
// End worst hack eva //
// add those custom shaders!
for (var key in this.uniforms)
{
// get the uniform locations..
this.uniforms[key].uniformLocation = gl.getUniformLocation(program, key);
}
this.initUniforms();
this.program = program;
};
/**
* Initialises the shader.
*
* @method init
*/
PIXI.PixiShader.prototype.init = function(usingFilter)
{
if (PIXI._enableMultiTextureToggle && !usingFilter) {
this.initMultitexShader();
} else {
this.initDefaultShader();
}
};
/**
* Initialises the shader uniform values.
*
* Uniforms are specified in the GLSL_ES Specification: http://www.khronos.org/registry/webgl/specs/latest/1.0/
* http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf
*
* @method initUniforms
*/
PIXI.PixiShader.prototype.initUniforms = function()
{
this.textureCount = 1;
var gl = this.gl;
var uniform;
for (var key in this.uniforms)
{
uniform = this.uniforms[key];
var type = uniform.type;
if (type === 'sampler2D')
{
uniform._init = false;
if (uniform.value !== null)
{
this.initSampler2D(uniform);
}
}
else if (type === 'mat2' || type === 'mat3' || type === 'mat4')
{
// These require special handling
uniform.glMatrix = true;
uniform.glValueLength = 1;
if (type === 'mat2')
{
uniform.glFunc = gl.uniformMatrix2fv;
}
else if (type === 'mat3')
{
uniform.glFunc = gl.uniformMatrix3fv;
}
else if (type === 'mat4')
{
uniform.glFunc = gl.uniformMatrix4fv;
}
}
else
{
// GL function reference
uniform.glFunc = gl['uniform' + type];
if (type === '2f' || type === '2i')
{
uniform.glValueLength = 2;
}
else if (type === '3f' || type === '3i')
{
uniform.glValueLength = 3;
}
else if (type === '4f' || type === '4i')
{
uniform.glValueLength = 4;
}
else
{
uniform.glValueLength = 1;
}
}
}
};
/**
* Initialises a Sampler2D uniform (which may only be available later on after initUniforms once the texture has loaded)
*
* @method initSampler2D
*/
PIXI.PixiShader.prototype.initSampler2D = function(uniform)
{
if (!uniform.value || !uniform.value.baseTexture || !uniform.value.baseTexture.hasLoaded)
{
return;
}
var gl = this.gl;
// No need to do string manipulation for this.
gl.activeTexture(gl.TEXTURE0 + this.textureCount);
gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id]);
// Extended texture data
if (uniform.textureData)
{
var data = uniform.textureData;
// GLTexture = mag linear, min linear_mipmap_linear, wrap repeat + gl.generateMipmap(gl.TEXTURE_2D);
// GLTextureLinear = mag/min linear, wrap clamp
// GLTextureNearestRepeat = mag/min NEAREST, wrap repeat
// GLTextureNearest = mag/min nearest, wrap clamp
// AudioTexture = whatever + luminance + width 512, height 2, border 0
// KeyTexture = whatever + luminance + width 256, height 2, border 0
// magFilter can be: gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR or gl.NEAREST
// wrapS/T can be: gl.CLAMP_TO_EDGE or gl.REPEAT
var magFilter = (data.magFilter) ? data.magFilter : gl.LINEAR;
var minFilter = (data.minFilter) ? data.minFilter : gl.LINEAR;
var wrapS = (data.wrapS) ? data.wrapS : gl.CLAMP_TO_EDGE;
var wrapT = (data.wrapT) ? data.wrapT : gl.CLAMP_TO_EDGE;
var format = (data.luminance) ? gl.LUMINANCE : gl.RGBA;
if (data.repeat)
{
wrapS = gl.REPEAT;
wrapT = gl.REPEAT;
}
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
if (data.width)
{
var width = (data.width) ? data.width : 512;
var height = (data.height) ? data.height : 2;
var border = (data.border) ? data.border : 0;
// void texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView? pixels);
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, border, format, gl.UNSIGNED_BYTE, null);
}
else
{
// void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, ImageData? pixels);
gl.texImage2D(gl.TEXTURE_2D, 0, format, gl.RGBA, gl.UNSIGNED_BYTE, uniform.value.baseTexture.source);
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT);
}
gl.uniform1i(uniform.uniformLocation, this.textureCount);
uniform._init = true;
this.textureCount++;
};
/**
* Updates the shader uniform values.
*
* @method syncUniforms
*/
PIXI.PixiShader.prototype.syncUniforms = function()
{
this.textureCount = 1;
var uniform;
var gl = this.gl;
// This would probably be faster in an array and it would guarantee key order
for (var key in this.uniforms)
{
uniform = this.uniforms[key];
if (uniform.glValueLength === 1)
{
if (uniform.glMatrix === true)
{
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.transpose, uniform.value);
}
else
{
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value);
}
}
else if (uniform.glValueLength === 2)
{
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y);
}
else if (uniform.glValueLength === 3)
{
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z);
}
else if (uniform.glValueLength === 4)
{
uniform.glFunc.call(gl, uniform.uniformLocation, uniform.value.x, uniform.value.y, uniform.value.z, uniform.value.w);
}
else if (uniform.type === 'sampler2D')
{
if (uniform._init)
{
gl.activeTexture(gl['TEXTURE' + this.textureCount]);
if(uniform.value.baseTexture._dirty[gl.id])
{
PIXI.instances[gl.id].updateTexture(uniform.value.baseTexture);
}
else
{
// bind the current texture
gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id]);
}
// gl.bindTexture(gl.TEXTURE_2D, uniform.value.baseTexture._glTextures[gl.id] || PIXI.createWebGLTexture( uniform.value.baseTexture, gl));
gl.uniform1i(uniform.uniformLocation, this.textureCount);
this.textureCount++;
}
else
{
this.initSampler2D(uniform);
}
}
}
};
/**
* Destroys the shader.
*
* @method destroy
*/
PIXI.PixiShader.prototype.destroy = function()
{
this.gl.deleteProgram( this.program );
this.uniforms = null;
this.gl = null;
this.attributes = null;
};
/**
* The Default Vertex shader source.
*
* @property defaultVertexSrc
* @type String
*/
PIXI.PixiShader.defaultVertexSrc = [
'// PixiShader Vertex Shader',
'// With multi-texture rendering',
'attribute vec2 aVertexPosition;',
'attribute vec2 aTextureCoord;',
'attribute vec4 aColor;',
'attribute float aTextureIndex;',
'uniform vec2 projectionVector;',
'uniform vec2 offsetVector;',
'varying vec2 vTextureCoord;',
'varying vec4 vColor;',
'varying float vTextureIndex;',
'const vec2 center = vec2(-1.0, 1.0);',
'void main(void) {',
' if (aTextureIndex > 0.0) gl_Position = vec4(0.0);',
' gl_Position = vec4( ((aVertexPosition + offsetVector) / projectionVector) + center , 0.0, 1.0);',
' vTextureCoord = aTextureCoord;',
' vColor = vec4(aColor.rgb * aColor.a, aColor.a);',
' vTextureIndex = aTextureIndex;',
'}'
];
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class PixiFastShader
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.PixiFastShader = function (gl) {
/**
* @property _UID
* @type Number
* @private
*/
this._UID = Phaser._UID++;
/**
* @property gl
* @type WebGLContext
*/
this.gl = gl;
/**
* The WebGL program.
* @property program
* @type Any
*/
this.program = null;
if (PIXI._enableMultiTextureToggle) {
var gl = this.gl;
this.MAX_TEXTURES = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
var dynamicIfs = '\tif (vTextureIndex == 0.0) gl_FragColor = texture2D(uSamplerArray[0], vTextureCoord) * vColor;\n'
for (var index = 1; index < this.MAX_TEXTURES; ++index)
{
dynamicIfs += '\telse if (vTextureIndex == ' +
index + '.0) gl_FragColor = texture2D(uSamplerArray[' +
index + '], vTextureCoord) * vColor;\n'
}
/**
* The fragment shader.
* @property fragmentSrc
* @type Array
*/
this.fragmentSrc = [
'// PixiFastShader Fragment Shader.',
'precision lowp float;',
'varying vec2 vTextureCoord;',
'varying float vColor;',
'varying float vTextureIndex;',
'uniform sampler2D uSamplerArray[' + this.MAX_TEXTURES + '];',
'const vec4 PINK = vec4(1.0, 0.0, 1.0, 1.0);',
'const vec4 GREEN = vec4(0.0, 1.0, 0.0, 1.0);',
'void main(void) {',
dynamicIfs,
'else gl_FragColor = PINK;',
'}'
];
} else {
this.fragmentSrc = [
'// PixiFastShader Fragment Shader.',
'precision lowp float;',
'varying vec2 vTextureCoord;',
'varying float vColor;',
'varying float vTextureIndex;',
'uniform sampler2D uSampler;',
'void main(void) {',
' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor;',
'}'
];
}
/**
* The vertex shader.
* @property vertexSrc
* @type Array
*/
this.vertexSrc = [
'// PixiFastShader Vertex Shader.',
'attribute vec2 aVertexPosition;',
'attribute vec2 aPositionCoord;',
'attribute vec2 aScale;',
'attribute float aRotation;',
'attribute vec2 aTextureCoord;',
'attribute float aColor;',
'attribute float aTextureIndex;',
'uniform vec2 projectionVector;',
'uniform vec2 offsetVector;',
'uniform mat3 uMatrix;',
'varying vec2 vTextureCoord;',
'varying float vColor;',
'varying float vTextureIndex;',
'const vec2 center = vec2(-1.0, 1.0);',
'void main(void) {',
' vec2 v;',
' vec2 sv = aVertexPosition * aScale;',
' v.x = (sv.x) * cos(aRotation) - (sv.y) * sin(aRotation);',
' v.y = (sv.x) * sin(aRotation) + (sv.y) * cos(aRotation);',
' v = ( uMatrix * vec3(v + aPositionCoord , 1.0) ).xy ;',
' gl_Position = vec4( ( v / projectionVector) + center , 0.0, 1.0);',
' vTextureCoord = aTextureCoord;',
' vTextureIndex = aTextureIndex;',
// ' vec3 color = mod(vec3(aColor.y/65536.0, aColor.y/256.0, aColor.y), 256.0) / 256.0;',
' vColor = aColor;',
'}'
];
/**
* A local texture counter for multi-texture shaders.
* @property textureCount
* @type Number
*/
this.textureCount = 0;
this.init();
};
PIXI.PixiFastShader.prototype.constructor = PIXI.PixiFastShader;
/**
* Initialises the shader.
*
* @method init
*/
PIXI.PixiFastShader.prototype.init = function () {
var gl = this.gl;
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc);
gl.useProgram(program);
// get and store the uniforms for the shader
this.uSampler = PIXI._enableMultiTextureToggle ?
gl.getUniformLocation(program, 'uSamplerArray[0]') :
gl.getUniformLocation(program, 'uSampler');
if (PIXI._enableMultiTextureToggle) {
var indices = [];
// HACK: we bind an empty texture to avoid WebGL warning spam.
var tempTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, null);
for (var i = 0; i < this.MAX_TEXTURES; ++i) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
indices.push(i);
}
gl.activeTexture(gl.TEXTURE0);
gl.uniform1iv(this.uSampler, indices);
}
this.projectionVector = gl.getUniformLocation(program, 'projectionVector');
this.offsetVector = gl.getUniformLocation(program, 'offsetVector');
this.dimensions = gl.getUniformLocation(program, 'dimensions');
this.uMatrix = gl.getUniformLocation(program, 'uMatrix');
// get and store the attributes
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
this.aPositionCoord = gl.getAttribLocation(program, 'aPositionCoord');
this.aScale = gl.getAttribLocation(program, 'aScale');
this.aRotation = gl.getAttribLocation(program, 'aRotation');
this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord');
this.colorAttribute = gl.getAttribLocation(program, 'aColor');
this.aTextureIndex = gl.getAttribLocation(program, 'aTextureIndex');
// Begin worst hack eva //
// WHY??? ONLY on my chrome pixel the line above returns -1 when using filters?
// maybe its somthing to do with the current state of the gl context.
// Im convinced this is a bug in the chrome browser as there is NO reason why this should be returning -1 especially as it only manifests on my chrome pixel
// If theres any webGL people that know why could happen please help :)
if (this.colorAttribute === -1) {
this.colorAttribute = 2;
}
this.attributes = [
this.aVertexPosition,
this.aPositionCoord,
this.aScale,
this.aRotation,
this.aTextureCoord,
this.colorAttribute,
this.aTextureIndex
];
// End worst hack eva //
this.program = program;
};
/**
* Destroys the shader.
*
* @method destroy
*/
PIXI.PixiFastShader.prototype.destroy = function () {
this.gl.deleteProgram(this.program);
this.uniforms = null;
this.gl = null;
this.attributes = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class StripShader
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.StripShader = function(gl)
{
/**
* @property _UID
* @type Number
* @private
*/
this._UID = Phaser._UID++;
/**
* @property gl
* @type WebGLContext
*/
this.gl = gl;
/**
* The WebGL program.
* @property program
* @type Any
*/
this.program = null;
if (PIXI._enableMultiTextureToggle) {
var gl = this.gl;
this.MAX_TEXTURES = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
var dynamicIfs = '\tif (vTextureIndex == 0.0) gl_FragColor = texture2D(uSamplerArray[0], vTextureCoord);\n'
for (var index = 1; index < this.MAX_TEXTURES; ++index)
{
dynamicIfs += '\telse if (vTextureIndex == ' +
index + '.0) gl_FragColor = texture2D(uSamplerArray[' +
index + '], vTextureCoord) ;\n'
}
/**
* The fragment shader.
* @property fragmentSrc
* @type Array
*/
this.fragmentSrc = [
'//StripShader Fragment Shader.',
'precision mediump float;',
'varying vec2 vTextureCoord;',
'varying float vTextureIndex;',
// 'varying float vColor;',
'uniform float alpha;',
'uniform sampler2D uSamplerArray[' + this.MAX_TEXTURES + '];',
'const vec4 PINK = vec4(1.0, 0.0, 1.0, 1.0);',
'const vec4 GREEN = vec4(0.0, 1.0, 0.0, 1.0);',
'void main(void) {',
dynamicIfs,
'else gl_FragColor = PINK;',
'}'
];
} else {
/**
* The fragment shader.
* @property fragmentSrc
* @type Array
*/
this.fragmentSrc = [
'//StripShader Fragment Shader.',
'precision mediump float;',
'varying vec2 vTextureCoord;',
'varying float vTextureIndex;',
// 'varying float vColor;',
'uniform float alpha;',
'uniform sampler2D uSampler;',
'void main(void) {',
' gl_FragColor = texture2D(uSampler, vTextureCoord);',
'}'
];
}
/**
* The vertex shader.
* @property vertexSrc
* @type Array
*/
this.vertexSrc = [
'//StripShader Vertex Shader.',
'attribute vec2 aVertexPosition;',
'attribute vec2 aTextureCoord;',
'attribute float aTextureIndex;',
'uniform mat3 translationMatrix;',
'uniform vec2 projectionVector;',
'uniform vec2 offsetVector;',
// 'uniform float alpha;',
// 'uniform vec3 tint;',
'varying vec2 vTextureCoord;',
'varying float vTextureIndex;',
// 'varying vec4 vColor;',
'void main(void) {',
' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);',
' v -= offsetVector.xyx;',
' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);',
' vTextureCoord = aTextureCoord;',
' vTextureIndex = aTextureIndex;',
// ' vColor = aColor * vec4(tint * alpha, alpha);',
'}'
];
this.init();
};
PIXI.StripShader.prototype.constructor = PIXI.StripShader;
/**
* Initialises the shader.
*
* @method init
*/
PIXI.StripShader.prototype.init = function()
{
var gl = this.gl;
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc);
gl.useProgram(program);
// get and store the uniforms for the shader
this.uSampler = PIXI._enableMultiTextureToggle ?
gl.getUniformLocation(program, 'uSamplerArray[0]') :
gl.getUniformLocation(program, 'uSampler');
if (PIXI._enableMultiTextureToggle) {
var indices = [];
// HACK: we bind an empty texture to avoid WebGL warning spam.
var tempTexture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, null);
for (var i = 0; i < this.MAX_TEXTURES; ++i) {
gl.activeTexture(gl.TEXTURE0 + i);
gl.bindTexture(gl.TEXTURE_2D, tempTexture);
indices.push(i);
}
gl.activeTexture(gl.TEXTURE0);
gl.uniform1iv(this.uSampler, indices);
}
this.projectionVector = gl.getUniformLocation(program, 'projectionVector');
this.offsetVector = gl.getUniformLocation(program, 'offsetVector');
this.colorAttribute = gl.getAttribLocation(program, 'aColor');
this.aTextureIndex = gl.getAttribLocation(program, 'aTextureIndex');
//this.dimensions = gl.getUniformLocation(this.program, 'dimensions');
// get and store the attributes
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
this.aTextureCoord = gl.getAttribLocation(program, 'aTextureCoord');
this.attributes = [this.aVertexPosition, this.aTextureCoord, this.aTextureIndex];
this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix');
this.alpha = gl.getUniformLocation(program, 'alpha');
this.program = program;
};
/**
* Destroys the shader.
*
* @method destroy
*/
PIXI.StripShader.prototype.destroy = function()
{
this.gl.deleteProgram( this.program );
this.uniforms = null;
this.gl = null;
this.attribute = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class PrimitiveShader
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.PrimitiveShader = function(gl)
{
/**
* @property _UID
* @type Number
* @private
*/
this._UID = Phaser._UID++;
/**
* @property gl
* @type WebGLContext
*/
this.gl = gl;
/**
* The WebGL program.
* @property program
* @type Any
*/
this.program = null;
/**
* The fragment shader.
* @property fragmentSrc
* @type Array
*/
this.fragmentSrc = [
'precision mediump float;',
'varying vec4 vColor;',
'void main(void) {',
' gl_FragColor = vColor;',
'}'
];
/**
* The vertex shader.
* @property vertexSrc
* @type Array
*/
this.vertexSrc = [
'attribute vec2 aVertexPosition;',
'attribute vec4 aColor;',
'uniform mat3 translationMatrix;',
'uniform vec2 projectionVector;',
'uniform vec2 offsetVector;',
'uniform float alpha;',
'uniform float flipY;',
'uniform vec3 tint;',
'varying vec4 vColor;',
'void main(void) {',
' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);',
' v -= offsetVector.xyx;',
' gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);',
' vColor = aColor * vec4(tint * alpha, alpha);',
'}'
];
this.init();
};
PIXI.PrimitiveShader.prototype.constructor = PIXI.PrimitiveShader;
/**
* Initialises the shader.
*
* @method init
*/
PIXI.PrimitiveShader.prototype.init = function()
{
var gl = this.gl;
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc);
gl.useProgram(program);
// get and store the uniforms for the shader
this.projectionVector = gl.getUniformLocation(program, 'projectionVector');
this.offsetVector = gl.getUniformLocation(program, 'offsetVector');
this.tintColor = gl.getUniformLocation(program, 'tint');
this.flipY = gl.getUniformLocation(program, 'flipY');
// get and store the attributes
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
this.colorAttribute = gl.getAttribLocation(program, 'aColor');
this.attributes = [this.aVertexPosition, this.colorAttribute];
this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix');
this.alpha = gl.getUniformLocation(program, 'alpha');
this.program = program;
};
/**
* Destroys the shader.
*
* @method destroy
*/
PIXI.PrimitiveShader.prototype.destroy = function()
{
this.gl.deleteProgram( this.program );
this.uniforms = null;
this.gl = null;
this.attributes = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class ComplexPrimitiveShader
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.ComplexPrimitiveShader = function(gl)
{
/**
* @property _UID
* @type Number
* @private
*/
this._UID = Phaser._UID++;
/**
* @property gl
* @type WebGLContext
*/
this.gl = gl;
/**
* The WebGL program.
* @property program
* @type Any
*/
this.program = null;
/**
* The fragment shader.
* @property fragmentSrc
* @type Array
*/
this.fragmentSrc = [
'precision mediump float;',
'varying vec4 vColor;',
'void main(void) {',
' gl_FragColor = vColor;',
'}'
];
/**
* The vertex shader.
* @property vertexSrc
* @type Array
*/
this.vertexSrc = [
'attribute vec2 aVertexPosition;',
//'attribute vec4 aColor;',
'uniform mat3 translationMatrix;',
'uniform vec2 projectionVector;',
'uniform vec2 offsetVector;',
'uniform vec3 tint;',
'uniform float alpha;',
'uniform vec3 color;',
'uniform float flipY;',
'varying vec4 vColor;',
'void main(void) {',
' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);',
' v -= offsetVector.xyx;',
' gl_Position = vec4( v.x / projectionVector.x -1.0, (v.y / projectionVector.y * -flipY) + flipY , 0.0, 1.0);',
' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);',
'}'
];
this.init();
};
PIXI.ComplexPrimitiveShader.prototype.constructor = PIXI.ComplexPrimitiveShader;
/**
* Initialises the shader.
*
* @method init
*/
PIXI.ComplexPrimitiveShader.prototype.init = function()
{
var gl = this.gl;
var program = PIXI.compileProgram(gl, this.vertexSrc, this.fragmentSrc);
gl.useProgram(program);
// get and store the uniforms for the shader
this.projectionVector = gl.getUniformLocation(program, 'projectionVector');
this.offsetVector = gl.getUniformLocation(program, 'offsetVector');
this.tintColor = gl.getUniformLocation(program, 'tint');
this.color = gl.getUniformLocation(program, 'color');
this.flipY = gl.getUniformLocation(program, 'flipY');
// get and store the attributes
this.aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition');
// this.colorAttribute = gl.getAttribLocation(program, 'aColor');
this.attributes = [this.aVertexPosition, this.colorAttribute];
this.translationMatrix = gl.getUniformLocation(program, 'translationMatrix');
this.alpha = gl.getUniformLocation(program, 'alpha');
this.program = program;
};
/**
* Destroys the shader.
*
* @method destroy
*/
PIXI.ComplexPrimitiveShader.prototype.destroy = function()
{
this.gl.deleteProgram( this.program );
this.uniforms = null;
this.gl = null;
this.attribute = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
PIXI.glContexts = []; // this is where we store the webGL contexts for easy access.
PIXI.instances = [];
PIXI._enableMultiTextureToggle = false;
/**
* The WebGLRenderer draws the stage and all its content onto a webGL enabled canvas. This renderer
* should be used for browsers that support webGL. This Render works by automatically managing webGLBatchs.
* So no need for Sprite Batches or Sprite Clouds.
* Don't forget to add the view to your DOM or you will not see anything :)
*
* @class WebGLRenderer
* @constructor
* @param game {Phaser.Game} A reference to the Phaser Game instance
*/
PIXI.WebGLRenderer = function(game) {
/**
* @property {Phaser.Game} game - A reference to the Phaser Game instance.
*/
this.game = game;
if (!PIXI.defaultRenderer)
{
PIXI.defaultRenderer = this;
}
this.extensions = {};
/**
* @property type
* @type Number
*/
this.type = Phaser.WEBGL;
/**
* The resolution of the renderer
*
* @property resolution
* @type Number
* @default 1
*/
this.resolution = game.resolution;
/**
* Whether the render view is transparent
*
* @property transparent
* @type Boolean
*/
this.transparent = game.transparent;
/**
* Whether the render view should be resized automatically
*
* @property autoResize
* @type Boolean
*/
this.autoResize = false;
/**
* The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering.
*
* @property preserveDrawingBuffer
* @type Boolean
*/
this.preserveDrawingBuffer = game.preserveDrawingBuffer;
/**
* This sets if the WebGLRenderer will clear the context texture or not before the new render pass. If true:
* If the Stage is NOT transparent, Pixi will clear to alpha (0, 0, 0, 0).
* If the Stage is transparent, Pixi will clear to the target Stage's background color.
* Disable this by setting this to false. For example: if your game has a canvas filling background image, you often don't need this set.
*
* @property clearBeforeRender
* @type Boolean
* @default
*/
this.clearBeforeRender = game.clearBeforeRender;
/**
* The width of the canvas view
*
* @property width
* @type Number
*/
this.width = game.width;
/**
* The height of the canvas view
*
* @property height
* @type Number
*/
this.height = game.height;
/**
* The canvas element that everything is drawn to
*
* @property view
* @type HTMLCanvasElement
*/
this.view = game.canvas;
/**
* @property _contextOptions
* @type Object
* @private
*/
this._contextOptions = {
alpha: this.transparent,
antialias: game.antialias,
premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied',
stencil: true,
preserveDrawingBuffer: this.preserveDrawingBuffer
};
/**
* @property projection
* @type Point
*/
this.projection = new PIXI.Point();
/**
* @property offset
* @type Point
*/
this.offset = new PIXI.Point();
// time to create the render managers! each one focuses on managing a state in webGL
/**
* Deals with managing the shader programs and their attribs
* @property shaderManager
* @type WebGLShaderManager
*/
this.shaderManager = new PIXI.WebGLShaderManager();
/**
* Manages the rendering of sprites
* @property spriteBatch
* @type WebGLSpriteBatch
*/
this.spriteBatch = new PIXI.WebGLSpriteBatch(game);
/**
* Manages the masks using the stencil buffer
* @property maskManager
* @type WebGLMaskManager
*/
this.maskManager = new PIXI.WebGLMaskManager();
/**
* Manages the filters
* @property filterManager
* @type WebGLFilterManager
*/
this.filterManager = new PIXI.WebGLFilterManager();
/**
* Manages the stencil buffer
* @property stencilManager
* @type WebGLStencilManager
*/
this.stencilManager = new PIXI.WebGLStencilManager();
/**
* Manages the blendModes
* @property blendModeManager
* @type WebGLBlendModeManager
*/
this.blendModeManager = new PIXI.WebGLBlendModeManager();
/**
* @property renderSession
* @type Object
*/
this.renderSession = {};
/**
* @property currentBatchedTextures
* @type Array
*/
this.currentBatchedTextures = [];
// Needed?
this.renderSession.game = this.game;
this.renderSession.gl = this.gl;
this.renderSession.drawCount = 0;
this.renderSession.shaderManager = this.shaderManager;
this.renderSession.maskManager = this.maskManager;
this.renderSession.filterManager = this.filterManager;
this.renderSession.blendModeManager = this.blendModeManager;
this.renderSession.spriteBatch = this.spriteBatch;
this.renderSession.stencilManager = this.stencilManager;
this.renderSession.renderer = this;
this.renderSession.resolution = this.resolution;
// time init the context..
this.initContext();
// map some webGL blend modes..
this.mapBlendModes();
};
// constructor
PIXI.WebGLRenderer.prototype.constructor = PIXI.WebGLRenderer;
/**
* @method initContext
*/
PIXI.WebGLRenderer.prototype.initContext = function()
{
var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions);
this.gl = gl;
if (!gl) {
// fail, not able to get a context
throw new Error('This browser does not support webGL. Try using the canvas renderer');
}
this.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
this.glContextId = gl.id = PIXI.WebGLRenderer.glContextId++;
PIXI.glContexts[this.glContextId] = gl;
PIXI.instances[this.glContextId] = this;
// set up the default pixi settings..
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
gl.enable(gl.BLEND);
// need to set the context for all the managers...
this.shaderManager.setContext(gl);
this.spriteBatch.setContext(gl);
this.maskManager.setContext(gl);
this.filterManager.setContext(gl);
this.blendModeManager.setContext(gl);
this.stencilManager.setContext(gl);
this.renderSession.gl = this.gl;
// now resize and we are good to go!
this.resize(this.width, this.height);
// Load WebGL extension
this.extensions.compression = {};
etc1 = gl.getExtension('WEBGL_compressed_texture_etc1') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_etc1');
pvrtc = gl.getExtension('WEBGL_compressed_texture_pvrtc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
s3tc = gl.getExtension('WEBGL_compressed_texture_s3tc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
if (etc1) this.extensions.compression.ETC1 = etc1;
if (pvrtc) this.extensions.compression.PVRTC = pvrtc;
if (s3tc) this.extensions.compression.S3TC = s3tc;
};
/**
* If Multi Texture support has been enabled, then calling this method will enable batching on the given
* textures. The texture collection is an array of keys, that map to Phaser.Cache image entries.
*
* The number of textures that can be batched is dependent on hardware. If you provide more textures
* than can be batched by the GPU, then only those at the start of the array will be used. Generally
* you shouldn't provide more than 16 textures to this method. You can check the hardware limit via the
* `maxTextures` property.
*
* You can also check the property `currentBatchedTextures` at any time, to see which textures are currently
* being batched.
*
* To stop all textures from being batched, call this method again with an empty array.
*
* To change the textures being batched, call this method with a new array of image keys. The old ones
* will all be purged out and no-longer batched, and the new ones enabled.
*
* Note: Throws a warning if you haven't enabled Multiple Texture batching support in the Phaser Game config.
*
* @method setTexturePriority
* @param textureNameCollection {Array} An Array of Texture Cache keys to use for multi-texture batching.
* @return {Array} An array containing the texture keys that were enabled for batching.
*/
PIXI.WebGLRenderer.prototype.setTexturePriority = function (textureNameCollection) {
if (!PIXI._enableMultiTextureToggle)
{
console.warn('setTexturePriority error: Multi Texture support hasn\'t been enabled in the Phaser Game Config.');
return;
}
var maxTextures = this.maxTextures;
var imageCache = this.game.cache._cache.image;
var imageName = null;
var gl = this.gl;
// Clear out all previously batched textures and reset their flags.
// If the array has been modified, then the developer will have to
// deal with that in their own way.
for (var i = 0; i < this.currentBatchedTextures.length; i++)
{
imageName = textureNameCollection[index];
if (!(imageName in imageCache))
{
continue;
}
imageCache[imageName].base.textureIndex = 0;
}
this.currentBatchedTextures.length = 0;
// We start from 1 because framebuffer texture uses unit 0.
for (var index = 0; index < textureNameCollection.length; ++index)
{
imageName = textureNameCollection[index];
if (!(imageName in imageCache))
{
continue;
}
if (index + 1 < maxTextures)
{
imageCache[imageName].base.textureIndex = index + 1;
}
else
{
imageCache[imageName].base.textureIndex = maxTextures - 1;
}
this.currentBatchedTextures.push(imageName);
}
return this.currentBatchedTextures;
};
/**
* Renders the stage to its webGL view
*
* @method render
* @param stage {Stage} the Stage element to be rendered
*/
PIXI.WebGLRenderer.prototype.render = function(stage)
{
// no point rendering if our context has been blown up!
if (this.contextLost)
{
return;
}
var gl = this.gl;
// -- Does this need to be set every frame? -- //
gl.viewport(0, 0, this.width, this.height);
// make sure we are bound to the main frame buffer
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
if (this.game.clearBeforeRender)
{
gl.clearColor(stage._bgColor.r, stage._bgColor.g, stage._bgColor.b, stage._bgColor.a);
gl.clear(gl.COLOR_BUFFER_BIT);
}
this.offset.x = this.game.camera._shake.x;
this.offset.y = this.game.camera._shake.y;
this.renderDisplayObject(stage, this.projection);
};
/**
* Renders a Display Object.
*
* @method renderDisplayObject
* @param displayObject {DisplayObject} The DisplayObject to render
* @param projection {Point} The projection
* @param buffer {Array} a standard WebGL buffer
*/
PIXI.WebGLRenderer.prototype.renderDisplayObject = function(displayObject, projection, buffer, matrix)
{
this.renderSession.blendModeManager.setBlendMode(PIXI.blendModes.NORMAL);
// reset the render session data..
this.renderSession.drawCount = 0;
// make sure to flip the Y if using a render texture..
this.renderSession.flipY = buffer ? -1 : 1;
// set the default projection
this.renderSession.projection = projection;
//set the default offset
this.renderSession.offset = this.offset;
// start the sprite batch
this.spriteBatch.begin(this.renderSession);
// start the filter manager
this.filterManager.begin(this.renderSession, buffer);
// render the scene!
displayObject._renderWebGL(this.renderSession, matrix);
// finish the sprite batch
this.spriteBatch.end();
};
/**
* Resizes the webGL view to the specified width and height.
*
* @method resize
* @param width {Number} the new width of the webGL view
* @param height {Number} the new height of the webGL view
*/
PIXI.WebGLRenderer.prototype.resize = function(width, height)
{
this.width = width * this.resolution;
this.height = height * this.resolution;
this.view.width = this.width;
this.view.height = this.height;
if (this.autoResize) {
this.view.style.width = this.width / this.resolution + 'px';
this.view.style.height = this.height / this.resolution + 'px';
}
this.gl.viewport(0, 0, this.width, this.height);
this.projection.x = this.width / 2 / this.resolution;
this.projection.y = -this.height / 2 / this.resolution;
};
/**
* Updates and creates a WebGL compressed texture for the renderers context.
*
* @method updateCompressedTexture
* @param texture {Texture} the texture to update
* @return {boolean} True if the texture was successfully bound, otherwise false.
*/
PIXI.WebGLRenderer.prototype.updateCompressedTexture = function (texture) {
if (!texture.hasLoaded)
{
return false;
}
var gl = this.gl;
var textureMetaData = texture.source;
if (!texture._glTextures[gl.id])
{
texture._glTextures[gl.id] = gl.createTexture();
}
gl.activeTexture(gl.TEXTURE0 + texture.textureIndex);
gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]);
gl.compressedTexImage2D(
gl.TEXTURE_2D,
0,
textureMetaData.glExtensionFormat,
textureMetaData.width,
textureMetaData.height,
0,
textureMetaData.textureData
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
if (texture.mipmap && Phaser.Math.isPowerOfTwo(texture.width, texture.height))
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
}
else
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
}
if (!texture._powerOf2)
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
}
else
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
}
texture._dirty[gl.id] = false;
return true;
};
/**
* Updates and Creates a WebGL texture for the renderers context.
*
* @method updateTexture
* @param texture {Texture} the texture to update
* @return {boolean} True if the texture was successfully bound, otherwise false.
*/
PIXI.WebGLRenderer.prototype.updateTexture = function(texture)
{
if (!texture.hasLoaded)
{
return false;
}
if (texture.source.compressionAlgorithm) {
return this.updateCompressedTexture(texture);
}
var gl = this.gl;
if (!texture._glTextures[gl.id])
{
texture._glTextures[gl.id] = gl.createTexture();
}
gl.activeTexture(gl.TEXTURE0 + texture.textureIndex);
gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]);
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultipliedAlpha);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
if (texture.mipmap && Phaser.Math.isPowerOfTwo(texture.width, texture.height))
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
}
else
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
}
if (!texture._powerOf2)
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
}
else
{
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
}
texture._dirty[gl.id] = false;
// return texture._glTextures[gl.id];
return true;
};
/**
* Removes everything from the renderer (event listeners, spritebatch, etc...)
*
* @method destroy
*/
PIXI.WebGLRenderer.prototype.destroy = function()
{
PIXI.glContexts[this.glContextId] = null;
this.projection = null;
this.offset = null;
this.shaderManager.destroy();
this.spriteBatch.destroy();
this.maskManager.destroy();
this.filterManager.destroy();
this.shaderManager = null;
this.spriteBatch = null;
this.maskManager = null;
this.filterManager = null;
this.gl = null;
this.renderSession = null;
Phaser.CanvasPool.remove(this);
PIXI.instances[this.glContextId] = null;
PIXI.WebGLRenderer.glContextId--;
};
/**
* Maps Pixi blend modes to WebGL blend modes.
*
* @method mapBlendModes
*/
PIXI.WebGLRenderer.prototype.mapBlendModes = function()
{
var gl = this.gl;
if (!PIXI.blendModesWebGL)
{
var b = [];
var modes = PIXI.blendModes;
b[modes.NORMAL] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.ADD] = [gl.SRC_ALPHA, gl.DST_ALPHA];
b[modes.MULTIPLY] = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA];
b[modes.SCREEN] = [gl.SRC_ALPHA, gl.ONE];
b[modes.OVERLAY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.DARKEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.LIGHTEN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.COLOR_DODGE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.COLOR_BURN] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.HARD_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.SOFT_LIGHT] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.DIFFERENCE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.EXCLUSION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.HUE] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.SATURATION] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.COLOR] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
b[modes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA];
PIXI.blendModesWebGL = b;
}
};
PIXI.WebGLRenderer.prototype.getMaxTextureUnit = function() {
var gl = this.gl;
return gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
};
PIXI.enableMultiTexture = function() {
PIXI._enableMultiTextureToggle = true;
};
PIXI.WebGLRenderer.glContextId = 0;
PIXI.WebGLRenderer.textureArray = [];
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class WebGLBlendModeManager
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLBlendModeManager = function()
{
/**
* @property currentBlendMode
* @type Number
*/
this.currentBlendMode = 99999;
};
PIXI.WebGLBlendModeManager.prototype.constructor = PIXI.WebGLBlendModeManager;
/**
* Sets the WebGL Context.
*
* @method setContext
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLBlendModeManager.prototype.setContext = function(gl)
{
this.gl = gl;
};
/**
* Sets-up the given blendMode from WebGL's point of view.
*
* @method setBlendMode
* @param blendMode {Number} the blendMode, should be a Pixi const, such as PIXI.BlendModes.ADD
*/
PIXI.WebGLBlendModeManager.prototype.setBlendMode = function(blendMode)
{
if(this.currentBlendMode === blendMode)return false;
this.currentBlendMode = blendMode;
var blendModeWebGL = PIXI.blendModesWebGL[this.currentBlendMode];
if (blendModeWebGL)
{
this.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]);
}
return true;
};
/**
* Destroys this object.
*
* @method destroy
*/
PIXI.WebGLBlendModeManager.prototype.destroy = function()
{
this.gl = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class WebGLMaskManager
* @constructor
* @private
*/
PIXI.WebGLMaskManager = function()
{
};
PIXI.WebGLMaskManager.prototype.constructor = PIXI.WebGLMaskManager;
/**
* Sets the drawing context to the one given in parameter.
*
* @method setContext
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLMaskManager.prototype.setContext = function(gl)
{
this.gl = gl;
};
/**
* Applies the Mask and adds it to the current filter stack.
*
* @method pushMask
* @param maskData {Array}
* @param renderSession {Object}
*/
PIXI.WebGLMaskManager.prototype.pushMask = function(maskData, renderSession)
{
var gl = renderSession.gl;
if (maskData.dirty)
{
PIXI.WebGLGraphics.updateGraphics(maskData, gl);
}
if (maskData._webGL[gl.id] === undefined || maskData._webGL[gl.id].data === undefined || maskData._webGL[gl.id].data.length === 0)
{
return;
}
renderSession.stencilManager.pushStencil(maskData, maskData._webGL[gl.id].data[0], renderSession);
};
/**
* Removes the last filter from the filter stack and doesn't return it.
*
* @method popMask
* @param maskData {Array}
* @param renderSession {Object} an object containing all the useful parameters
*/
PIXI.WebGLMaskManager.prototype.popMask = function(maskData, renderSession)
{
var gl = this.gl;
if (maskData._webGL[gl.id] === undefined || maskData._webGL[gl.id].data === undefined || maskData._webGL[gl.id].data.length === 0)
{
return;
}
renderSession.stencilManager.popStencil(maskData, maskData._webGL[gl.id].data[0], renderSession);
};
/**
* Destroys the mask stack.
*
* @method destroy
*/
PIXI.WebGLMaskManager.prototype.destroy = function()
{
this.gl = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class WebGLStencilManager
* @constructor
* @private
*/
PIXI.WebGLStencilManager = function()
{
this.stencilStack = [];
this.reverse = true;
this.count = 0;
};
/**
* Sets the drawing context to the one given in parameter.
*
* @method setContext
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLStencilManager.prototype.setContext = function(gl)
{
this.gl = gl;
};
/**
* Applies the Mask and adds it to the current filter stack.
*
* @method pushMask
* @param graphics {Graphics}
* @param webGLData {Array}
* @param renderSession {Object}
*/
PIXI.WebGLStencilManager.prototype.pushStencil = function(graphics, webGLData, renderSession)
{
var gl = this.gl;
this.bindGraphics(graphics, webGLData, renderSession);
if(this.stencilStack.length === 0)
{
gl.enable(gl.STENCIL_TEST);
gl.clear(gl.STENCIL_BUFFER_BIT);
this.reverse = true;
this.count = 0;
}
this.stencilStack.push(webGLData);
var level = this.count;
gl.colorMask(false, false, false, false);
gl.stencilFunc(gl.ALWAYS,0,0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT);
// draw the triangle strip!
if(webGLData.mode === 1)
{
gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 );
if(this.reverse)
{
gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);
}
else
{
gl.stencilFunc(gl.EQUAL,level, 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);
}
// draw a quad to increment..
gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 );
if(this.reverse)
{
gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF);
}
else
{
gl.stencilFunc(gl.EQUAL,level+1, 0xFF);
}
this.reverse = !this.reverse;
}
else
{
if(!this.reverse)
{
gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);
}
else
{
gl.stencilFunc(gl.EQUAL,level, 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);
}
gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 );
if(!this.reverse)
{
gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF);
}
else
{
gl.stencilFunc(gl.EQUAL,level+1, 0xFF);
}
}
gl.colorMask(true, true, true, true);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);
this.count++;
};
/**
* TODO this does not belong here!
*
* @method bindGraphics
* @param graphics {Graphics}
* @param webGLData {Array}
* @param renderSession {Object}
*/
PIXI.WebGLStencilManager.prototype.bindGraphics = function(graphics, webGLData, renderSession)
{
//if(this._currentGraphics === graphics)return;
this._currentGraphics = graphics;
var gl = this.gl;
// bind the graphics object..
var projection = renderSession.projection,
offset = renderSession.offset,
shader;// = renderSession.shaderManager.primitiveShader;
if(webGLData.mode === 1)
{
shader = renderSession.shaderManager.complexPrimitiveShader;
renderSession.shaderManager.setShader( shader );
gl.uniform1f(shader.flipY, renderSession.flipY);
gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true));
gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);
gl.uniform3fv(shader.tintColor, Phaser.Color.hexToRGBArray(graphics.tint));
gl.uniform3fv(shader.color, webGLData.color);
gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha);
gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer);
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0);
// now do the rest..
// set the index buffer!
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer);
}
else
{
//renderSession.shaderManager.activatePrimitiveShader();
shader = renderSession.shaderManager.primitiveShader;
renderSession.shaderManager.setShader( shader );
gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true));
gl.uniform1f(shader.flipY, renderSession.flipY);
gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);
gl.uniform3fv(shader.tintColor, Phaser.Color.hexToRGBArray(graphics.tint));
gl.uniform1f(shader.alpha, graphics.worldAlpha);
gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer);
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0);
gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4);
// set the index buffer!
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer);
}
};
/**
* @method popStencil
* @param graphics {Graphics}
* @param webGLData {Array}
* @param renderSession {Object}
*/
PIXI.WebGLStencilManager.prototype.popStencil = function(graphics, webGLData, renderSession)
{
var gl = this.gl;
this.stencilStack.pop();
this.count--;
if(this.stencilStack.length === 0)
{
// the stack is empty!
gl.disable(gl.STENCIL_TEST);
}
else
{
var level = this.count;
this.bindGraphics(graphics, webGLData, renderSession);
gl.colorMask(false, false, false, false);
if(webGLData.mode === 1)
{
this.reverse = !this.reverse;
if(this.reverse)
{
gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);
}
else
{
gl.stencilFunc(gl.EQUAL,level+1, 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);
}
// draw a quad to increment..
gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 );
gl.stencilFunc(gl.ALWAYS,0,0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT);
// draw the triangle strip!
gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 );
if(!this.reverse)
{
gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF);
}
else
{
gl.stencilFunc(gl.EQUAL,level, 0xFF);
}
}
else
{
// console.log("<<>>")
if(!this.reverse)
{
gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR);
}
else
{
gl.stencilFunc(gl.EQUAL,level+1, 0xFF);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR);
}
gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 );
if(!this.reverse)
{
gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF);
}
else
{
gl.stencilFunc(gl.EQUAL,level, 0xFF);
}
}
gl.colorMask(true, true, true, true);
gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);
}
};
/**
* Destroys the mask stack.
*
* @method destroy
*/
PIXI.WebGLStencilManager.prototype.destroy = function()
{
this.stencilStack = null;
this.gl = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class WebGLShaderManager
* @constructor
* @private
*/
PIXI.WebGLShaderManager = function()
{
/**
* @property maxAttibs
* @type Number
*/
this.maxAttibs = 10;
/**
* @property attribState
* @type Array
*/
this.attribState = [];
/**
* @property tempAttribState
* @type Array
*/
this.tempAttribState = [];
for (var i = 0; i < this.maxAttibs; i++)
{
this.attribState[i] = false;
}
/**
* @property stack
* @type Array
*/
this.stack = [];
};
PIXI.WebGLShaderManager.prototype.constructor = PIXI.WebGLShaderManager;
/**
* Initialises the context and the properties.
*
* @method setContext
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLShaderManager.prototype.setContext = function(gl)
{
this.gl = gl;
// the next one is used for rendering primitives
this.primitiveShader = new PIXI.PrimitiveShader(gl);
// the next one is used for rendering triangle strips
this.complexPrimitiveShader = new PIXI.ComplexPrimitiveShader(gl);
// this shader is used for the default sprite rendering
this.defaultShader = new PIXI.PixiShader(gl);
// this shader is used for the fast sprite rendering
this.fastShader = new PIXI.PixiFastShader(gl);
// the next one is used for rendering triangle strips
this.stripShader = new PIXI.StripShader(gl);
this.setShader(this.defaultShader);
};
/**
* Takes the attributes given in parameters.
*
* @method setAttribs
* @param attribs {Array} attribs
*/
PIXI.WebGLShaderManager.prototype.setAttribs = function(attribs)
{
// reset temp state
var i;
for (i = 0; i < this.tempAttribState.length; i++)
{
this.tempAttribState[i] = false;
}
// set the new attribs
for (i = 0; i < attribs.length; i++)
{
var attribId = attribs[i];
this.tempAttribState[attribId] = true;
}
var gl = this.gl;
for (i = 0; i < this.attribState.length; i++)
{
if(this.attribState[i] !== this.tempAttribState[i])
{
this.attribState[i] = this.tempAttribState[i];
if(this.tempAttribState[i])
{
gl.enableVertexAttribArray(i);
}
else
{
gl.disableVertexAttribArray(i);
}
}
}
};
/**
* Sets the current shader.
*
* @method setShader
* @param shader {Any}
*/
PIXI.WebGLShaderManager.prototype.setShader = function(shader)
{
if(this._currentId === shader._UID)return false;
this._currentId = shader._UID;
this.currentShader = shader;
this.gl.useProgram(shader.program);
this.setAttribs(shader.attributes);
return true;
};
/**
* Destroys this object.
*
* @method destroy
*/
PIXI.WebGLShaderManager.prototype.destroy = function()
{
this.attribState = null;
this.tempAttribState = null;
this.primitiveShader.destroy();
this.complexPrimitiveShader.destroy();
this.defaultShader.destroy();
this.fastShader.destroy();
this.stripShader.destroy();
this.gl = null;
};
/**
* @author Mat Groves
*
* Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/
* for creating the original pixi version!
* Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer
*
* Heavily inspired by LibGDX's WebGLSpriteBatch:
* https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java
*/
/**
*
* @class WebGLSpriteBatch
* @private
* @constructor
*/
PIXI.WebGLSpriteBatch = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property vertSize
* @type Number
*/
this.vertSize = 5;
/**
* The number of images in the SpriteBatch before it flushes
* @property size
* @type Number
*/
this.size = 2000; //Math.pow(2, 16) / this.vertSize;
//the total number of bytes in our batch
// Including texture index:
// position + uv + color + textureIndex
// vec2 + vec2 + (char * 4) + float
this.vertexSize = (4 * 2) + (4 * 2) + (4) + (4);
var numVerts = this.vertexSize * this.size * 4;
//this.size * 4 * 4 * this.vertSize;
//the total number of indices in our batch
var numIndices = this.size * 6;
/**
* Holds the vertices
*
* @property vertices
* @type ArrayBuffer
*/
this.vertices = new ArrayBuffer(numVerts);
/**
* View on the vertices as a Float32Array
*
* @property positions
* @type Float32Array
*/
this.positions = new Float32Array(this.vertices);
/**
* View on the vertices as a Uint32Array
*
* @property colors
* @type Uint32Array
*/
this.colors = new Uint32Array(this.vertices);
/**
* Holds the indices
*
* @property indices
* @type Uint16Array
*/
this.indices = new Uint16Array(numIndices);
/**
* @property lastIndexCount
* @type Number
*/
this.lastIndexCount = 0;
for (var i = 0, j = 0; i < numIndices; i += 6, j += 4) {
this.indices[i + 0] = j + 0;
this.indices[i + 1] = j + 1;
this.indices[i + 2] = j + 2;
this.indices[i + 3] = j + 0;
this.indices[i + 4] = j + 2;
this.indices[i + 5] = j + 3;
}
/**
* @property drawing
* @type Boolean
*/
this.drawing = false;
/**
* @property currentBatchSize
* @type Number
*/
this.currentBatchSize = 0;
/**
* @property currentBaseTexture
* @type BaseTexture
*/
this.currentBaseTexture = null;
/**
* @property dirty
* @type Boolean
*/
this.dirty = true;
/**
* @property textures
* @type Array
*/
this.textures = [];
/**
* @property blendModes
* @type Array
*/
this.blendModes = [];
/**
* @property shaders
* @type Array
*/
this.shaders = [];
/**
* @property sprites
* @type Array
*/
this.sprites = [];
/**
* @property defaultShader
* @type Phaser.Filter
*/
this.defaultShader = null;
};
/**
* @method setContext
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLSpriteBatch.prototype.setContext = function (gl) {
this.MAX_TEXTURES = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
this.gl = gl;
if (PIXI._enableMultiTextureToggle) {
var dynamicIfs = '\tif (vTextureIndex == 0.0) gl_FragColor = texture2D(uSamplerArray[0], vTextureCoord) * vColor;\n'
for (var index = 1; index < this.MAX_TEXTURES; ++index) {
dynamicIfs += '\telse if (vTextureIndex == ' +
index + '.0) gl_FragColor = texture2D(uSamplerArray[' +
index + '], vTextureCoord) * vColor;\n'
}
this.defaultShader = new Phaser.Filter(
this.game,
undefined,
[
'//WebGLSpriteBatch Fragment Shader.',
'precision lowp float;',
'varying vec2 vTextureCoord;',
'varying vec4 vColor;',
'varying float vTextureIndex;',
'uniform sampler2D uSamplerArray[' + this.MAX_TEXTURES + '];',
'void main(void) {',
dynamicIfs,
'\telse gl_FragColor = texture2D(uSamplerArray[0], vTextureCoord) * vColor;',
'}'
]);
}
else
{
this.defaultShader = new Phaser.Filter(
this.game,
undefined,
[
'//WebGLSpriteBatch Fragment Shader.',
'precision lowp float;',
'varying vec2 vTextureCoord;',
'varying vec4 vColor;',
'varying float vTextureIndex;',
'uniform sampler2D uSampler;',
'void main(void) {',
' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor;',
'}'
]);
}
// create a couple of buffers
this.vertexBuffer = gl.createBuffer();
this.indexBuffer = gl.createBuffer();
// 65535 is max index, so 65535 / 6 = 10922.
//upload the index data
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW);
this.currentBlendMode = 99999;
var shader = new PIXI.PixiShader(gl);
shader.fragmentSrc = this.defaultShader.fragmentSrc;
shader.uniforms = {};
shader.init();
this.defaultShader.shaders[gl.id] = shader;
};
/**
* @method begin
* @param renderSession {Object} The RenderSession object
*/
PIXI.WebGLSpriteBatch.prototype.begin = function (renderSession) {
this.renderSession = renderSession;
this.shader = this.renderSession.shaderManager.defaultShader;
this.start();
};
/**
* @method end
*/
PIXI.WebGLSpriteBatch.prototype.end = function () {
this.flush();
};
/**
* @method render
* @param sprite {Sprite} the sprite to render when using this spritebatch
* @param {Matrix} [matrix] - Optional matrix. If provided the Display Object will be rendered using this matrix, otherwise it will use its worldTransform.
*/
PIXI.WebGLSpriteBatch.prototype.render = function (sprite, matrix) {
var texture = sprite.texture;
var baseTexture = texture.baseTexture;
var gl = this.gl;
if (PIXI.WebGLRenderer.textureArray[baseTexture.textureIndex] != baseTexture) {
this.flush();
gl.activeTexture(gl.TEXTURE0 + baseTexture.textureIndex);
gl.bindTexture(gl.TEXTURE_2D, baseTexture._glTextures[gl.id]);
PIXI.WebGLRenderer.textureArray[baseTexture.textureIndex] = baseTexture;
}
// They provided an alternative rendering matrix, so use it
var wt = sprite.worldTransform;
if (matrix) {
wt = matrix;
}
// check texture..
if (this.currentBatchSize >= this.size) {
this.flush();
this.currentBaseTexture = texture.baseTexture;
}
// get the uvs for the texture
var uvs = texture._uvs;
// if the uvs have not updated then no point rendering just yet!
if (!uvs) {
return;
}
var aX = sprite.anchor.x;
var aY = sprite.anchor.y;
var w0, w1, h0, h1;
if (texture.trim) {
// if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.
var trim = texture.trim;
w1 = trim.x - aX * trim.width;
w0 = w1 + texture.crop.width;
h1 = trim.y - aY * trim.height;
h0 = h1 + texture.crop.height;
} else {
w0 = (texture.frame.width) * (1 - aX);
w1 = (texture.frame.width) * -aX;
h0 = texture.frame.height * (1 - aY);
h1 = texture.frame.height * -aY;
}
var i = this.currentBatchSize * this.vertexSize; //4 * this.vertSize;
var tiOffset = this.currentBatchSize * 4;
var resolution = texture.baseTexture.resolution;
var textureIndex = texture.baseTexture.textureIndex;
var a = wt.a / resolution;
var b = wt.b / resolution;
var c = wt.c / resolution;
var d = wt.d / resolution;
var tx = wt.tx;
var ty = wt.ty;
var cw = texture.crop.width;
var ch = texture.crop.height;
if (texture.rotated)
{
var a0 = wt.a;
var b0 = wt.b;
var c0 = wt.c;
var d0 = wt.d;
var _w1 = w1;
var _w0 = w0;
// Offset before rotating
tx = wt.c * ch + tx;
ty = wt.d * ch + ty;
// Rotate matrix by 90 degrees
// We use precalculated values for sine and cosine of rad(90)
a = a0 * 6.123233995736766e-17 + -c0;
b = b0 * 6.123233995736766e-17 + -d0;
c = a0 + c0 * 6.123233995736766e-17;
d = b0 + d0 * 6.123233995736766e-17;
// Update UV coordinates
texture._updateUvsInverted();
// Rotate dimensions
w0 = h0;
w1 = h1;
h0 = _w0;
h1 = _w1;
}
var colors = this.colors;
var positions = this.positions;
var tint = sprite.tint;
var color = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24);
if (this.renderSession.roundPixels) {
positions[i++] = a * w1 + c * h1 + tx | 0;
positions[i++] = d * h1 + b * w1 + ty | 0;
positions[i++] = uvs.x0;
positions[i++] = uvs.y0;
colors[i++] = color;
positions[i++] = textureIndex;
positions[i++] = a * w0 + c * h1 + tx | 0;
positions[i++] = d * h1 + b * w0 + ty | 0;
positions[i++] = uvs.x1;
positions[i++] = uvs.y1;
colors[i++] = color;
positions[i++] = textureIndex;
positions[i++] = a * w0 + c * h0 + tx | 0;
positions[i++] = d * h0 + b * w0 + ty | 0;
positions[i++] = uvs.x2;
positions[i++] = uvs.y2;
colors[i++] = color;
positions[i++] = textureIndex;
positions[i++] = a * w1 + c * h0 + tx | 0;
positions[i++] = d * h0 + b * w1 + ty | 0;
positions[i++] = uvs.x3;
positions[i++] = uvs.y3;
colors[i++] = color;
positions[i++] = textureIndex;
} else {
positions[i++] = a * w1 + c * h1 + tx;
positions[i++] = d * h1 + b * w1 + ty;
positions[i++] = uvs.x0;
positions[i++] = uvs.y0;
colors[i++] = color;
positions[i++] = textureIndex;
positions[i++] = a * w0 + c * h1 + tx;
positions[i++] = d * h1 + b * w0 + ty;
positions[i++] = uvs.x1;
positions[i++] = uvs.y1;
colors[i++] = color;
positions[i++] = textureIndex;
positions[i++] = a * w0 + c * h0 + tx;
positions[i++] = d * h0 + b * w0 + ty;
positions[i++] = uvs.x2;
positions[i++] = uvs.y2;
colors[i++] = color;
positions[i++] = textureIndex;
positions[i++] = a * w1 + c * h0 + tx;
positions[i++] = d * h0 + b * w1 + ty;
positions[i++] = uvs.x3;
positions[i++] = uvs.y3;
colors[i++] = color;
positions[i++] = textureIndex;
}
// increment the batchsize
this.sprites[this.currentBatchSize++] = sprite;
};
/**
* Renders a TilingSprite using the spriteBatch.
*
* @method renderTilingSprite
* @param sprite {TilingSprite} the sprite to render
*/
PIXI.WebGLSpriteBatch.prototype.renderTilingSprite = function (sprite) {
var texture = sprite.tilingTexture;
var baseTexture = texture.baseTexture;
var gl = this.gl;
var textureIndex = sprite.texture.baseTexture.textureIndex;
if (PIXI.WebGLRenderer.textureArray[textureIndex] != baseTexture) {
this.flush();
gl.activeTexture(gl.TEXTURE0 + textureIndex);
gl.bindTexture(gl.TEXTURE_2D, baseTexture._glTextures[gl.id]);
PIXI.WebGLRenderer.textureArray[textureIndex] = baseTexture;
}
// check texture..
if (this.currentBatchSize >= this.size) {
this.flush();
this.currentBaseTexture = texture.baseTexture;
}
// set the textures uvs temporarily
if (!sprite._uvs) {
sprite._uvs = new PIXI.TextureUvs();
}
var uvs = sprite._uvs;
var w = texture.baseTexture.width;
var h = texture.baseTexture.height;
// var w = sprite._frame.sourceSizeW;
// var h = sprite._frame.sourceSizeH;
// w = 16;
// h = 16;
sprite.tilePosition.x %= w * sprite.tileScaleOffset.x;
sprite.tilePosition.y %= h * sprite.tileScaleOffset.y;
var offsetX = sprite.tilePosition.x / (w * sprite.tileScaleOffset.x);
var offsetY = sprite.tilePosition.y / (h * sprite.tileScaleOffset.y);
var scaleX = (sprite.width / w) / (sprite.tileScale.x * sprite.tileScaleOffset.x);
var scaleY = (sprite.height / h) / (sprite.tileScale.y * sprite.tileScaleOffset.y);
uvs.x0 = 0 - offsetX;
uvs.y0 = 0 - offsetY;
uvs.x1 = (1 * scaleX) - offsetX;
uvs.y1 = 0 - offsetY;
uvs.x2 = (1 * scaleX) - offsetX;
uvs.y2 = (1 * scaleY) - offsetY;
uvs.x3 = 0 - offsetX;
uvs.y3 = (1 * scaleY) - offsetY;
// Get the sprites current alpha and tint and combine them into a single color
var tint = sprite.tint;
var color = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24);
var positions = this.positions;
var colors = this.colors;
var width = sprite.width;
var height = sprite.height;
// TODO trim??
var aX = sprite.anchor.x;
var aY = sprite.anchor.y;
var w0 = width * (1 - aX);
var w1 = width * -aX;
var h0 = height * (1 - aY);
var h1 = height * -aY;
var i = this.currentBatchSize * this.vertexSize; //4 * this.vertSize;
var resolution = texture.baseTexture.resolution;
var wt = sprite.worldTransform;
var a = wt.a / resolution;
var b = wt.b / resolution;
var c = wt.c / resolution;
var d = wt.d / resolution;
var tx = wt.tx;
var ty = wt.ty;
// xy
positions[i++] = a * w1 + c * h1 + tx;
positions[i++] = d * h1 + b * w1 + ty;
// uv
positions[i++] = uvs.x0;
positions[i++] = uvs.y0;
// color
colors[i++] = color;
// texture index
positions[i++] = textureIndex;
// xy
positions[i++] = (a * w0 + c * h1 + tx);
positions[i++] = d * h1 + b * w0 + ty;
// uv
positions[i++] = uvs.x1;
positions[i++] = uvs.y1;
// color
colors[i++] = color;
// texture index
positions[i++] = textureIndex;
// xy
positions[i++] = a * w0 + c * h0 + tx;
positions[i++] = d * h0 + b * w0 + ty;
// uv
positions[i++] = uvs.x2;
positions[i++] = uvs.y2;
// color
colors[i++] = color;
// texture index
positions[i++] = textureIndex;
// xy
positions[i++] = a * w1 + c * h0 + tx;
positions[i++] = d * h0 + b * w1 + ty;
// uv
positions[i++] = uvs.x3;
positions[i++] = uvs.y3;
// color
colors[i++] = color;
// texture index
positions[i++] = textureIndex;
// increment the batchsize
this.sprites[this.currentBatchSize++] = sprite;
};
/**
* Renders the content and empties the current batch.
*
* @method flush
*/
PIXI.WebGLSpriteBatch.prototype.flush = function () {
// If the batch is length 0 then return as there is nothing to draw
if (this.currentBatchSize === 0) {
return;
}
var gl = this.gl;
var shader;
if (this.dirty) {
this.dirty = false;
shader = this.defaultShader.shaders[gl.id];
// bind the main texture
gl.activeTexture(gl.TEXTURE0);
// bind the buffers
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
// this is the same for each shader?
var stride = this.vertexSize; //this.vertSize * 4;
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0);
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, stride, 8);
// color attributes will be interpreted as unsigned bytes and normalized
gl.vertexAttribPointer(shader.colorAttribute, 4, gl.UNSIGNED_BYTE, true, stride, 16);
// Texture index
gl.vertexAttribPointer(shader.aTextureIndex, 1, gl.FLOAT, false, stride, 20);
}
// upload the verts to the buffer
if (this.currentBatchSize > (this.size * 0.5)) {
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices);
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
var view = this.positions.subarray(0, this.currentBatchSize * this.vertexSize);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, view);
}
var nextTexture, nextBlendMode, nextShader;
var batchSize = 0;
var start = 0;
var currentBaseTexture = null;
var currentBlendMode = this.renderSession.blendModeManager.currentBlendMode;
var currentShader = null;
var blendSwap = false;
var shaderSwap = false;
var sprite;
var textureIndex = 0;
for (var i = 0, j = this.currentBatchSize; i < j; i++) {
sprite = this.sprites[i];
if (sprite.tilingTexture) {
nextTexture = sprite.tilingTexture.baseTexture;
} else {
nextTexture = sprite.texture.baseTexture;
}
nextBlendMode = sprite.blendMode;
nextShader = sprite.shader || this.defaultShader;
blendSwap = currentBlendMode !== nextBlendMode;
shaderSwap = currentShader !== nextShader; // should I use _UIDS???
var skip = nextTexture.skipRender;
if (skip && sprite.children.length > 0) {
skip = false;
}
//
if (/*(currentBaseTexture != nextTexture && !skip) ||*/
blendSwap ||
shaderSwap) {
this.renderBatch(currentBaseTexture, batchSize, start);
start = i;
batchSize = 0;
currentBaseTexture = nextTexture;
if (blendSwap) {
currentBlendMode = nextBlendMode;
this.renderSession.blendModeManager.setBlendMode(currentBlendMode);
}
if (shaderSwap) {
currentShader = nextShader;
shader = currentShader.shaders[gl.id];
if (!shader) {
shader = new PIXI.PixiShader(gl);
shader.fragmentSrc = currentShader.fragmentSrc;
shader.uniforms = currentShader.uniforms;
shader.init();
currentShader.shaders[gl.id] = shader;
}
// set shader function???
this.renderSession.shaderManager.setShader(shader);
if (shader.dirty) {
shader.syncUniforms();
}
// both these only need to be set if they are changing..
// set the projection
var projection = this.renderSession.projection;
gl.uniform2f(shader.projectionVector, projection.x, projection.y);
// TODO - this is temporary!
var offsetVector = this.renderSession.offset;
gl.uniform2f(shader.offsetVector, offsetVector.x, offsetVector.y);
// set the pointers
}
}
batchSize++;
}
this.renderBatch(currentBaseTexture, batchSize, start);
// then reset the batch!
this.currentBatchSize = 0;
};
/**
* @method renderBatch
* @param texture {Texture}
* @param size {Number}
* @param startIndex {Number}
*/
PIXI.WebGLSpriteBatch.prototype.renderBatch = function (texture, size, startIndex) {
if (size === 0) {
return;
}
var gl = this.gl;
// check if a texture is dirty..
if (texture._dirty[gl.id]) {
if (!this.renderSession.renderer.updateTexture(texture)) {
// If updateTexture returns false then we cannot render it, so bail out now
return;
}
}
gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2);
// increment the draw count
this.renderSession.drawCount++;
};
/**
* @method stop
*/
PIXI.WebGLSpriteBatch.prototype.stop = function () {
this.flush();
this.dirty = true;
};
/**
* @method start
*/
PIXI.WebGLSpriteBatch.prototype.start = function () {
this.dirty = true;
};
/**
* Destroys the SpriteBatch.
*
* @method destroy
*/
PIXI.WebGLSpriteBatch.prototype.destroy = function () {
this.vertices = null;
this.indices = null;
this.gl.deleteBuffer(this.vertexBuffer);
this.gl.deleteBuffer(this.indexBuffer);
this.currentBaseTexture = null;
this.gl = null;
};
/**
* @author Mat Groves
*
* Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/
* for creating the original pixi version!
*
* Heavily inspired by LibGDX's WebGLSpriteBatch:
* https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/WebGLSpriteBatch.java
*/
/**
* @class WebGLFastSpriteBatch
* @constructor
*/
PIXI.WebGLFastSpriteBatch = function(gl)
{
/**
* @property vertSize
* @type Number
*/
this.vertSize = 11;
/**
* @property maxSize
* @type Number
*/
this.maxSize = 6000;//Math.pow(2, 16) / this.vertSize;
/**
* @property size
* @type Number
*/
this.size = this.maxSize;
//the total number of floats in our batch
var numVerts = this.size * 4 * this.vertSize;
//the total number of indices in our batch
var numIndices = this.maxSize * 6;
/**
* Vertex data
* @property vertices
* @type Float32Array
*/
this.vertices = new Float32Array(numVerts);
/**
* Index data
* @property indices
* @type Uint16Array
*/
this.indices = new Uint16Array(numIndices);
/**
* @property vertexBuffer
* @type Object
*/
this.vertexBuffer = null;
/**
* @property indexBuffer
* @type Object
*/
this.indexBuffer = null;
/**
* @property lastIndexCount
* @type Number
*/
this.lastIndexCount = 0;
for (var i=0, j=0; i < numIndices; i += 6, j += 4)
{
this.indices[i + 0] = j + 0;
this.indices[i + 1] = j + 1;
this.indices[i + 2] = j + 2;
this.indices[i + 3] = j + 0;
this.indices[i + 4] = j + 2;
this.indices[i + 5] = j + 3;
}
/**
* @property drawing
* @type Boolean
*/
this.drawing = false;
/**
* @property currentBatchSize
* @type Number
*/
this.currentBatchSize = 0;
/**
* @property currentBaseTexture
* @type BaseTexture
*/
this.currentBaseTexture = null;
/**
* @property currentBlendMode
* @type Number
*/
this.currentBlendMode = 0;
/**
* @property renderSession
* @type Object
*/
this.renderSession = null;
/**
* @property shader
* @type Object
*/
this.shader = null;
/**
* @property matrix
* @type Matrix
*/
this.matrix = null;
this.setContext(gl);
};
PIXI.WebGLFastSpriteBatch.prototype.constructor = PIXI.WebGLFastSpriteBatch;
/**
* Sets the WebGL Context.
*
* @method setContext
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLFastSpriteBatch.prototype.setContext = function(gl)
{
this.gl = gl;
// create a couple of buffers
this.vertexBuffer = gl.createBuffer();
this.indexBuffer = gl.createBuffer();
// 65535 is max index, so 65535 / 6 = 10922.
//upload the index data
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW);
};
/**
* @method begin
* @param spriteBatch {WebGLSpriteBatch}
* @param renderSession {Object}
*/
PIXI.WebGLFastSpriteBatch.prototype.begin = function(spriteBatch, renderSession)
{
this.renderSession = renderSession;
this.shader = this.renderSession.shaderManager.fastShader;
this.matrix = spriteBatch.worldTransform.toArray(true);
this.start();
};
/**
* @method end
*/
PIXI.WebGLFastSpriteBatch.prototype.end = function()
{
this.flush();
};
/**
* @method render
* @param spriteBatch {WebGLSpriteBatch}
*/
PIXI.WebGLFastSpriteBatch.prototype.render = function (spriteBatch)
{
var children = spriteBatch.children;
var sprite = children[0];
// if the uvs have not updated then no point rendering just yet!
// check texture.
if(!sprite.texture._uvs)return;
this.currentBaseTexture = sprite.texture.baseTexture;
// check blend mode
if(sprite.blendMode !== this.renderSession.blendModeManager.currentBlendMode)
{
this.flush();
this.renderSession.blendModeManager.setBlendMode(sprite.blendMode);
}
for(var i=0,j= children.length; i<j; i++)
{
this.renderSprite(children[i]);
}
this.flush();
};
/**
* @method renderSprite
* @param sprite {Sprite}
*/
PIXI.WebGLFastSpriteBatch.prototype.renderSprite = function(sprite)
{
var texture = sprite.texture;
var baseTexture = texture.baseTexture;
var gl = this.gl;
var textureIndex = sprite.texture.baseTexture.textureIndex;
if (PIXI.WebGLRenderer.textureArray[textureIndex] != baseTexture &&
baseTexture._glTextures[gl.id] && !sprite.texture.baseTexture.skipRender) {
this.flush();
gl.activeTexture(gl.TEXTURE0 + textureIndex);
gl.bindTexture(gl.TEXTURE_2D, baseTexture._glTextures[gl.id]);
PIXI.WebGLRenderer.textureArray[textureIndex] = baseTexture;
if(!sprite.texture._uvs)return;
}
//sprite = children[i];
if(!sprite.visible)return;
var uvs, vertices = this.vertices, width, height, w0, w1, h0, h1, index;
uvs = sprite.texture._uvs;
width = sprite.texture.frame.width;
height = sprite.texture.frame.height;
if (sprite.texture.trim)
{
// if the sprite is trimmed then we need to add the extra space before transforming the sprite coords..
var trim = sprite.texture.trim;
w1 = trim.x - sprite.anchor.x * trim.width;
w0 = w1 + sprite.texture.crop.width;
h1 = trim.y - sprite.anchor.y * trim.height;
h0 = h1 + sprite.texture.crop.height;
}
else
{
w0 = (sprite.texture.frame.width ) * (1-sprite.anchor.x);
w1 = (sprite.texture.frame.width ) * -sprite.anchor.x;
h0 = sprite.texture.frame.height * (1-sprite.anchor.y);
h1 = sprite.texture.frame.height * -sprite.anchor.y;
}
index = this.currentBatchSize * 4 * this.vertSize;
// xy
vertices[index++] = w1;
vertices[index++] = h1;
vertices[index++] = sprite.position.x;
vertices[index++] = sprite.position.y;
//scale
vertices[index++] = sprite.scale.x;
vertices[index++] = sprite.scale.y;
//rotation
vertices[index++] = sprite.rotation;
// uv
vertices[index++] = uvs.x0;
vertices[index++] = uvs.y1;
// color
vertices[index++] = sprite.alpha;
// texture Index
vertices[index++] = textureIndex;
// xy
vertices[index++] = w0;
vertices[index++] = h1;
vertices[index++] = sprite.position.x;
vertices[index++] = sprite.position.y;
//scale
vertices[index++] = sprite.scale.x;
vertices[index++] = sprite.scale.y;
//rotation
vertices[index++] = sprite.rotation;
// uv
vertices[index++] = uvs.x1;
vertices[index++] = uvs.y1;
// color
vertices[index++] = sprite.alpha;
// texture Index
vertices[index++] = textureIndex;
// xy
vertices[index++] = w0;
vertices[index++] = h0;
vertices[index++] = sprite.position.x;
vertices[index++] = sprite.position.y;
//scale
vertices[index++] = sprite.scale.x;
vertices[index++] = sprite.scale.y;
//rotation
vertices[index++] = sprite.rotation;
// uv
vertices[index++] = uvs.x2;
vertices[index++] = uvs.y2;
// color
vertices[index++] = sprite.alpha;
// texture Index
vertices[index++] = textureIndex;
// xy
vertices[index++] = w1;
vertices[index++] = h0;
vertices[index++] = sprite.position.x;
vertices[index++] = sprite.position.y;
//scale
vertices[index++] = sprite.scale.x;
vertices[index++] = sprite.scale.y;
//rotation
vertices[index++] = sprite.rotation;
// uv
vertices[index++] = uvs.x3;
vertices[index++] = uvs.y3;
// color
vertices[index++] = sprite.alpha;
// texture Index
vertices[index++] = textureIndex;
// increment the batchs
this.currentBatchSize++;
if(this.currentBatchSize >= this.size)
{
this.flush();
}
};
/**
* @method flush
*/
PIXI.WebGLFastSpriteBatch.prototype.flush = function()
{
// If the batch is length 0 then return as there is nothing to draw
if (this.currentBatchSize===0)return;
var gl = this.gl;
// bind the current texture
if(!this.currentBaseTexture._glTextures[gl.id]) {
this.renderSession.renderer.updateTexture(this.currentBaseTexture, gl);
return;
}
//gl.bindTexture(gl.TEXTURE_2D, this.currentBaseTexture._glTextures[gl.id]);
// upload the verts to the buffer
if(this.currentBatchSize > ( this.size * 0.5 ) )
{
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices);
}
else
{
var view = this.vertices.subarray(0, this.currentBatchSize * 4 * this.vertSize);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, view);
}
// now draw those suckas!
gl.drawElements(gl.TRIANGLES, this.currentBatchSize * 6, gl.UNSIGNED_SHORT, 0);
// then reset the batch!
this.currentBatchSize = 0;
// increment the draw count
this.renderSession.drawCount++;
};
/**
* @method stop
*/
PIXI.WebGLFastSpriteBatch.prototype.stop = function()
{
this.flush();
};
/**
* @method start
*/
PIXI.WebGLFastSpriteBatch.prototype.start = function()
{
var gl = this.gl;
// bind the main texture
gl.activeTexture(gl.TEXTURE0);
// bind the buffers
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
// set the projection
var projection = this.renderSession.projection;
gl.uniform2f(this.shader.projectionVector, projection.x, projection.y);
// set the matrix
gl.uniformMatrix3fv(this.shader.uMatrix, false, this.matrix);
// set the pointers
var stride = this.vertSize * 4;
gl.vertexAttribPointer(this.shader.aVertexPosition, 2, gl.FLOAT, false, stride, 0);
gl.vertexAttribPointer(this.shader.aPositionCoord, 2, gl.FLOAT, false, stride, 2 * 4);
gl.vertexAttribPointer(this.shader.aScale, 2, gl.FLOAT, false, stride, 4 * 4);
gl.vertexAttribPointer(this.shader.aRotation, 1, gl.FLOAT, false, stride, 6 * 4);
gl.vertexAttribPointer(this.shader.aTextureCoord, 2, gl.FLOAT, false, stride, 7 * 4);
gl.vertexAttribPointer(this.shader.colorAttribute, 1, gl.FLOAT, false, stride, 9 * 4);
gl.vertexAttribPointer(this.shader.aTextureIndex, 1, gl.FLOAT, false, stride, 10 * 4);
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* @class WebGLFilterManager
* @constructor
*/
PIXI.WebGLFilterManager = function()
{
/**
* @property filterStack
* @type Array
*/
this.filterStack = [];
/**
* @property offsetX
* @type Number
*/
this.offsetX = 0;
/**
* @property offsetY
* @type Number
*/
this.offsetY = 0;
};
PIXI.WebGLFilterManager.prototype.constructor = PIXI.WebGLFilterManager;
/**
* Initialises the context and the properties.
*
* @method setContext
* @param gl {WebGLContext} the current WebGL drawing context
*/
PIXI.WebGLFilterManager.prototype.setContext = function(gl)
{
this.gl = gl;
this.texturePool = [];
this.initShaderBuffers();
};
/**
* @method begin
* @param renderSession {RenderSession}
* @param buffer {ArrayBuffer}
*/
PIXI.WebGLFilterManager.prototype.begin = function(renderSession, buffer)
{
this.renderSession = renderSession;
this.defaultShader = renderSession.shaderManager.defaultShader;
var projection = this.renderSession.projection;
this.width = projection.x * 2;
this.height = -projection.y * 2;
this.buffer = buffer;
};
/**
* Applies the filter and adds it to the current filter stack.
*
* @method pushFilter
* @param filterBlock {Object} the filter that will be pushed to the current filter stack
*/
PIXI.WebGLFilterManager.prototype.pushFilter = function(filterBlock)
{
var gl = this.gl;
var projection = this.renderSession.projection;
var offset = this.renderSession.offset;
filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds();
// >>> modify by nextht
filterBlock._previous_stencil_mgr = this.renderSession.stencilManager;
this.renderSession.stencilManager = new PIXI.WebGLStencilManager();
this.renderSession.stencilManager.setContext(gl);
gl.disable(gl.STENCIL_TEST);
// <<< modify by nextht
// filter program
// OPTIMISATION - the first filter is free if its a simple color change?
this.filterStack.push(filterBlock);
var filter = filterBlock.filterPasses[0];
this.offsetX += filterBlock._filterArea.x;
this.offsetY += filterBlock._filterArea.y;
var texture = this.texturePool.pop();
if(!texture)
{
texture = new PIXI.FilterTexture(this.gl, this.width * this.renderSession.resolution, this.height * this.renderSession.resolution);
}
else
{
texture.resize(this.width * this.renderSession.resolution, this.height * this.renderSession.resolution);
}
gl.bindTexture(gl.TEXTURE_2D, texture.texture);
var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea;
var padding = filter.padding;
filterArea.x -= padding;
filterArea.y -= padding;
filterArea.width += padding * 2;
filterArea.height += padding * 2;
// cap filter to screen size..
if(filterArea.x < 0)filterArea.x = 0;
if(filterArea.width > this.width)filterArea.width = this.width;
if(filterArea.y < 0)filterArea.y = 0;
if(filterArea.height > this.height)filterArea.height = this.height;
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer);
// set view port
gl.viewport(0, 0, filterArea.width * this.renderSession.resolution, filterArea.height * this.renderSession.resolution);
projection.x = filterArea.width/2;
projection.y = -filterArea.height/2;
offset.x = -filterArea.x;
offset.y = -filterArea.y;
// update projection
// now restore the regular shader..
// this.renderSession.shaderManager.setShader(this.defaultShader);
//gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2);
//gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y);
gl.colorMask(true, true, true, true);
gl.clearColor(0,0,0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
filterBlock._glFilterTexture = texture;
};
/**
* Removes the last filter from the filter stack and doesn't return it.
*
* @method popFilter
*/
PIXI.WebGLFilterManager.prototype.popFilter = function()
{
var gl = this.gl;
var filterBlock = this.filterStack.pop();
var filterArea = filterBlock._filterArea;
var texture = filterBlock._glFilterTexture;
var projection = this.renderSession.projection;
var offset = this.renderSession.offset;
if(filterBlock.filterPasses.length > 1)
{
gl.viewport(0, 0, filterArea.width * this.renderSession.resolution, filterArea.height * this.renderSession.resolution);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
this.vertexArray[0] = 0;
this.vertexArray[1] = filterArea.height;
this.vertexArray[2] = filterArea.width;
this.vertexArray[3] = filterArea.height;
this.vertexArray[4] = 0;
this.vertexArray[5] = 0;
this.vertexArray[6] = filterArea.width;
this.vertexArray[7] = 0;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray);
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
// now set the uvs..
this.uvArray[2] = filterArea.width/this.width;
this.uvArray[5] = filterArea.height/this.height;
this.uvArray[6] = filterArea.width/this.width;
this.uvArray[7] = filterArea.height/this.height;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray);
var inputTexture = texture;
var outputTexture = this.texturePool.pop();
if(!outputTexture)outputTexture = new PIXI.FilterTexture(this.gl, this.width * this.renderSession.resolution, this.height * this.renderSession.resolution);
outputTexture.resize(this.width * this.renderSession.resolution, this.height * this.renderSession.resolution);
// need to clear this FBO as it may have some left over elements from a previous filter.
gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer );
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.BLEND);
for (var i = 0; i < filterBlock.filterPasses.length-1; i++)
{
var filterPass = filterBlock.filterPasses[i];
gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer );
// set texture
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture);
// draw texture..
//filterPass.applyFilterPass(filterArea.width, filterArea.height);
this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height);
// swap the textures..
var temp = inputTexture;
inputTexture = outputTexture;
outputTexture = temp;
}
gl.enable(gl.BLEND);
texture = inputTexture;
this.texturePool.push(outputTexture);
}
var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1];
this.offsetX -= filterArea.x;
this.offsetY -= filterArea.y;
var sizeX = this.width;
var sizeY = this.height;
var offsetX = 0;
var offsetY = 0;
var buffer = this.buffer;
// time to render the filters texture to the previous scene
if(this.filterStack.length === 0)
{
gl.colorMask(true, true, true, true);//this.transparent);
}
else
{
var currentFilter = this.filterStack[this.filterStack.length-1];
filterArea = currentFilter._filterArea;
sizeX = filterArea.width;
sizeY = filterArea.height;
offsetX = filterArea.x;
offsetY = filterArea.y;
buffer = currentFilter._glFilterTexture.frameBuffer;
}
// TODO need to remove these global elements..
projection.x = sizeX/2;
projection.y = -sizeY/2;
offset.x = offsetX;
offset.y = offsetY;
filterArea = filterBlock._filterArea;
var x = filterArea.x-offsetX;
var y = filterArea.y-offsetY;
// update the buffers..
// make sure to flip the y!
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
this.vertexArray[0] = x;
this.vertexArray[1] = y + filterArea.height;
this.vertexArray[2] = x + filterArea.width;
this.vertexArray[3] = y + filterArea.height;
this.vertexArray[4] = x;
this.vertexArray[5] = y;
this.vertexArray[6] = x + filterArea.width;
this.vertexArray[7] = y;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray);
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
this.uvArray[2] = filterArea.width/this.width;
this.uvArray[5] = filterArea.height/this.height;
this.uvArray[6] = filterArea.width/this.width;
this.uvArray[7] = filterArea.height/this.height;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray);
gl.viewport(0, 0, sizeX * this.renderSession.resolution, sizeY * this.renderSession.resolution);
// bind the buffer
gl.bindFramebuffer(gl.FRAMEBUFFER, buffer );
// set the blend mode!
//gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
// set texture
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture.texture);
// >>> modify by nextht
if (this.renderSession.stencilManager) {
this.renderSession.stencilManager.destroy();
}
this.renderSession.stencilManager = filterBlock._previous_stencil_mgr;
filterBlock._previous_stencil_mgr = null;
if (this.renderSession.stencilManager.count > 0) {
gl.enable(gl.STENCIL_TEST);
}
else {
gl.disable(gl.STENCIL_TEST);
}
// <<< modify by nextht
// apply!
this.applyFilterPass(filter, filterArea, sizeX, sizeY);
// now restore the regular shader.. should happen automatically now..
// this.renderSession.shaderManager.setShader(this.defaultShader);
// gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2);
// gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY);
// return the texture to the pool
this.texturePool.push(texture);
filterBlock._glFilterTexture = null;
};
/**
* Applies the filter to the specified area.
*
* @method applyFilterPass
* @param filter {Phaser.Filter} the filter that needs to be applied
* @param filterArea {Texture} TODO - might need an update
* @param width {Number} the horizontal range of the filter
* @param height {Number} the vertical range of the filter
*/
PIXI.WebGLFilterManager.prototype.applyFilterPass = function(filter, filterArea, width, height)
{
// use program
var gl = this.gl;
var shader = filter.shaders[gl.id];
if(!shader)
{
shader = new PIXI.PixiShader(gl);
shader.fragmentSrc = filter.fragmentSrc;
shader.uniforms = filter.uniforms;
shader.init(true);
filter.shaders[gl.id] = shader;
}
// set the shader
this.renderSession.shaderManager.setShader(shader);
// gl.useProgram(shader.program);
gl.uniform2f(shader.projectionVector, width/2, -height/2);
gl.uniform2f(shader.offsetVector, 0,0);
if(filter.uniforms.dimensions)
{
filter.uniforms.dimensions.value[0] = this.width;//width;
filter.uniforms.dimensions.value[1] = this.height;//height;
filter.uniforms.dimensions.value[2] = this.vertexArray[0];
filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height;
}
shader.syncUniforms();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
// draw the filter...
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
this.renderSession.drawCount++;
};
/**
* Initialises the shader buffers.
*
* @method initShaderBuffers
*/
PIXI.WebGLFilterManager.prototype.initShaderBuffers = function()
{
var gl = this.gl;
// create some buffers
this.vertexBuffer = gl.createBuffer();
this.uvBuffer = gl.createBuffer();
this.colorBuffer = gl.createBuffer();
this.indexBuffer = gl.createBuffer();
// bind and upload the vertexs..
// keep a reference to the vertexFloatData..
this.vertexArray = new Float32Array([0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW);
// bind and upload the uv buffer
this.uvArray = new Float32Array([0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW);
this.colorArray = new Float32Array([1.0, 0xFFFFFF,
1.0, 0xFFFFFF,
1.0, 0xFFFFFF,
1.0, 0xFFFFFF]);
gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW);
// bind and upload the index
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW);
};
/**
* Destroys the filter and removes it from the filter stack.
*
* @method destroy
*/
PIXI.WebGLFilterManager.prototype.destroy = function()
{
var gl = this.gl;
this.filterStack = null;
this.offsetX = 0;
this.offsetY = 0;
// destroy textures
for (var i = 0; i < this.texturePool.length; i++) {
this.texturePool[i].destroy();
}
this.texturePool = null;
//destroy buffers..
gl.deleteBuffer(this.vertexBuffer);
gl.deleteBuffer(this.uvBuffer);
gl.deleteBuffer(this.colorBuffer);
gl.deleteBuffer(this.indexBuffer);
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
function _CreateEmptyTexture(gl, width, height, scaleMode) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === PIXI.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
return texture;
}
var _fbErrors = {
36054: 'Incomplete attachment',
36055: 'Missing attachment',
36057: 'Incomplete dimensions',
36061: 'Framebuffer unsupported'
};
function _CreateFramebuffer(gl, width, height, scaleMode, textureUnit) {
var framebuffer = gl.createFramebuffer();
var depthStencilBuffer = gl.createRenderbuffer();
var colorBuffer = null;
var fbStatus = 0;
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.renderBuffer);
colorBuffer = _CreateEmptyTexture(gl, width, height, scaleMode);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorBuffer, 0);
fbStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if(fbStatus !== gl.FRAMEBUFFER_COMPLETE) {
console.error('Incomplete GL framebuffer. ', _fbErrors[fbStatus]);
}
framebuffer.width = width;
framebuffer.height = height;
framebuffer.targetTexture = colorBuffer;
framebuffer.renderBuffer = depthStencilBuffer;
return framebuffer;
}
/**
* @class FilterTexture
* @constructor
* @param gl {WebGLContext} the current WebGL drawing context
* @param width {Number} the horizontal range of the filter
* @param height {Number} the vertical range of the filter
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values
*/
PIXI.FilterTexture = function(gl, width, height, scaleMode, textureUnit)
{
textureUnit = typeof textureUnit === 'number' ? textureUnit : 0;
/**
* @property gl
* @type WebGLContext
*/
this.gl = gl;
// next time to create a frame buffer and texture
/**
* @property frameBuffer
* @type Any
*/
this.frameBuffer = _CreateFramebuffer(gl, width, height, scaleMode || PIXI.scaleModes.DEFAULT, textureUnit);
/**
* @property texture
* @type Any
*/
this.texture = this.frameBuffer.targetTexture;
this.width = width;
this.height = height;
this.renderBuffer = this.frameBuffer.renderBuffer;
};
PIXI.FilterTexture.prototype.constructor = PIXI.FilterTexture;
/**
* Clears the filter texture.
*
* @method clear
*/
PIXI.FilterTexture.prototype.clear = function()
{
var gl = this.gl;
gl.clearColor(0,0,0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
};
/**
* Resizes the texture to the specified width and height
*
* @method resize
* @param width {Number} the new width of the texture
* @param height {Number} the new height of the texture
*/
PIXI.FilterTexture.prototype.resize = function(width, height)
{
if(this.width === width && this.height === height) return;
this.width = width;
this.height = height;
var gl = this.gl;
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// update the stencil buffer width and height
gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width , height );
};
/**
* Destroys the filter texture.
*
* @method destroy
*/
PIXI.FilterTexture.prototype.destroy = function()
{
var gl = this.gl;
gl.deleteFramebuffer( this.frameBuffer );
gl.deleteTexture( this.texture );
this.frameBuffer = null;
this.texture = null;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* Creates a Canvas element of the given size.
*
* @class CanvasBuffer
* @constructor
* @param width {Number} the width for the newly created canvas
* @param height {Number} the height for the newly created canvas
*/
PIXI.CanvasBuffer = function(width, height)
{
/**
* The width of the Canvas in pixels.
*
* @property width
* @type Number
*/
this.width = width;
/**
* The height of the Canvas in pixels.
*
* @property height
* @type Number
*/
this.height = height;
/**
* The Canvas object that belongs to this CanvasBuffer.
*
* @property canvas
* @type HTMLCanvasElement
*/
this.canvas = Phaser.CanvasPool.create(this, this.width, this.height);
/**
* A CanvasRenderingContext2D object representing a two-dimensional rendering context.
*
* @property context
* @type CanvasRenderingContext2D
*/
this.context = this.canvas.getContext("2d");
this.canvas.width = width;
this.canvas.height = height;
};
PIXI.CanvasBuffer.prototype.constructor = PIXI.CanvasBuffer;
/**
* Clears the canvas that was created by the CanvasBuffer class.
*
* @method clear
* @private
*/
PIXI.CanvasBuffer.prototype.clear = function()
{
this.context.setTransform(1, 0, 0, 1, 0, 0);
this.context.clearRect(0,0, this.width, this.height);
};
/**
* Resizes the canvas to the specified width and height.
*
* @method resize
* @param width {Number} the new width of the canvas
* @param height {Number} the new height of the canvas
*/
PIXI.CanvasBuffer.prototype.resize = function(width, height)
{
this.width = this.canvas.width = width;
this.height = this.canvas.height = height;
};
/**
* Frees the canvas up for use again.
*
* @method destroy
*/
PIXI.CanvasBuffer.prototype.destroy = function()
{
Phaser.CanvasPool.remove(this);
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* A set of functions used to handle masking.
*
* @class CanvasMaskManager
* @constructor
*/
PIXI.CanvasMaskManager = function()
{
};
PIXI.CanvasMaskManager.prototype.constructor = PIXI.CanvasMaskManager;
/**
* This method adds it to the current stack of masks.
*
* @method pushMask
* @param maskData {Object} the maskData that will be pushed
* @param renderSession {Object} The renderSession whose context will be used for this mask manager.
*/
PIXI.CanvasMaskManager.prototype.pushMask = function(maskData, renderSession) {
var context = renderSession.context;
context.save();
var cacheAlpha = maskData.alpha;
var transform = maskData.worldTransform;
var resolution = renderSession.resolution;
context.setTransform(transform.a * resolution,
transform.b * resolution,
transform.c * resolution,
transform.d * resolution,
transform.tx * resolution,
transform.ty * resolution);
PIXI.CanvasGraphics.renderGraphicsMask(maskData, context);
context.clip();
maskData.worldAlpha = cacheAlpha;
};
/**
* Restores the current drawing context to the state it was before the mask was applied.
*
* @method popMask
* @param renderSession {Object} The renderSession whose context will be used for this mask manager.
*/
PIXI.CanvasMaskManager.prototype.popMask = function(renderSession)
{
renderSession.context.restore();
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* Utility methods for Sprite/Texture tinting.
*
* @class CanvasTinter
* @static
*/
PIXI.CanvasTinter = function() {};
/**
* Basically this method just needs a sprite and a color and tints the sprite with the given color.
*
* @method getTintedTexture
* @static
* @param sprite {Sprite} the sprite to tint
* @param color {Number} the color to use to tint the sprite with
* @return {HTMLCanvasElement} The tinted canvas
*/
PIXI.CanvasTinter.getTintedTexture = function(sprite, color)
{
var canvas = sprite.tintedTexture || Phaser.CanvasPool.create(this);
PIXI.CanvasTinter.tintMethod(sprite.texture, color, canvas);
return canvas;
};
/**
* Tint a texture using the "multiply" operation.
*
* @method tintWithMultiply
* @static
* @param texture {Texture} the texture to tint
* @param color {Number} the color to use to tint the sprite with
* @param canvas {HTMLCanvasElement} the current canvas
*/
PIXI.CanvasTinter.tintWithMultiply = function(texture, color, canvas)
{
var context = canvas.getContext("2d");
var crop = texture.crop;
var w = crop.width;
var h = crop.height;
if (texture.rotated)
{
w = h;
h = crop.width;
}
if (canvas.width !== w || canvas.height !== h)
{
canvas.width = w;
canvas.height = h;
}
context.clearRect(0, 0, w, h);
context.fillStyle = "#" + ("00000" + (color | 0).toString(16)).substr(-6);
context.fillRect(0, 0, w, h);
context.globalCompositeOperation = "multiply";
context.drawImage(texture.baseTexture.source, crop.x, crop.y, w, h, 0, 0, w, h);
context.globalCompositeOperation = "destination-atop";
context.drawImage(texture.baseTexture.source, crop.x, crop.y, w, h, 0, 0, w, h);
};
/**
* Tint a texture pixel per pixel.
*
* @method tintPerPixel
* @static
* @param texture {Texture} the texture to tint
* @param color {Number} the color to use to tint the sprite with
* @param canvas {HTMLCanvasElement} the current canvas
*/
PIXI.CanvasTinter.tintWithPerPixel = function(texture, color, canvas)
{
var context = canvas.getContext("2d");
var crop = texture.crop;
var w = crop.width;
var h = crop.height;
if (texture.rotated)
{
w = h;
h = crop.width;
}
if (canvas.width !== w || canvas.height !== h)
{
canvas.width = w;
canvas.height = h;
}
context.globalCompositeOperation = "copy";
context.drawImage(texture.baseTexture.source, crop.x, crop.y, w, h, 0, 0, w, h);
var rgbValues = Phaser.Color.hexToRGBArray(color);
var r = rgbValues[0], g = rgbValues[1], b = rgbValues[2];
var pixelData = context.getImageData(0, 0, w, h);
var pixels = pixelData.data;
for (var i = 0; i < pixels.length; i += 4)
{
pixels[i + 0] *= r;
pixels[i + 1] *= g;
pixels[i + 2] *= b;
if (!PIXI.CanvasTinter.canHandleAlpha)
{
var alpha = pixels[i + 3];
pixels[i + 0] /= 255 / alpha;
pixels[i + 1] /= 255 / alpha;
pixels[i + 2] /= 255 / alpha;
}
}
context.putImageData(pixelData, 0, 0);
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* The CanvasRenderer draws the Stage and all its content onto a 2d canvas. This renderer should be used for browsers that do not support webGL.
* Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :)
*
* @class CanvasRenderer
* @constructor
* @param game {Phaser.Game} A reference to the Phaser Game instance
*/
PIXI.CanvasRenderer = function (game) {
/**
* @property {Phaser.Game} game - A reference to the Phaser Game instance.
*/
this.game = game;
if (!PIXI.defaultRenderer)
{
PIXI.defaultRenderer = this;
}
/**
* The renderer type.
*
* @property type
* @type Number
*/
this.type = Phaser.CANVAS;
/**
* The resolution of the canvas.
*
* @property resolution
* @type Number
*/
this.resolution = game.resolution;
/**
* This sets if the CanvasRenderer will clear the canvas or not before the new render pass.
* If the Stage is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color.
* If the Stage is transparent Pixi will use clearRect to clear the canvas every frame.
* Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set.
*
* @property clearBeforeRender
* @type Boolean
* @default
*/
this.clearBeforeRender = game.clearBeforeRender;
/**
* Whether the render view is transparent
*
* @property transparent
* @type Boolean
*/
this.transparent = game.transparent;
/**
* Whether the render view should be resized automatically
*
* @property autoResize
* @type Boolean
*/
this.autoResize = false;
/**
* The width of the canvas view
*
* @property width
* @type Number
* @default 800
*/
this.width = game.width * this.resolution;
/**
* The height of the canvas view
*
* @property height
* @type Number
* @default 600
*/
this.height = game.height * this.resolution;
/**
* The canvas element that everything is drawn to.
*
* @property view
* @type HTMLCanvasElement
*/
this.view = game.canvas;
/**
* The canvas 2d context that everything is drawn with
* @property context
* @type CanvasRenderingContext2D
*/
this.context = this.view.getContext("2d", { alpha: this.transparent } );
/**
* Boolean flag controlling canvas refresh.
*
* @property refresh
* @type Boolean
*/
this.refresh = true;
/**
* Internal var.
*
* @property count
* @type Number
*/
this.count = 0;
/**
* Instance of a PIXI.CanvasMaskManager, handles masking when using the canvas renderer
* @property CanvasMaskManager
* @type CanvasMaskManager
*/
this.maskManager = new PIXI.CanvasMaskManager();
/**
* The render session is just a bunch of parameter used for rendering
* @property renderSession
* @type Object
*/
this.renderSession = {
context: this.context,
maskManager: this.maskManager,
scaleMode: null,
smoothProperty: Phaser.Canvas.getSmoothingPrefix(this.context),
/**
* If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation.
* Handy for crisp pixel art and speed on legacy devices.
*/
roundPixels: false
};
this.mapBlendModes();
this.resize(this.width, this.height);
};
// constructor
PIXI.CanvasRenderer.prototype.constructor = PIXI.CanvasRenderer;
/**
* Renders the DisplayObjectContainer, usually the Phaser.Stage, to this canvas view.
*
* @method render
* @param root {Phaser.Stage|PIXI.DisplayObjectContainer} The root element to be rendered.
*/
PIXI.CanvasRenderer.prototype.render = function (root) {
this.context.setTransform(1, 0, 0, 1, 0, 0);
this.context.globalAlpha = 1;
this.renderSession.currentBlendMode = 0;
this.renderSession.shakeX = this.game.camera._shake.x;
this.renderSession.shakeY = this.game.camera._shake.y;
this.context.globalCompositeOperation = 'source-over';
if (navigator.isCocoonJS && this.view.screencanvas)
{
this.context.fillStyle = "black";
this.context.clear();
}
if (this.clearBeforeRender)
{
if (this.transparent)
{
this.context.clearRect(0, 0, this.width, this.height);
}
else if (root._bgColor)
{
this.context.fillStyle = root._bgColor.rgba;
this.context.fillRect(0, 0, this.width , this.height);
}
}
this.renderDisplayObject(root);
};
PIXI.CanvasRenderer.prototype.setTexturePriority = function (textureNameCollection) {
// Does nothing on Canvas, but here to allow you to simply set
// `game.renderer.setTexturePriority()` without having to worry about
// running in WebGL or not.
};
/**
* Removes everything from the renderer and optionally removes the Canvas DOM element.
*
* @method destroy
* @param [removeView=true] {boolean} Removes the Canvas element from the DOM.
*/
PIXI.CanvasRenderer.prototype.destroy = function (removeView) {
if (removeView === undefined) { removeView = true; }
if (removeView && this.view.parent)
{
this.view.parent.removeChild(this.view);
}
this.view = null;
this.context = null;
this.maskManager = null;
this.renderSession = null;
};
/**
* Resizes the canvas view to the specified width and height
*
* @method resize
* @param width {Number} the new width of the canvas view
* @param height {Number} the new height of the canvas view
*/
PIXI.CanvasRenderer.prototype.resize = function (width, height) {
this.width = width * this.resolution;
this.height = height * this.resolution;
this.view.width = this.width;
this.view.height = this.height;
if (this.autoResize)
{
this.view.style.width = this.width / this.resolution + "px";
this.view.style.height = this.height / this.resolution + "px";
}
if (this.renderSession.smoothProperty)
{
this.context[this.renderSession.smoothProperty] = (this.renderSession.scaleMode === PIXI.scaleModes.LINEAR);
}
};
/**
* Renders a display object
*
* @method renderDisplayObject
* @param displayObject {DisplayObject} The displayObject to render
* @param context {CanvasRenderingContext2D} the context 2d method of the canvas
* @param [matrix] {Matrix} Optional matrix to apply to the display object before rendering.
* @private
*/
PIXI.CanvasRenderer.prototype.renderDisplayObject = function (displayObject, context, matrix) {
this.renderSession.context = context || this.context;
this.renderSession.resolution = this.resolution;
displayObject._renderCanvas(this.renderSession, matrix);
};
/**
* Maps Pixi blend modes to canvas blend modes.
*
* @method mapBlendModes
* @private
*/
PIXI.CanvasRenderer.prototype.mapBlendModes = function () {
if (!PIXI.blendModesCanvas)
{
var b = [];
var modes = PIXI.blendModes;
var useNew = this.game.device.canUseMultiply;
b[modes.NORMAL] = 'source-over';
b[modes.ADD] = 'lighter';
b[modes.MULTIPLY] = (useNew) ? 'multiply' : 'source-over';
b[modes.SCREEN] = (useNew) ? 'screen' : 'source-over';
b[modes.OVERLAY] = (useNew) ? 'overlay' : 'source-over';
b[modes.DARKEN] = (useNew) ? 'darken' : 'source-over';
b[modes.LIGHTEN] = (useNew) ? 'lighten' : 'source-over';
b[modes.COLOR_DODGE] = (useNew) ? 'color-dodge' : 'source-over';
b[modes.COLOR_BURN] = (useNew) ? 'color-burn' : 'source-over';
b[modes.HARD_LIGHT] = (useNew) ? 'hard-light' : 'source-over';
b[modes.SOFT_LIGHT] = (useNew) ? 'soft-light' : 'source-over';
b[modes.DIFFERENCE] = (useNew) ? 'difference' : 'source-over';
b[modes.EXCLUSION] = (useNew) ? 'exclusion' : 'source-over';
b[modes.HUE] = (useNew) ? 'hue' : 'source-over';
b[modes.SATURATION] = (useNew) ? 'saturation' : 'source-over';
b[modes.COLOR] = (useNew) ? 'color' : 'source-over';
b[modes.LUMINOSITY] = (useNew) ? 'luminosity' : 'source-over';
PIXI.blendModesCanvas = b;
}
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* A texture stores the information that represents an image. All textures have a base texture.
*
* @class BaseTexture
* @uses EventTarget
* @constructor
* @param source {String|Canvas} the source object (image or canvas)
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values
*/
PIXI.BaseTexture = function(source, scaleMode)
{
/**
* The Resolution of the texture.
*
* @property resolution
* @type Number
*/
this.resolution = 1;
/**
* [read-only] The width of the base texture set when the image has loaded
*
* @property width
* @type Number
* @readOnly
*/
this.width = 100;
/**
* [read-only] The height of the base texture set when the image has loaded
*
* @property height
* @type Number
* @readOnly
*/
this.height = 100;
/**
* The scale mode to apply when scaling this texture
*
* @property scaleMode
* @type {Number}
* @default PIXI.scaleModes.LINEAR
*/
this.scaleMode = scaleMode || PIXI.scaleModes.DEFAULT;
/**
* [read-only] Set to true once the base texture has loaded
*
* @property hasLoaded
* @type Boolean
* @readOnly
*/
this.hasLoaded = false;
/**
* The image source that is used to create the texture.
*
* @property source
* @type Image
*/
this.source = source;
/**
* Controls if RGB channels should be pre-multiplied by Alpha (WebGL only)
*
* @property premultipliedAlpha
* @type Boolean
* @default true
*/
this.premultipliedAlpha = true;
// used for webGL
/**
* @property _glTextures
* @type Array
* @private
*/
this._glTextures = [];
/**
* Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used
* Also the texture must be a power of two size to work
*
* @property mipmap
* @type {Boolean}
*/
this.mipmap = false;
/**
* The multi texture batching index number.
* @property textureIndex
* @type Number
*/
this.textureIndex = 0;
/**
* @property _dirty
* @type Array
* @private
*/
this._dirty = [true, true, true, true];
if (!source)
{
return;
}
if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height)
{
this.hasLoaded = true;
this.width = this.source.naturalWidth || this.source.width;
this.height = this.source.naturalHeight || this.source.height;
this.dirty();
}
/**
* A BaseTexture can be set to skip the rendering phase in the WebGL Sprite Batch.
*
* You may want to do this if you have a parent Sprite with no visible texture (i.e. uses the internal `__default` texture)
* that has children that you do want to render, without causing a batch flush in the process.
*
* @property skipRender
* @type Boolean
*/
this.skipRender = false;
/**
* @property _powerOf2
* @type Boolean
* @private
*/
this._powerOf2 = false;
};
PIXI.BaseTexture.prototype.constructor = PIXI.BaseTexture;
/**
* Forces this BaseTexture to be set as loaded, with the given width and height.
* Then calls BaseTexture.dirty.
* Important for when you don't want to modify the source object by forcing in `complete` or dimension properties it may not have.
*
* @method forceLoaded
* @param {number} width - The new width to force the BaseTexture to be.
* @param {number} height - The new height to force the BaseTexture to be.
*/
PIXI.BaseTexture.prototype.forceLoaded = function(width, height)
{
this.hasLoaded = true;
this.width = width;
this.height = height;
this.dirty();
};
/**
* Destroys this base texture
*
* @method destroy
*/
PIXI.BaseTexture.prototype.destroy = function()
{
if (this.source)
{
Phaser.CanvasPool.removeByCanvas(this.source);
}
this.source = null;
this.unloadFromGPU();
};
/**
* Changes the source image of the texture
*
* @method updateSourceImage
* @param newSrc {String} the path of the image
* @deprecated This method is deprecated. Please use Phaser.Sprite.loadTexture instead.
*/
PIXI.BaseTexture.prototype.updateSourceImage = function(newSrc)
{
console.warn("PIXI.BaseTexture.updateSourceImage is deprecated. Use Phaser.Sprite.loadTexture instead.");
};
/**
* Sets all glTextures to be dirty.
*
* @method dirty
*/
PIXI.BaseTexture.prototype.dirty = function()
{
for (var i = 0; i < this._glTextures.length; i++)
{
this._dirty[i] = true;
}
};
/**
* Removes the base texture from the GPU, useful for managing resources on the GPU.
* Atexture is still 100% usable and will simply be reuploaded if there is a sprite on screen that is using it.
*
* @method unloadFromGPU
*/
PIXI.BaseTexture.prototype.unloadFromGPU = function()
{
this.dirty();
// delete the webGL textures if any.
for (var i = this._glTextures.length - 1; i >= 0; i--)
{
var glTexture = this._glTextures[i];
var gl = PIXI.glContexts[i];
if(gl && glTexture)
{
gl.deleteTexture(glTexture);
}
}
this._glTextures.length = 0;
this.dirty();
};
/**
* Helper function that creates a base texture from the given canvas element.
*
* @static
* @method fromCanvas
* @param canvas {Canvas} The canvas element source of the texture
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values
* @return {BaseTexture}
*/
PIXI.BaseTexture.fromCanvas = function(canvas, scaleMode)
{
if (canvas.width === 0)
{
canvas.width = 1;
}
if (canvas.height === 0)
{
canvas.height = 1;
}
return new PIXI.BaseTexture(canvas, scaleMode);
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* TextureSilentFail is a boolean that defaults to `false`.
* If `true` then `PIXI.Texture.setFrame` will no longer throw an error if the texture dimensions are incorrect.
* Instead `Texture.valid` will be set to `false` (#1556)
*
* @type {boolean}
*/
PIXI.TextureSilentFail = false;
/**
* A texture stores the information that represents an image or part of an image. It cannot be added
* to the display list directly. Instead use it as the texture for a PIXI.Sprite. If no frame is provided then the whole image is used.
*
* @class Texture
* @uses EventTarget
* @constructor
* @param baseTexture {BaseTexture} The base texture source to create the texture from
* @param frame {Rectangle} The rectangle frame of the texture to show
* @param [crop] {Rectangle} The area of original texture
* @param [trim] {Rectangle} Trimmed texture rectangle
*/
PIXI.Texture = function(baseTexture, frame, crop, trim)
{
/**
* Does this Texture have any frame data assigned to it?
*
* @property noFrame
* @type Boolean
*/
this.noFrame = false;
if (!frame)
{
this.noFrame = true;
frame = new PIXI.Rectangle(0,0,1,1);
}
if (baseTexture instanceof PIXI.Texture)
{
baseTexture = baseTexture.baseTexture;
}
/**
* The base texture that this texture uses.
*
* @property baseTexture
* @type BaseTexture
*/
this.baseTexture = baseTexture;
/**
* The frame specifies the region of the base texture that this texture uses
*
* @property frame
* @type Rectangle
*/
this.frame = frame;
/**
* The texture trim data.
*
* @property trim
* @type Rectangle
*/
this.trim = trim;
/**
* This will let the renderer know if the texture is valid. If it's not then it cannot be rendered.
*
* @property valid
* @type Boolean
*/
this.valid = false;
/**
* Is this a tiling texture? As used by the likes of a TilingSprite.
*
* @property isTiling
* @type Boolean
*/
this.isTiling = false;
/**
* This will let a renderer know that a texture has been updated (used mainly for webGL uv updates)
*
* @property requiresUpdate
* @type Boolean
*/
this.requiresUpdate = false;
/**
* This will let a renderer know that a tinted parent has updated its texture.
*
* @property requiresReTint
* @type Boolean
*/
this.requiresReTint = false;
/**
* The WebGL UV data cache.
*
* @property _uvs
* @type Object
* @private
*/
this._uvs = null;
/**
* The width of the Texture in pixels.
*
* @property width
* @type Number
*/
this.width = 0;
/**
* The height of the Texture in pixels.
*
* @property height
* @type Number
*/
this.height = 0;
/**
* This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering,
* irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases)
*
* @property crop
* @type Rectangle
*/
this.crop = crop || new PIXI.Rectangle(0, 0, 1, 1);
/**
* A flag that controls if this frame is rotated or not.
* Rotation allows you to use rotated frames in texture atlas packing, it has nothing to do with
* Sprite rotation.
*
* @property rotated
* @type Boolean
*/
this.rotated = false;
if (baseTexture.hasLoaded)
{
if (this.noFrame) frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height);
this.setFrame(frame);
}
};
PIXI.Texture.prototype.constructor = PIXI.Texture;
/**
* Called when the base texture is loaded
*
* @method onBaseTextureLoaded
* @private
*/
PIXI.Texture.prototype.onBaseTextureLoaded = function()
{
var baseTexture = this.baseTexture;
if (this.noFrame)
{
this.frame = new PIXI.Rectangle(0, 0, baseTexture.width, baseTexture.height);
}
this.setFrame(this.frame);
};
/**
* Destroys this texture
*
* @method destroy
* @param destroyBase {Boolean} Whether to destroy the base texture as well
*/
PIXI.Texture.prototype.destroy = function(destroyBase)
{
if (destroyBase) this.baseTexture.destroy();
this.valid = false;
};
/**
* Specifies the region of the baseTexture that this texture will use.
*
* @method setFrame
* @param frame {Rectangle} The frame of the texture to set it to
*/
PIXI.Texture.prototype.setFrame = function(frame)
{
this.noFrame = false;
this.frame = frame;
this.width = frame.width;
this.height = frame.height;
this.crop.x = frame.x;
this.crop.y = frame.y;
this.crop.width = frame.width;
this.crop.height = frame.height;
if (!this.trim && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height))
{
if (!PIXI.TextureSilentFail)
{
throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this);
}
this.valid = false;
return;
}
this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded;
if (this.trim)
{
this.width = this.trim.width;
this.height = this.trim.height;
this.frame.width = this.trim.width;
this.frame.height = this.trim.height;
}
if (this.valid) this._updateUvs();
};
/**
* Updates the internal WebGL UV cache.
*
* @method _updateUvs
* @private
*/
PIXI.Texture.prototype._updateUvs = function()
{
if(!this._uvs)this._uvs = new PIXI.TextureUvs();
var frame = this.crop;
var tw = this.baseTexture.width;
var th = this.baseTexture.height;
this._uvs.x0 = frame.x / tw;
this._uvs.y0 = frame.y / th;
this._uvs.x1 = (frame.x + frame.width) / tw;
this._uvs.y1 = frame.y / th;
this._uvs.x2 = (frame.x + frame.width) / tw;
this._uvs.y2 = (frame.y + frame.height) / th;
this._uvs.x3 = frame.x / tw;
this._uvs.y3 = (frame.y + frame.height) / th;
};
/**
* Updates the internal WebGL UV cache.
*
* @method _updateUvsInverted
* @private
*/
PIXI.Texture.prototype._updateUvsInverted = function () {
if (!this._uvs) { this._uvs = new PIXI.TextureUvs(); }
var frame = this.crop;
var tw = this.baseTexture.width;
var th = this.baseTexture.height;
this._uvs.x0 = frame.x / tw;
this._uvs.y0 = frame.y / th;
this._uvs.x1 = (frame.x + frame.height) / tw;
this._uvs.y1 = frame.y / th;
this._uvs.x2 = (frame.x + frame.height) / tw;
this._uvs.y2 = (frame.y + frame.width) / th;
this._uvs.x3 = frame.x / tw;
this._uvs.y3 = (frame.y + frame.width) / th;
};
/**
* Helper function that creates a new a Texture based on the given canvas element.
*
* @static
* @method fromCanvas
* @param canvas {Canvas} The canvas element source of the texture
* @param scaleMode {Number} See {{#crossLink "PIXI/scaleModes:property"}}PIXI.scaleModes{{/crossLink}} for possible values
* @return {Texture}
*/
PIXI.Texture.fromCanvas = function(canvas, scaleMode)
{
var baseTexture = PIXI.BaseTexture.fromCanvas(canvas, scaleMode);
return new PIXI.Texture(baseTexture);
};
PIXI.TextureUvs = function()
{
this.x0 = 0;
this.y0 = 0;
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.x3 = 0;
this.y3 = 0;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = PIXI;
}
exports.PIXI = PIXI;
} else if (typeof define !== 'undefined' && define.amd) {
define('PIXI', (function() { return root.PIXI = PIXI; })() );
} else {
root.PIXI = PIXI;
}
return PIXI;
}).call(this);
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
(function(){
var root = this;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* @namespace Phaser
*/
var Phaser = Phaser || { // jshint ignore:line
/**
* The Phaser version number.
* @constant
* @type {string}
*/
VERSION: '2.7.0',
/**
* An array of Phaser game instances.
* @constant
* @type {array}
*/
GAMES: [],
/**
* AUTO renderer - picks between WebGL or Canvas based on device.
* @constant
* @type {integer}
*/
AUTO: 0,
/**
* Canvas Renderer.
* @constant
* @type {integer}
*/
CANVAS: 1,
/**
* WebGL Renderer.
* @constant
* @type {integer}
*/
WEBGL: 2,
/**
* Headless renderer (not visual output)
* @constant
* @type {integer}
*/
HEADLESS: 3,
/**
* WebGL Renderer with MultiTexture support enabled.
* @constant
* @type {integer}
*/
WEBGL_MULTI: 4,
/**
* Direction constant.
* @constant
* @type {integer}
*/
NONE: 0,
/**
* Direction constant.
* @constant
* @type {integer}
*/
LEFT: 1,
/**
* Direction constant.
* @constant
* @type {integer}
*/
RIGHT: 2,
/**
* Direction constant.
* @constant
* @type {integer}
*/
UP: 3,
/**
* Direction constant.
* @constant
* @type {integer}
*/
DOWN: 4,
/**
* Game Object type.
* @constant
* @type {integer}
*/
SPRITE: 0,
/**
* Game Object type.
* @constant
* @type {integer}
*/
BUTTON: 1,
/**
* Game Object type.
* @constant
* @type {integer}
*/
IMAGE: 2,
/**
* Game Object type.
* @constant
* @type {integer}
*/
GRAPHICS: 3,
/**
* Game Object type.
* @constant
* @type {integer}
*/
TEXT: 4,
/**
* Game Object type.
* @constant
* @type {integer}
*/
TILESPRITE: 5,
/**
* Game Object type.
* @constant
* @type {integer}
*/
BITMAPTEXT: 6,
/**
* Game Object type.
* @constant
* @type {integer}
*/
GROUP: 7,
/**
* Game Object type.
* @constant
* @type {integer}
*/
RENDERTEXTURE: 8,
/**
* Game Object type.
* @constant
* @type {integer}
*/
TILEMAP: 9,
/**
* Game Object type.
* @constant
* @type {integer}
*/
TILEMAPLAYER: 10,
/**
* Game Object type.
* @constant
* @type {integer}
*/
EMITTER: 11,
/**
* Game Object type.
* @constant
* @type {integer}
*/
POLYGON: 12,
/**
* Game Object type.
* @constant
* @type {integer}
*/
BITMAPDATA: 13,
/**
* Game Object type.
* @constant
* @type {integer}
*/
CANVAS_FILTER: 14,
/**
* Game Object type.
* @constant
* @type {integer}
*/
WEBGL_FILTER: 15,
/**
* Game Object type.
* @constant
* @type {integer}
*/
ELLIPSE: 16,
/**
* Game Object type.
* @constant
* @type {integer}
*/
SPRITEBATCH: 17,
/**
* Game Object type.
* @constant
* @type {integer}
*/
RETROFONT: 18,
/**
* Game Object type.
* @constant
* @type {integer}
*/
POINTER: 19,
/**
* Game Object type.
* @constant
* @type {integer}
*/
ROPE: 20,
/**
* Game Object type.
* @constant
* @type {integer}
*/
CIRCLE: 21,
/**
* Game Object type.
* @constant
* @type {integer}
*/
RECTANGLE: 22,
/**
* Game Object type.
* @constant
* @type {integer}
*/
LINE: 23,
/**
* Game Object type.
* @constant
* @type {integer}
*/
MATRIX: 24,
/**
* Game Object type.
* @constant
* @type {integer}
*/
POINT: 25,
/**
* Game Object type.
* @constant
* @type {integer}
*/
ROUNDEDRECTANGLE: 26,
/**
* Game Object type.
* @constant
* @type {integer}
*/
CREATURE: 27,
/**
* Game Object type.
* @constant
* @type {integer}
*/
VIDEO: 28,
/**
* Game Object type.
* @constant
* @type {integer}
*/
PENDING_ATLAS: -1,
/**
* A horizontal orientation
* @constant
* @type {integer}
*/
HORIZONTAL: 0,
/**
* A vertical orientation
* @constant
* @type {integer}
*/
VERTICAL: 1,
/**
* A landscape orientation
* @constant
* @type {integer}
*/
LANDSCAPE: 0,
/**
* A portrait orientation
* @constant
* @type {integer}
*/
PORTRAIT: 1,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face up.
* @constant
* @type {integer}
*/
ANGLE_UP: 270,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face down.
* @constant
* @type {integer}
*/
ANGLE_DOWN: 90,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face left.
* @constant
* @type {integer}
*/
ANGLE_LEFT: 180,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face right.
* @constant
* @type {integer}
*/
ANGLE_RIGHT: 0,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face north east.
* @constant
* @type {integer}
*/
ANGLE_NORTH_EAST: 315,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face north west.
* @constant
* @type {integer}
*/
ANGLE_NORTH_WEST: 225,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face south east.
* @constant
* @type {integer}
*/
ANGLE_SOUTH_EAST: 45,
/**
* The Angle (in degrees) a Game Object needs to be set to in order to face south west.
* @constant
* @type {integer}
*/
ANGLE_SOUTH_WEST: 135,
/**
* A constant representing a top-left alignment or position.
* @constant
* @type {integer}
*/
TOP_LEFT: 0,
/**
* A constant representing a top-center alignment or position.
* @constant
* @type {integer}
*/
TOP_CENTER: 1,
/**
* A constant representing a top-right alignment or position.
* @constant
* @type {integer}
*/
TOP_RIGHT: 2,
/**
* A constant representing a left-top alignment or position.
* @constant
* @type {integer}
*/
LEFT_TOP: 3,
/**
* A constant representing a left-center alignment or position.
* @constant
* @type {integer}
*/
LEFT_CENTER: 4,
/**
* A constant representing a left-bottom alignment or position.
* @constant
* @type {integer}
*/
LEFT_BOTTOM: 5,
/**
* A constant representing a center alignment or position.
* @constant
* @type {integer}
*/
CENTER: 6,
/**
* A constant representing a right-top alignment or position.
* @constant
* @type {integer}
*/
RIGHT_TOP: 7,
/**
* A constant representing a right-center alignment or position.
* @constant
* @type {integer}
*/
RIGHT_CENTER: 8,
/**
* A constant representing a right-bottom alignment or position.
* @constant
* @type {integer}
*/
RIGHT_BOTTOM: 9,
/**
* A constant representing a bottom-left alignment or position.
* @constant
* @type {integer}
*/
BOTTOM_LEFT: 10,
/**
* A constant representing a bottom-center alignment or position.
* @constant
* @type {integer}
*/
BOTTOM_CENTER: 11,
/**
* A constant representing a bottom-right alignment or position.
* @constant
* @type {integer}
*/
BOTTOM_RIGHT: 12,
/**
* Various blend modes supported by Pixi.
*
* IMPORTANT: The WebGL renderer only supports the NORMAL, ADD, MULTIPLY and SCREEN blend modes.
*
* @constant
* @property {Number} blendModes.NORMAL
* @property {Number} blendModes.ADD
* @property {Number} blendModes.MULTIPLY
* @property {Number} blendModes.SCREEN
* @property {Number} blendModes.OVERLAY
* @property {Number} blendModes.DARKEN
* @property {Number} blendModes.LIGHTEN
* @property {Number} blendModes.COLOR_DODGE
* @property {Number} blendModes.COLOR_BURN
* @property {Number} blendModes.HARD_LIGHT
* @property {Number} blendModes.SOFT_LIGHT
* @property {Number} blendModes.DIFFERENCE
* @property {Number} blendModes.EXCLUSION
* @property {Number} blendModes.HUE
* @property {Number} blendModes.SATURATION
* @property {Number} blendModes.COLOR
* @property {Number} blendModes.LUMINOSITY
* @static
*/
blendModes: {
NORMAL: 0,
ADD: 1,
MULTIPLY: 2,
SCREEN: 3,
OVERLAY: 4,
DARKEN: 5,
LIGHTEN: 6,
COLOR_DODGE: 7,
COLOR_BURN: 8,
HARD_LIGHT: 9,
SOFT_LIGHT: 10,
DIFFERENCE: 11,
EXCLUSION: 12,
HUE: 13,
SATURATION: 14,
COLOR: 15,
LUMINOSITY: 16
},
/**
* The scale modes that are supported by Pixi.
*
* The DEFAULT scale mode affects the default scaling mode of future operations.
* It can be re-assigned to either LINEAR or NEAREST, depending upon suitability.
*
* @constant
* @property {Object} Phaser.scaleModes
* @property {Number} scaleModes.DEFAULT=LINEAR
* @property {Number} scaleModes.LINEAR Smooth scaling
* @property {Number} scaleModes.NEAREST Pixelating scaling
* @static
*/
scaleModes: {
DEFAULT: 0,
LINEAR: 0,
NEAREST: 1
},
PIXI: PIXI || {},
// Used to create IDs for various Pixi objects.
_UID: 0
};
/**
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
// ES6 Math.trunc - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc
if (!Math.trunc) {
Math.trunc = function trunc(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};
}
/**
* A polyfill for Function.prototype.bind
*/
if (!Function.prototype.bind) {
/* jshint freeze: false */
Function.prototype.bind = (function () {
var slice = Array.prototype.slice;
return function (thisArg) {
var target = this, boundArgs = slice.call(arguments, 1);
if (typeof target !== 'function')
{
throw new TypeError();
}
function bound() {
var args = boundArgs.concat(slice.call(arguments));
target.apply(this instanceof bound ? this : thisArg, args);
}
bound.prototype = (function F(proto) {
if (proto)
{
F.prototype = proto;
}
if (!(this instanceof F))
{
/* jshint supernew: true */
return new F;
}
})(target.prototype);
return bound;
};
})();
}
/**
* A polyfill for Array.isArray
*/
if (!Array.isArray)
{
Array.isArray = function (arg)
{
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
/**
* A polyfill for Array.forEach
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
*/
if (!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun /*, thisArg */)
{
"use strict";
if (this === void 0 || this === null)
{
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function")
{
throw new TypeError();
}
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++)
{
if (i in t)
{
fun.call(thisArg, t[i], i, t);
}
}
};
}
/**
* Low-budget Float32Array knock-off, suitable for use with P2.js in IE9
* Source: http://www.html5gamedevs.com/topic/5988-phaser-12-ie9/
* Cameron Foale (http://www.kibibu.com)
*/
if (typeof window.Uint32Array !== "function" && typeof window.Uint32Array !== "object")
{
var CheapArray = function(type)
{
var proto = new Array(); // jshint ignore:line
window[type] = function(arg) {
if (typeof(arg) === "number")
{
Array.call(this, arg);
this.length = arg;
for (var i = 0; i < this.length; i++)
{
this[i] = 0;
}
}
else
{
Array.call(this, arg.length);
this.length = arg.length;
for (var i = 0; i < this.length; i++)
{
this[i] = arg[i];
}
}
};
window[type].prototype = proto;
window[type].constructor = window[type];
};
CheapArray('Float32Array'); // jshint ignore:line
CheapArray('Uint32Array'); // jshint ignore:line
CheapArray('Uint16Array'); // jshint ignore:line
CheapArray('Int16Array'); // jshint ignore:line
CheapArray('ArrayBuffer'); // jshint ignore:line
}
/**
* Also fix for the absent console in IE9
*/
if (!window.console)
{
window.console = {};
window.console.log = window.console.assert = function(){};
window.console.warn = window.console.assert = function(){};
}
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* @class Phaser.Utils
* @static
*/
Phaser.Utils = {
/**
* Takes the given string and reverses it, returning the reversed string.
* For example if given the string `Atari 520ST` it would return `TS025 iratA`.
*
* @method Phaser.Utils.reverseString
* @param {string} string - The string to be reversed.
* @return {string} The reversed string.
*/
reverseString: function (string) {
return string.split('').reverse().join('');
},
/**
* Gets an objects property by string.
*
* @method Phaser.Utils.getProperty
* @param {object} obj - The object to traverse.
* @param {string} prop - The property whose value will be returned.
* @return {*} the value of the property or null if property isn't found .
*/
getProperty: function(obj, prop) {
var parts = prop.split('.'),
last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while (i < l && (obj = obj[current]))
{
current = parts[i];
i++;
}
if (obj)
{
return obj[last];
}
else
{
return null;
}
},
/**
* Sets an objects property by string.
*
* @method Phaser.Utils.setProperty
* @param {object} obj - The object to traverse
* @param {string} prop - The property whose value will be changed
* @return {object} The object on which the property was set.
*/
setProperty: function(obj, prop, value) {
var parts = prop.split('.'),
last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while (i < l && (obj = obj[current]))
{
current = parts[i];
i++;
}
if (obj)
{
obj[last] = value;
}
return obj;
},
/**
* Generate a random bool result based on the chance value.
*
* Returns true or false based on the chance value (default 50%). For example if you wanted a player to have a 30% chance
* of getting a bonus, call chanceRoll(30) - true means the chance passed, false means it failed.
*
* @method Phaser.Utils#chanceRoll
* @param {number} chance - The chance of receiving the value. A number between 0 and 100 (effectively 0% to 100%).
* @return {boolean} True if the roll passed, or false otherwise.
*/
chanceRoll: function (chance) {
if (chance === undefined) { chance = 50; }
return chance > 0 && (Math.random() * 100 <= chance);
},
/**
* Choose between one of two values randomly.
*
* @method Phaser.Utils#randomChoice
* @param {any} choice1
* @param {any} choice2
* @return {any} The randomly selected choice
*/
randomChoice: function (choice1, choice2) {
return (Math.random() < 0.5) ? choice1 : choice2;
},
/**
* Get a unit dimension from a string.
*
* @method Phaser.Utils.parseDimension
* @param {string|number} size - The size to parse.
* @param {number} dimension - The window dimension to check.
* @return {number} The parsed dimension.
*/
parseDimension: function (size, dimension) {
var f = 0;
var px = 0;
if (typeof size === 'string')
{
// %?
if (size.substr(-1) === '%')
{
f = parseInt(size, 10) / 100;
if (dimension === 0)
{
px = window.innerWidth * f;
}
else
{
px = window.innerHeight * f;
}
}
else
{
px = parseInt(size, 10);
}
}
else
{
px = size;
}
return px;
},
/**
* Takes the given string and pads it out, to the length required, using the character
* specified. For example if you need a string to be 6 characters long, you can call:
*
* `pad('bob', 6, '-', 2)`
*
* This would return: `bob---` as it has padded it out to 6 characters, using the `-` on the right.
*
* You can also use it to pad numbers (they are always returned as strings):
*
* `pad(512, 6, '0', 1)`
*
* Would return: `000512` with the string padded to the left.
*
* If you don't specify a direction it'll pad to both sides:
*
* `pad('c64', 7, '*')`
*
* Would return: `**c64**`
*
* @method Phaser.Utils.pad
* @param {string} str - The target string. `toString()` will be called on the string, which means you can also pass in common data types like numbers.
* @param {integer} [len=0] - The number of characters to be added.
* @param {string} [pad=" "] - The string to pad it out with (defaults to a space).
* @param {integer} [dir=3] - The direction dir = 1 (left), 2 (right), 3 (both).
* @return {string} The padded string.
*/
pad: function (str, len, pad, dir) {
if (len === undefined) { var len = 0; }
if (pad === undefined) { var pad = ' '; }
if (dir === undefined) { var dir = 3; }
str = str.toString();
var padlen = 0;
if (len + 1 >= str.length)
{
switch (dir)
{
case 1:
str = new Array(len + 1 - str.length).join(pad) + str;
break;
case 3:
var right = Math.ceil((padlen = len - str.length) / 2);
var left = padlen - right;
str = new Array(left+1).join(pad) + str + new Array(right+1).join(pad);
break;
default:
str = str + new Array(len + 1 - str.length).join(pad);
break;
}
}
return str;
},
/**
* This is a slightly modified version of jQuery.isPlainObject.
* A plain object is an object whose internal class property is [object Object].
* @method Phaser.Utils.isPlainObject
* @param {object} obj - The object to inspect.
* @return {boolean} - true if the object is plain, otherwise false.
*/
isPlainObject: function (obj) {
// Not plain objects:
// - Any object or value whose internal [[Class]] property is not "[object Object]"
// - DOM nodes
// - window
if (typeof(obj) !== "object" || obj.nodeType || obj === obj.window)
{
return false;
}
// Support: Firefox <20
// The try/catch suppresses exceptions thrown when attempting to access
// the "constructor" property of certain host objects, ie. |window.location|
// https://bugzilla.mozilla.org/show_bug.cgi?id=814622
try {
if (obj.constructor && !({}).hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf"))
{
return false;
}
} catch (e) {
return false;
}
// If the function hasn't returned already, we're confident that
// |obj| is a plain object, created by {} or constructed with new Object
return true;
},
/**
* This is a slightly modified version of http://api.jquery.com/jQuery.extend/
*
* @method Phaser.Utils.extend
* @param {boolean} deep - Perform a deep copy?
* @param {object} target - The target object to copy to.
* @return {object} The extended object.
*/
extend: function () {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if (typeof target === "boolean")
{
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
// extend Phaser if only one argument is passed
if (length === i)
{
target = this;
--i;
}
for (; i < length; i++)
{
// Only deal with non-null/undefined values
if ((options = arguments[i]) != null)
{
// Extend the base object
for (name in options)
{
src = target[name];
copy = options[name];
// Prevent never-ending loop
if (target === copy)
{
continue;
}
// Recurse if we're merging plain objects or arrays
if (deep && copy && (Phaser.Utils.isPlainObject(copy) || (copyIsArray = Array.isArray(copy))))
{
if (copyIsArray)
{
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
}
else
{
clone = src && Phaser.Utils.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[name] = Phaser.Utils.extend(deep, clone, copy);
// Don't bring in undefined values
}
else if (copy !== undefined)
{
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
},
/**
* Mixes in an existing mixin object with the target.
*
* Values in the mixin that have either `get` or `set` functions are created as properties via `defineProperty`
* _except_ if they also define a `clone` method - if a clone method is defined that is called instead and
* the result is assigned directly.
*
* @method Phaser.Utils.mixinPrototype
* @param {object} target - The target object to receive the new functions.
* @param {object} mixin - The object to copy the functions from.
* @param {boolean} [replace=false] - If the target object already has a matching function should it be overwritten or not?
*/
mixinPrototype: function (target, mixin, replace) {
if (replace === undefined) { replace = false; }
var mixinKeys = Object.keys(mixin);
for (var i = 0; i < mixinKeys.length; i++)
{
var key = mixinKeys[i];
var value = mixin[key];
if (!replace && (key in target))
{
// Not overwriting existing property
continue;
}
else
{
if (value &&
(typeof value.get === 'function' || typeof value.set === 'function'))
{
// Special case for classes like Phaser.Point which has a 'set' function!
if (typeof value.clone === 'function')
{
target[key] = value.clone();
}
else
{
Object.defineProperty(target, key, value);
}
}
else
{
target[key] = value;
}
}
}
},
/**
* Mixes the source object into the destination object, returning the newly modified destination object.
* Based on original code by @mudcube
*
* @method Phaser.Utils.mixin
* @param {object} from - The object to copy (the source object).
* @param {object} to - The object to copy to (the destination object).
* @return {object} The modified destination object.
*/
mixin: function (from, to) {
if (!from || typeof (from) !== "object")
{
return to;
}
for (var key in from)
{
var o = from[key];
if (o.childNodes || o.cloneNode)
{
continue;
}
var type = typeof (from[key]);
if (!from[key] || type !== "object")
{
to[key] = from[key];
}
else
{
// Clone sub-object
if (typeof (to[key]) === type)
{
to[key] = Phaser.Utils.mixin(from[key], to[key]);
}
else
{
to[key] = Phaser.Utils.mixin(from[key], new o.constructor());
}
}
}
return to;
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a new Circle object with the center coordinate specified by the x and y parameters and the diameter specified by the diameter parameter.
* If you call this function without parameters, a circle with x, y, diameter and radius properties set to 0 is created.
*
* @class Phaser.Circle
* @constructor
* @param {number} [x=0] - The x coordinate of the center of the circle.
* @param {number} [y=0] - The y coordinate of the center of the circle.
* @param {number} [diameter=0] - The diameter of the circle.
*/
Phaser.Circle = function (x, y, diameter) {
x = x || 0;
y = y || 0;
diameter = diameter || 0;
/**
* @property {number} x - The x coordinate of the center of the circle.
*/
this.x = x;
/**
* @property {number} y - The y coordinate of the center of the circle.
*/
this.y = y;
/**
* @property {number} _diameter - The diameter of the circle.
* @private
*/
this._diameter = diameter;
/**
* @property {number} _radius - The radius of the circle.
* @private
*/
this._radius = 0;
if (diameter > 0)
{
this._radius = diameter * 0.5;
}
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.CIRCLE;
};
Phaser.Circle.prototype = {
/**
* The circumference of the circle.
*
* @method Phaser.Circle#circumference
* @return {number} The circumference of the circle.
*/
circumference: function () {
return 2 * (Math.PI * this._radius);
},
/**
* Returns a uniformly distributed random point from anywhere within this Circle.
*
* @method Phaser.Circle#random
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in.
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object.
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties.
*/
random: function (out) {
if (out === undefined) { out = new Phaser.Point(); }
var t = 2 * Math.PI * Math.random();
var u = Math.random() + Math.random();
var r = (u > 1) ? 2 - u : u;
var x = r * Math.cos(t);
var y = r * Math.sin(t);
out.x = this.x + (x * this.radius);
out.y = this.y + (y * this.radius);
return out;
},
/**
* Returns the framing rectangle of the circle as a Phaser.Rectangle object.
*
* @method Phaser.Circle#getBounds
* @return {Phaser.Rectangle} The bounds of the Circle.
*/
getBounds: function () {
return new Phaser.Rectangle(this.x - this.radius, this.y - this.radius, this.diameter, this.diameter);
},
/**
* Sets the members of Circle to the specified values.
* @method Phaser.Circle#setTo
* @param {number} x - The x coordinate of the center of the circle.
* @param {number} y - The y coordinate of the center of the circle.
* @param {number} diameter - The diameter of the circle.
* @return {Circle} This circle object.
*/
setTo: function (x, y, diameter) {
this.x = x;
this.y = y;
this._diameter = diameter;
this._radius = diameter * 0.5;
return this;
},
/**
* Copies the x, y and diameter properties from any given object to this Circle.
* @method Phaser.Circle#copyFrom
* @param {any} source - The object to copy from.
* @return {Circle} This Circle object.
*/
copyFrom: function (source) {
return this.setTo(source.x, source.y, source.diameter);
},
/**
* Copies the x, y and diameter properties from this Circle to any given object.
* @method Phaser.Circle#copyTo
* @param {any} dest - The object to copy to.
* @return {object} This dest object.
*/
copyTo: function (dest) {
dest.x = this.x;
dest.y = this.y;
dest.diameter = this._diameter;
return dest;
},
/**
* Returns the distance from the center of the Circle object to the given object
* (can be Circle, Point or anything with x/y properties)
* @method Phaser.Circle#distance
* @param {object} dest - The target object. Must have visible x and y properties that represent the center of the object.
* @param {boolean} [round=false] - Round the distance to the nearest integer.
* @return {number} The distance between this Point object and the destination Point object.
*/
distance: function (dest, round) {
var distance = Phaser.Math.distance(this.x, this.y, dest.x, dest.y);
return round ? Math.round(distance) : distance;
},
/**
* Returns a new Circle object with the same values for the x, y, width, and height properties as this Circle object.
* @method Phaser.Circle#clone
* @param {Phaser.Circle} output - Optional Circle object. If given the values will be set into the object, otherwise a brand new Circle object will be created and returned.
* @return {Phaser.Circle} The cloned Circle object.
*/
clone: function (output) {
if (output === undefined || output === null)
{
output = new Phaser.Circle(this.x, this.y, this.diameter);
}
else
{
output.setTo(this.x, this.y, this.diameter);
}
return output;
},
/**
* Return true if the given x/y coordinates are within this Circle object.
* @method Phaser.Circle#contains
* @param {number} x - The X value of the coordinate to test.
* @param {number} y - The Y value of the coordinate to test.
* @return {boolean} True if the coordinates are within this circle, otherwise false.
*/
contains: function (x, y) {
return Phaser.Circle.contains(this, x, y);
},
/**
* Returns a Point object containing the coordinates of a point on the circumference of the Circle based on the given angle.
* @method Phaser.Circle#circumferencePoint
* @param {number} angle - The angle in radians (unless asDegrees is true) to return the point from.
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)?
* @param {Phaser.Point} [out] - An optional Point object to put the result in to. If none specified a new Point object will be created.
* @return {Phaser.Point} The Point object holding the result.
*/
circumferencePoint: function (angle, asDegrees, out) {
return Phaser.Circle.circumferencePoint(this, angle, asDegrees, out);
},
/**
* Adjusts the location of the Circle object, as determined by its center coordinate, by the specified amounts.
* @method Phaser.Circle#offset
* @param {number} dx - Moves the x value of the Circle object by this amount.
* @param {number} dy - Moves the y value of the Circle object by this amount.
* @return {Circle} This Circle object.
*/
offset: function (dx, dy) {
this.x += dx;
this.y += dy;
return this;
},
/**
* Adjusts the location of the Circle object using a Point object as a parameter. This method is similar to the Circle.offset() method, except that it takes a Point object as a parameter.
* @method Phaser.Circle#offsetPoint
* @param {Point} point A Point object to use to offset this Circle object (or any valid object with exposed x and y properties).
* @return {Circle} This Circle object.
*/
offsetPoint: function (point) {
return this.offset(point.x, point.y);
},
/**
* Returns a string representation of this object.
* @method Phaser.Circle#toString
* @return {string} a string representation of the instance.
*/
toString: function () {
return "[{Phaser.Circle (x=" + this.x + " y=" + this.y + " diameter=" + this.diameter + " radius=" + this.radius + ")}]";
}
};
Phaser.Circle.prototype.constructor = Phaser.Circle;
/**
* The largest distance between any two points on the circle. The same as the radius * 2.
*
* @name Phaser.Circle#diameter
* @property {number} diameter - Gets or sets the diameter of the circle.
*/
Object.defineProperty(Phaser.Circle.prototype, "diameter", {
get: function () {
return this._diameter;
},
set: function (value) {
if (value > 0)
{
this._diameter = value;
this._radius = value * 0.5;
}
}
});
/**
* The length of a line extending from the center of the circle to any point on the circle itself. The same as half the diameter.
* @name Phaser.Circle#radius
* @property {number} radius - Gets or sets the radius of the circle.
*/
Object.defineProperty(Phaser.Circle.prototype, "radius", {
get: function () {
return this._radius;
},
set: function (value) {
if (value > 0)
{
this._radius = value;
this._diameter = value * 2;
}
}
});
/**
* The x coordinate of the leftmost point of the circle. Changing the left property of a Circle object has no effect on the x and y properties. However it does affect the diameter, whereas changing the x value does not affect the diameter property.
* @name Phaser.Circle#left
* @propety {number} left - Gets or sets the value of the leftmost point of the circle.
*/
Object.defineProperty(Phaser.Circle.prototype, "left", {
get: function () {
return this.x - this._radius;
},
set: function (value) {
if (value > this.x)
{
this._radius = 0;
this._diameter = 0;
}
else
{
this.radius = this.x - value;
}
}
});
/**
* The x coordinate of the rightmost point of the circle. Changing the right property of a Circle object has no effect on the x and y properties. However it does affect the diameter, whereas changing the x value does not affect the diameter property.
* @name Phaser.Circle#right
* @property {number} right - Gets or sets the value of the rightmost point of the circle.
*/
Object.defineProperty(Phaser.Circle.prototype, "right", {
get: function () {
return this.x + this._radius;
},
set: function (value) {
if (value < this.x)
{
this._radius = 0;
this._diameter = 0;
}
else
{
this.radius = value - this.x;
}
}
});
/**
* The sum of the y minus the radius property. Changing the top property of a Circle object has no effect on the x and y properties, but does change the diameter.
* @name Phaser.Circle#top
* @property {number} top - Gets or sets the top of the circle.
*/
Object.defineProperty(Phaser.Circle.prototype, "top", {
get: function () {
return this.y - this._radius;
},
set: function (value) {
if (value > this.y)
{
this._radius = 0;
this._diameter = 0;
}
else
{
this.radius = this.y - value;
}
}
});
/**
* The sum of the y and radius properties. Changing the bottom property of a Circle object has no effect on the x and y properties, but does change the diameter.
* @name Phaser.Circle#bottom
* @property {number} bottom - Gets or sets the bottom of the circle.
*/
Object.defineProperty(Phaser.Circle.prototype, "bottom", {
get: function () {
return this.y + this._radius;
},
set: function (value) {
if (value < this.y)
{
this._radius = 0;
this._diameter = 0;
}
else
{
this.radius = value - this.y;
}
}
});
/**
* The area of this Circle.
* @name Phaser.Circle#area
* @property {number} area - The area of this circle.
* @readonly
*/
Object.defineProperty(Phaser.Circle.prototype, "area", {
get: function () {
if (this._radius > 0)
{
return Math.PI * this._radius * this._radius;
}
else
{
return 0;
}
}
});
/**
* Determines whether or not this Circle object is empty. Will return a value of true if the Circle objects diameter is less than or equal to 0; otherwise false.
* If set to true it will reset all of the Circle objects properties to 0. A Circle object is empty if its diameter is less than or equal to 0.
* @name Phaser.Circle#empty
* @property {boolean} empty - Gets or sets the empty state of the circle.
*/
Object.defineProperty(Phaser.Circle.prototype, "empty", {
get: function () {
return (this._diameter === 0);
},
set: function (value) {
if (value === true)
{
this.setTo(0, 0, 0);
}
}
});
/**
* Return true if the given x/y coordinates are within the Circle object.
* @method Phaser.Circle.contains
* @param {Phaser.Circle} a - The Circle to be checked.
* @param {number} x - The X value of the coordinate to test.
* @param {number} y - The Y value of the coordinate to test.
* @return {boolean} True if the coordinates are within this circle, otherwise false.
*/
Phaser.Circle.contains = function (a, x, y) {
// Check if x/y are within the bounds first
if (a.radius > 0 && x >= a.left && x <= a.right && y >= a.top && y <= a.bottom)
{
var dx = (a.x - x) * (a.x - x);
var dy = (a.y - y) * (a.y - y);
return (dx + dy) <= (a.radius * a.radius);
}
else
{
return false;
}
};
/**
* Determines whether the two Circle objects match. This method compares the x, y and diameter properties.
* @method Phaser.Circle.equals
* @param {Phaser.Circle} a - The first Circle object.
* @param {Phaser.Circle} b - The second Circle object.
* @return {boolean} A value of true if the object has exactly the same values for the x, y and diameter properties as this Circle object; otherwise false.
*/
Phaser.Circle.equals = function (a, b) {
return (a.x === b.x && a.y === b.y && a.diameter === b.diameter);
};
/**
* Determines whether the two Circle objects intersect.
* This method checks the radius distances between the two Circle objects to see if they intersect.
* @method Phaser.Circle.intersects
* @param {Phaser.Circle} a - The first Circle object.
* @param {Phaser.Circle} b - The second Circle object.
* @return {boolean} A value of true if the specified object intersects with this Circle object; otherwise false.
*/
Phaser.Circle.intersects = function (a, b) {
return (Phaser.Math.distance(a.x, a.y, b.x, b.y) <= (a.radius + b.radius));
};
/**
* Returns a Point object containing the coordinates of a point on the circumference of the Circle based on the given angle.
* @method Phaser.Circle.circumferencePoint
* @param {Phaser.Circle} a - The first Circle object.
* @param {number} angle - The angle in radians (unless asDegrees is true) to return the point from.
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)?
* @param {Phaser.Point} [out] - An optional Point object to put the result in to. If none specified a new Point object will be created.
* @return {Phaser.Point} The Point object holding the result.
*/
Phaser.Circle.circumferencePoint = function (a, angle, asDegrees, out) {
if (asDegrees === undefined) { asDegrees = false; }
if (out === undefined) { out = new Phaser.Point(); }
if (asDegrees === true)
{
angle = Phaser.Math.degToRad(angle);
}
out.x = a.x + a.radius * Math.cos(angle);
out.y = a.y + a.radius * Math.sin(angle);
return out;
};
/**
* Checks if the given Circle and Rectangle objects intersect.
* @method Phaser.Circle.intersectsRectangle
* @param {Phaser.Circle} c - The Circle object to test.
* @param {Phaser.Rectangle} r - The Rectangle object to test.
* @return {boolean} True if the two objects intersect, otherwise false.
*/
Phaser.Circle.intersectsRectangle = function (c, r) {
var cx = Math.abs(c.x - r.x - r.halfWidth);
var xDist = r.halfWidth + c.radius;
if (cx > xDist)
{
return false;
}
var cy = Math.abs(c.y - r.y - r.halfHeight);
var yDist = r.halfHeight + c.radius;
if (cy > yDist)
{
return false;
}
if (cx <= r.halfWidth || cy <= r.halfHeight)
{
return true;
}
var xCornerDist = cx - r.halfWidth;
var yCornerDist = cy - r.halfHeight;
var xCornerDistSq = xCornerDist * xCornerDist;
var yCornerDistSq = yCornerDist * yCornerDist;
var maxCornerDistSq = c.radius * c.radius;
return xCornerDistSq + yCornerDistSq <= maxCornerDistSq;
};
// Because PIXI uses its own Circle, we'll replace it with ours to avoid duplicating code or confusion.
PIXI.Circle = Phaser.Circle;
/**
* @author Richard Davey <rich@photonstorm.com>
* @author Chad Engler <chad@pantherdev.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a Ellipse object. A curve on a plane surrounding two focal points.
*
* @class Phaser.Ellipse
* @constructor
* @param {number} [x=0] - The X coordinate of the upper-left corner of the framing rectangle of this ellipse.
* @param {number} [y=0] - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse.
* @param {number} [width=0] - The overall width of this ellipse.
* @param {number} [height=0] - The overall height of this ellipse.
*/
Phaser.Ellipse = function (x, y, width, height) {
x = x || 0;
y = y || 0;
width = width || 0;
height = height || 0;
/**
* @property {number} x - The X coordinate of the upper-left corner of the framing rectangle of this ellipse.
*/
this.x = x;
/**
* @property {number} y - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse.
*/
this.y = y;
/**
* @property {number} width - The overall width of this ellipse.
*/
this.width = width;
/**
* @property {number} height - The overall height of this ellipse.
*/
this.height = height;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.ELLIPSE;
};
Phaser.Ellipse.prototype = {
/**
* Sets the members of the Ellipse to the specified values.
* @method Phaser.Ellipse#setTo
* @param {number} x - The X coordinate of the upper-left corner of the framing rectangle of this ellipse.
* @param {number} y - The Y coordinate of the upper-left corner of the framing rectangle of this ellipse.
* @param {number} width - The overall width of this ellipse.
* @param {number} height - The overall height of this ellipse.
* @return {Phaser.Ellipse} This Ellipse object.
*/
setTo: function (x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
return this;
},
/**
* Returns the framing rectangle of the ellipse as a Phaser.Rectangle object.
*
* @method Phaser.Ellipse#getBounds
* @return {Phaser.Rectangle} The bounds of the Ellipse.
*/
getBounds: function () {
return new Phaser.Rectangle(this.x - this.width, this.y - this.height, this.width, this.height);
},
/**
* Copies the x, y, width and height properties from any given object to this Ellipse.
*
* @method Phaser.Ellipse#copyFrom
* @param {any} source - The object to copy from.
* @return {Phaser.Ellipse} This Ellipse object.
*/
copyFrom: function (source) {
return this.setTo(source.x, source.y, source.width, source.height);
},
/**
* Copies the x, y, width and height properties from this Ellipse to any given object.
* @method Phaser.Ellipse#copyTo
* @param {any} dest - The object to copy to.
* @return {object} This dest object.
*/
copyTo: function(dest) {
dest.x = this.x;
dest.y = this.y;
dest.width = this.width;
dest.height = this.height;
return dest;
},
/**
* Returns a new Ellipse object with the same values for the x, y, width, and height properties as this Ellipse object.
* @method Phaser.Ellipse#clone
* @param {Phaser.Ellipse} output - Optional Ellipse object. If given the values will be set into the object, otherwise a brand new Ellipse object will be created and returned.
* @return {Phaser.Ellipse} The cloned Ellipse object.
*/
clone: function(output) {
if (output === undefined || output === null)
{
output = new Phaser.Ellipse(this.x, this.y, this.width, this.height);
}
else
{
output.setTo(this.x, this.y, this.width, this.height);
}
return output;
},
/**
* Return true if the given x/y coordinates are within this Ellipse object.
*
* @method Phaser.Ellipse#contains
* @param {number} x - The X value of the coordinate to test.
* @param {number} y - The Y value of the coordinate to test.
* @return {boolean} True if the coordinates are within this ellipse, otherwise false.
*/
contains: function (x, y) {
return Phaser.Ellipse.contains(this, x, y);
},
/**
* Returns a uniformly distributed random point from anywhere within this Ellipse.
*
* @method Phaser.Ellipse#random
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in.
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object.
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties.
*/
random: function (out) {
if (out === undefined) { out = new Phaser.Point(); }
var p = Math.random() * Math.PI * 2;
var r = Math.random();
out.x = Math.sqrt(r) * Math.cos(p);
out.y = Math.sqrt(r) * Math.sin(p);
out.x = this.x + (out.x * this.width / 2.0);
out.y = this.y + (out.y * this.height / 2.0);
return out;
},
/**
* Returns a string representation of this object.
* @method Phaser.Ellipse#toString
* @return {string} A string representation of the instance.
*/
toString: function () {
return "[{Phaser.Ellipse (x=" + this.x + " y=" + this.y + " width=" + this.width + " height=" + this.height + ")}]";
}
};
Phaser.Ellipse.prototype.constructor = Phaser.Ellipse;
/**
* The left coordinate of the Ellipse. The same as the X coordinate.
* @name Phaser.Ellipse#left
* @propety {number} left - Gets or sets the value of the leftmost point of the ellipse.
*/
Object.defineProperty(Phaser.Ellipse.prototype, "left", {
get: function () {
return this.x;
},
set: function (value) {
this.x = value;
}
});
/**
* The x coordinate of the rightmost point of the Ellipse. Changing the right property of an Ellipse object has no effect on the x property, but does adjust the width.
* @name Phaser.Ellipse#right
* @property {number} right - Gets or sets the value of the rightmost point of the ellipse.
*/
Object.defineProperty(Phaser.Ellipse.prototype, "right", {
get: function () {
return this.x + this.width;
},
set: function (value) {
if (value < this.x)
{
this.width = 0;
}
else
{
this.width = value - this.x;
}
}
});
/**
* The top of the Ellipse. The same as its y property.
* @name Phaser.Ellipse#top
* @property {number} top - Gets or sets the top of the ellipse.
*/
Object.defineProperty(Phaser.Ellipse.prototype, "top", {
get: function () {
return this.y;
},
set: function (value) {
this.y = value;
}
});
/**
* The sum of the y and height properties. Changing the bottom property of an Ellipse doesn't adjust the y property, but does change the height.
* @name Phaser.Ellipse#bottom
* @property {number} bottom - Gets or sets the bottom of the ellipse.
*/
Object.defineProperty(Phaser.Ellipse.prototype, "bottom", {
get: function () {
return this.y + this.height;
},
set: function (value) {
if (value < this.y)
{
this.height = 0;
}
else
{
this.height = value - this.y;
}
}
});
/**
* Determines whether or not this Ellipse object is empty. Will return a value of true if the Ellipse objects dimensions are less than or equal to 0; otherwise false.
* If set to true it will reset all of the Ellipse objects properties to 0. An Ellipse object is empty if its width or height is less than or equal to 0.
* @name Phaser.Ellipse#empty
* @property {boolean} empty - Gets or sets the empty state of the ellipse.
*/
Object.defineProperty(Phaser.Ellipse.prototype, "empty", {
get: function () {
return (this.width === 0 || this.height === 0);
},
set: function (value) {
if (value === true)
{
this.setTo(0, 0, 0, 0);
}
}
});
/**
* Return true if the given x/y coordinates are within the Ellipse object.
*
* @method Phaser.Ellipse.contains
* @param {Phaser.Ellipse} a - The Ellipse to be checked.
* @param {number} x - The X value of the coordinate to test.
* @param {number} y - The Y value of the coordinate to test.
* @return {boolean} True if the coordinates are within this ellipse, otherwise false.
*/
Phaser.Ellipse.contains = function (a, x, y) {
if (a.width <= 0 || a.height <= 0) {
return false;
}
// Normalize the coords to an ellipse with center 0,0 and a radius of 0.5
var normx = ((x - a.x) / a.width) - 0.5;
var normy = ((y - a.y) / a.height) - 0.5;
normx *= normx;
normy *= normy;
return (normx + normy < 0.25);
};
// Because PIXI uses its own Ellipse, we'll replace it with ours to avoid duplicating code or confusion.
PIXI.Ellipse = Phaser.Ellipse;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a new Line object with a start and an end point.
*
* @class Phaser.Line
* @constructor
* @param {number} [x1=0] - The x coordinate of the start of the line.
* @param {number} [y1=0] - The y coordinate of the start of the line.
* @param {number} [x2=0] - The x coordinate of the end of the line.
* @param {number} [y2=0] - The y coordinate of the end of the line.
*/
Phaser.Line = function (x1, y1, x2, y2) {
x1 = x1 || 0;
y1 = y1 || 0;
x2 = x2 || 0;
y2 = y2 || 0;
/**
* @property {Phaser.Point} start - The start point of the line.
*/
this.start = new Phaser.Point(x1, y1);
/**
* @property {Phaser.Point} end - The end point of the line.
*/
this.end = new Phaser.Point(x2, y2);
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.LINE;
};
Phaser.Line.prototype = {
/**
* Sets the components of the Line to the specified values.
*
* @method Phaser.Line#setTo
* @param {number} [x1=0] - The x coordinate of the start of the line.
* @param {number} [y1=0] - The y coordinate of the start of the line.
* @param {number} [x2=0] - The x coordinate of the end of the line.
* @param {number} [y2=0] - The y coordinate of the end of the line.
* @return {Phaser.Line} This line object
*/
setTo: function (x1, y1, x2, y2) {
this.start.setTo(x1, y1);
this.end.setTo(x2, y2);
return this;
},
/**
* Sets the line to match the x/y coordinates of the two given sprites.
* Can optionally be calculated from their center coordinates.
*
* @method Phaser.Line#fromSprite
* @param {Phaser.Sprite} startSprite - The coordinates of this Sprite will be set to the Line.start point.
* @param {Phaser.Sprite} endSprite - The coordinates of this Sprite will be set to the Line.start point.
* @param {boolean} [useCenter=false] - If true it will use startSprite.centerX, if false startSprite.x.
* @return {Phaser.Line} This line object
*/
fromSprite: function (startSprite, endSprite, useCenter) {
if (useCenter === undefined) { useCenter = false; }
if (useCenter)
{
return this.setTo(startSprite.centerX, startSprite.centerY, endSprite.centerX, endSprite.centerY);
}
return this.setTo(startSprite.x, startSprite.y, endSprite.x, endSprite.y);
},
/**
* Sets this line to start at the given `x` and `y` coordinates and for the segment to extend at `angle` for the given `length`.
*
* @method Phaser.Line#fromAngle
* @param {number} x - The x coordinate of the start of the line.
* @param {number} y - The y coordinate of the start of the line.
* @param {number} angle - The angle of the line in radians.
* @param {number} length - The length of the line in pixels.
* @return {Phaser.Line} This line object
*/
fromAngle: function (x, y, angle, length) {
this.start.setTo(x, y);
this.end.setTo(x + (Math.cos(angle) * length), y + (Math.sin(angle) * length));
return this;
},
/**
* Rotates the line by the amount specified in `angle`.
*
* Rotation takes place from the center of the line.
* If you wish to rotate around a different point see Line.rotateAround.
*
* If you wish to rotate the ends of the Line then see Line.start.rotate or Line.end.rotate.
*
* @method Phaser.Line#rotate
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the line by.
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)?
* @return {Phaser.Line} This line object
*/
rotate: function (angle, asDegrees) {
var cx = (this.start.x + this.end.x) / 2;
var cy = (this.start.y + this.end.y) / 2;
this.start.rotate(cx, cy, angle, asDegrees);
this.end.rotate(cx, cy, angle, asDegrees);
return this;
},
/**
* Rotates the line by the amount specified in `angle`.
*
* Rotation takes place around the coordinates given.
*
* @method Phaser.Line#rotateAround
* @param {number} x - The x coordinate to offset the rotation from.
* @param {number} y - The y coordinate to offset the rotation from.
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the line by.
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)?
* @return {Phaser.Line} This line object
*/
rotateAround: function (x, y, angle, asDegrees) {
this.start.rotate(x, y, angle, asDegrees);
this.end.rotate(x, y, angle, asDegrees);
return this;
},
/**
* Checks for intersection between this line and another Line.
* If asSegment is true it will check for segment intersection. If asSegment is false it will check for line intersection.
* Returns the intersection segment of AB and EF as a Point, or null if there is no intersection.
*
* @method Phaser.Line#intersects
* @param {Phaser.Line} line - The line to check against this one.
* @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection.
* @param {Phaser.Point} [result] - A Point object to store the result in, if not given a new one will be created.
* @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection.
*/
intersects: function (line, asSegment, result) {
return Phaser.Line.intersectsPoints(this.start, this.end, line.start, line.end, asSegment, result);
},
/**
* Returns the reflected angle between two lines.
* This is the outgoing angle based on the angle of this line and the normalAngle of the given line.
*
* @method Phaser.Line#reflect
* @param {Phaser.Line} line - The line to reflect off this line.
* @return {number} The reflected angle in radians.
*/
reflect: function (line) {
return Phaser.Line.reflect(this, line);
},
/**
* Returns a Point object where the x and y values correspond to the center (or midpoint) of the Line segment.
*
* @method Phaser.Line#midPoint
* @param {Phaser.Point} [out] - A Phaser.Point object into which the result will be populated. If not given a new Point object is created.
* @return {Phaser.Point} A Phaser.Point object with the x and y values set to the center of the line segment.
*/
midPoint: function (out) {
if (out === undefined) { out = new Phaser.Point(); }
out.x = (this.start.x + this.end.x) / 2;
out.y = (this.start.y + this.end.y) / 2;
return out;
},
/**
* Centers this Line on the given coordinates.
*
* The line is centered by positioning the start and end points so that the lines midpoint matches
* the coordinates given.
*
* @method Phaser.Line#centerOn
* @param {number} x - The x position to center the line on.
* @param {number} y - The y position to center the line on.
* @return {Phaser.Line} This line object
*/
centerOn: function (x, y) {
var cx = (this.start.x + this.end.x) / 2;
var cy = (this.start.y + this.end.y) / 2;
var tx = x - cx;
var ty = y - cy;
this.start.add(tx, ty);
this.end.add(tx, ty);
},
/**
* Tests if the given coordinates fall on this line. See pointOnSegment to test against just the line segment.
*
* @method Phaser.Line#pointOnLine
* @param {number} x - The line to check against this one.
* @param {number} y - The line to check against this one.
* @return {boolean} True if the point is on the line, false if not.
*/
pointOnLine: function (x, y) {
return ((x - this.start.x) * (this.end.y - this.start.y) === (this.end.x - this.start.x) * (y - this.start.y));
},
/**
* Tests if the given coordinates fall on this line and within the segment. See pointOnLine to test against just the line.
*
* @method Phaser.Line#pointOnSegment
* @param {number} x - The line to check against this one.
* @param {number} y - The line to check against this one.
* @return {boolean} True if the point is on the line and segment, false if not.
*/
pointOnSegment: function (x, y) {
var xMin = Math.min(this.start.x, this.end.x);
var xMax = Math.max(this.start.x, this.end.x);
var yMin = Math.min(this.start.y, this.end.y);
var yMax = Math.max(this.start.y, this.end.y);
return (this.pointOnLine(x, y) && (x >= xMin && x <= xMax) && (y >= yMin && y <= yMax));
},
/**
* Picks a random point from anywhere on the Line segment and returns it.
*
* @method Phaser.Line#random
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in.
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an object.
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties.
*/
random: function (out) {
if (out === undefined) { out = new Phaser.Point(); }
var t = Math.random();
out.x = this.start.x + t * (this.end.x - this.start.x);
out.y = this.start.y + t * (this.end.y - this.start.y);
return out;
},
/**
* Using Bresenham's line algorithm this will return an array of all coordinates on this line.
* The start and end points are rounded before this runs as the algorithm works on integers.
*
* @method Phaser.Line#coordinatesOnLine
* @param {number} [stepRate=1] - How many steps will we return? 1 = every coordinate on the line, 2 = every other coordinate, etc.
* @param {array} [results] - The array to store the results in. If not provided a new one will be generated.
* @return {array} An array of coordinates.
*/
coordinatesOnLine: function (stepRate, results) {
if (stepRate === undefined) { stepRate = 1; }
if (results === undefined) { results = []; }
var x1 = Math.round(this.start.x);
var y1 = Math.round(this.start.y);
var x2 = Math.round(this.end.x);
var y2 = Math.round(this.end.y);
var dx = Math.abs(x2 - x1);
var dy = Math.abs(y2 - y1);
var sx = (x1 < x2) ? 1 : -1;
var sy = (y1 < y2) ? 1 : -1;
var err = dx - dy;
results.push([x1, y1]);
var i = 1;
while (!((x1 === x2) && (y1 === y2)))
{
var e2 = err << 1;
if (e2 > -dy)
{
err -= dy;
x1 += sx;
}
if (e2 < dx)
{
err += dx;
y1 += sy;
}
if (i % stepRate === 0)
{
results.push([x1, y1]);
}
i++;
}
return results;
},
/**
* Returns a new Line object with the same values for the start and end properties as this Line object.
* @method Phaser.Line#clone
* @param {Phaser.Line} output - Optional Line object. If given the values will be set into the object, otherwise a brand new Line object will be created and returned.
* @return {Phaser.Line} The cloned Line object.
*/
clone: function (output) {
if (output === undefined || output === null)
{
output = new Phaser.Line(this.start.x, this.start.y, this.end.x, this.end.y);
}
else
{
output.setTo(this.start.x, this.start.y, this.end.x, this.end.y);
}
return output;
}
};
/**
* @name Phaser.Line#length
* @property {number} length - Gets the length of the line segment.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "length", {
get: function () {
return Math.sqrt((this.end.x - this.start.x) * (this.end.x - this.start.x) + (this.end.y - this.start.y) * (this.end.y - this.start.y));
}
});
/**
* @name Phaser.Line#angle
* @property {number} angle - Gets the angle of the line in radians.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "angle", {
get: function () {
return Math.atan2(this.end.y - this.start.y, this.end.x - this.start.x);
}
});
/**
* @name Phaser.Line#slope
* @property {number} slope - Gets the slope of the line (y/x).
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "slope", {
get: function () {
return (this.end.y - this.start.y) / (this.end.x - this.start.x);
}
});
/**
* @name Phaser.Line#perpSlope
* @property {number} perpSlope - Gets the perpendicular slope of the line (x/y).
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "perpSlope", {
get: function () {
return -((this.end.x - this.start.x) / (this.end.y - this.start.y));
}
});
/**
* @name Phaser.Line#x
* @property {number} x - Gets the x coordinate of the top left of the bounds around this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "x", {
get: function () {
return Math.min(this.start.x, this.end.x);
}
});
/**
* @name Phaser.Line#y
* @property {number} y - Gets the y coordinate of the top left of the bounds around this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "y", {
get: function () {
return Math.min(this.start.y, this.end.y);
}
});
/**
* @name Phaser.Line#left
* @property {number} left - Gets the left-most point of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "left", {
get: function () {
return Math.min(this.start.x, this.end.x);
}
});
/**
* @name Phaser.Line#right
* @property {number} right - Gets the right-most point of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "right", {
get: function () {
return Math.max(this.start.x, this.end.x);
}
});
/**
* @name Phaser.Line#top
* @property {number} top - Gets the top-most point of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "top", {
get: function () {
return Math.min(this.start.y, this.end.y);
}
});
/**
* @name Phaser.Line#bottom
* @property {number} bottom - Gets the bottom-most point of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "bottom", {
get: function () {
return Math.max(this.start.y, this.end.y);
}
});
/**
* @name Phaser.Line#width
* @property {number} width - Gets the width of this bounds of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "width", {
get: function () {
return Math.abs(this.start.x - this.end.x);
}
});
/**
* @name Phaser.Line#height
* @property {number} height - Gets the height of this bounds of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "height", {
get: function () {
return Math.abs(this.start.y - this.end.y);
}
});
/**
* @name Phaser.Line#normalX
* @property {number} normalX - Gets the x component of the left-hand normal of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "normalX", {
get: function () {
return Math.cos(this.angle - 1.5707963267948966);
}
});
/**
* @name Phaser.Line#normalY
* @property {number} normalY - Gets the y component of the left-hand normal of this line.
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "normalY", {
get: function () {
return Math.sin(this.angle - 1.5707963267948966);
}
});
/**
* @name Phaser.Line#normalAngle
* @property {number} normalAngle - Gets the angle in radians of the normal of this line (line.angle - 90 degrees.)
* @readonly
*/
Object.defineProperty(Phaser.Line.prototype, "normalAngle", {
get: function () {
return Phaser.Math.wrap(this.angle - 1.5707963267948966, -Math.PI, Math.PI);
}
});
/**
* Checks for intersection between two lines as defined by the given start and end points.
* If asSegment is true it will check for line segment intersection. If asSegment is false it will check for line intersection.
* Returns the intersection segment of AB and EF as a Point, or null if there is no intersection.
* Adapted from code by Keith Hair
*
* @method Phaser.Line.intersectsPoints
* @param {Phaser.Point} a - The start of the first Line to be checked.
* @param {Phaser.Point} b - The end of the first line to be checked.
* @param {Phaser.Point} e - The start of the second Line to be checked.
* @param {Phaser.Point} f - The end of the second line to be checked.
* @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection.
* @param {Phaser.Point|object} [result] - A Point object to store the result in, if not given a new one will be created.
* @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection.
*/
Phaser.Line.intersectsPoints = function (a, b, e, f, asSegment, result) {
if (asSegment === undefined) { asSegment = true; }
if (result === undefined) { result = new Phaser.Point(); }
var a1 = b.y - a.y;
var a2 = f.y - e.y;
var b1 = a.x - b.x;
var b2 = e.x - f.x;
var c1 = (b.x * a.y) - (a.x * b.y);
var c2 = (f.x * e.y) - (e.x * f.y);
var denom = (a1 * b2) - (a2 * b1);
if (denom === 0)
{
return null;
}
result.x = ((b1 * c2) - (b2 * c1)) / denom;
result.y = ((a2 * c1) - (a1 * c2)) / denom;
if (asSegment)
{
var uc = ((f.y - e.y) * (b.x - a.x) - (f.x - e.x) * (b.y - a.y));
var ua = (((f.x - e.x) * (a.y - e.y)) - (f.y - e.y) * (a.x - e.x)) / uc;
var ub = (((b.x - a.x) * (a.y - e.y)) - ((b.y - a.y) * (a.x - e.x))) / uc;
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
{
return result;
}
else
{
return null;
}
}
return result;
};
/**
* Checks for intersection between two lines.
* If asSegment is true it will check for segment intersection.
* If asSegment is false it will check for line intersection.
* Returns the intersection segment of AB and EF as a Point, or null if there is no intersection.
* Adapted from code by Keith Hair
*
* @method Phaser.Line.intersects
* @param {Phaser.Line} a - The first Line to be checked.
* @param {Phaser.Line} b - The second Line to be checked.
* @param {boolean} [asSegment=true] - If true it will check for segment intersection, otherwise full line intersection.
* @param {Phaser.Point} [result] - A Point object to store the result in, if not given a new one will be created.
* @return {Phaser.Point} The intersection segment of the two lines as a Point, or null if there is no intersection.
*/
Phaser.Line.intersects = function (a, b, asSegment, result) {
return Phaser.Line.intersectsPoints(a.start, a.end, b.start, b.end, asSegment, result);
};
/**
* Checks for intersection between the Line and a Rectangle shape, or a rectangle-like
* object, with public `x`, `y`, `right` and `bottom` properties, such as a Sprite or Body.
*
* An intersection is considered valid if:
*
* The line starts within, or ends within, the Rectangle.
* The line segment intersects one of the 4 rectangle edges.
*
* The for the purposes of this function rectangles are considered 'solid'.
*
* @method Phaser.Line.intersectsRectangle
* @param {Phaser.Line} line - The line to check for intersection with.
* @param {Phaser.Rectangle|object} rect - The rectangle, or rectangle-like object, to check for intersection with.
* @return {boolean} True if the line intersects with the rectangle edges, or starts or ends within the rectangle.
*/
Phaser.Line.intersectsRectangle = function (line, rect) {
// Quick bail out of the Line and Rect bounds don't intersect
if (!Phaser.Rectangle.intersects(line, rect))
{
return false;
}
var x1 = line.start.x;
var y1 = line.start.y;
var x2 = line.end.x;
var y2 = line.end.y;
var bx1 = rect.x;
var by1 = rect.y;
var bx2 = rect.right;
var by2 = rect.bottom;
var t = 0;
// If the start or end of the line is inside the rect then we assume
// collision, as rects are solid for our use-case.
if ((x1 >= bx1 && x1 <= bx2 && y1 >= by1 && y1 <= by2) ||
(x2 >= bx1 && x2 <= bx2 && y2 >= by1 && y2 <= by2))
{
return true;
}
if (x1 < bx1 && x2 >= bx1)
{
// Left edge
t = y1 + (y2 - y1) * (bx1 - x1) / (x2 - x1);
if (t > by1 && t <= by2)
{
return true;
}
}
else if (x1 > bx2 && x2 <= bx2)
{
// Right edge
t = y1 + (y2 - y1) * (bx2 - x1) / (x2 - x1);
if (t >= by1 && t <= by2)
{
return true;
}
}
if (y1 < by1 && y2 >= by1)
{
// Top edge
t = x1 + (x2 - x1) * (by1 - y1) / (y2 - y1);
if (t >= bx1 && t <= bx2)
{
return true;
}
}
else if (y1 > by2 && y2 <= by2)
{
// Bottom edge
t = x1 + (x2 - x1) * (by2 - y1) / (y2 - y1);
if (t >= bx1 && t <= bx2)
{
return true;
}
}
return false;
};
/**
* Returns the reflected angle between two lines.
* This is the outgoing angle based on the angle of Line 1 and the normalAngle of Line 2.
*
* @method Phaser.Line.reflect
* @param {Phaser.Line} a - The base line.
* @param {Phaser.Line} b - The line to be reflected from the base line.
* @return {number} The reflected angle in radians.
*/
Phaser.Line.reflect = function (a, b) {
return 2 * b.normalAngle - 3.141592653589793 - a.angle;
};
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Matrix is a 3x3 matrix mostly used for display transforms within the renderer.
*
* It is represented like so:
*
* | a | b | tx |
* | c | d | ty |
* | 0 | 0 | 1 |
*
* @class Phaser.Matrix
* @constructor
* @param {number} [a=1] - Horizontal scaling
* @param {number} [b=0] - Horizontal skewing
* @param {number} [c=0] - Vertical skewing
* @param {number} [d=1] - Vertical scaling
* @param {number} [tx=0] - Horizontal translation
* @param {number} [ty=0] - Vertical translation
*/
Phaser.Matrix = function (a, b, c, d, tx, ty) {
if (a === undefined || a === null) { a = 1; }
if (b === undefined || b === null) { b = 0; }
if (c === undefined || c === null) { c = 0; }
if (d === undefined || d === null) { d = 1; }
if (tx === undefined || tx === null) { tx = 0; }
if (ty === undefined || ty === null) { ty = 0; }
/**
* @property {number} a
* @default 1
*/
this.a = a;
/**
* @property {number} b
* @default 0
*/
this.b = b;
/**
* @property {number} c
* @default 0
*/
this.c = c;
/**
* @property {number} d
* @default 1
*/
this.d = d;
/**
* @property {number} tx
* @default 0
*/
this.tx = tx;
/**
* @property {number} ty
* @default 0
*/
this.ty = ty;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.MATRIX;
};
Phaser.Matrix.prototype = {
/**
* Sets the values of this Matrix to the values in the given array.
*
* The Array elements should be set as follows:
*
* a = array[0]
* b = array[1]
* c = array[3]
* d = array[4]
* tx = array[2]
* ty = array[5]
*
* @method Phaser.Matrix#fromArray
* @param {Array} array - The array to copy from.
* @return {Phaser.Matrix} This Matrix object.
*/
fromArray: function (array) {
return this.setTo(array[0], array[1], array[3], array[4], array[2], array[5]);
},
/**
* Sets the values of this Matrix to the given values.
*
* @method Phaser.Matrix#setTo
* @param {number} a - Horizontal scaling
* @param {number} b - Horizontal skewing
* @param {number} c - Vertical skewing
* @param {number} d - Vertical scaling
* @param {number} tx - Horizontal translation
* @param {number} ty - Vertical translation
* @return {Phaser.Matrix} This Matrix object.
*/
setTo: function (a, b, c, d, tx, ty) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.tx = tx;
this.ty = ty;
return this;
},
/**
* Creates a new Matrix object based on the values of this Matrix.
* If you provide the output parameter the values of this Matrix will be copied over to it.
* If the output parameter is blank a new Matrix object will be created.
*
* @method Phaser.Matrix#clone
* @param {Phaser.Matrix} [output] - If provided the values of this Matrix will be copied to it, otherwise a new Matrix object is created.
* @return {Phaser.Matrix} A clone of this Matrix.
*/
clone: function (output) {
if (output === undefined || output === null)
{
output = new Phaser.Matrix(this.a, this.b, this.c, this.d, this.tx, this.ty);
}
else
{
output.a = this.a;
output.b = this.b;
output.c = this.c;
output.d = this.d;
output.tx = this.tx;
output.ty = this.ty;
}
return output;
},
/**
* Copies the properties from this Matrix to the given Matrix.
*
* @method Phaser.Matrix#copyTo
* @param {Phaser.Matrix} matrix - The Matrix to copy from.
* @return {Phaser.Matrix} The destination Matrix object.
*/
copyTo: function (matrix) {
matrix.copyFrom(this);
return matrix;
},
/**
* Copies the properties from the given Matrix into this Matrix.
*
* @method Phaser.Matrix#copyFrom
* @param {Phaser.Matrix} matrix - The Matrix to copy from.
* @return {Phaser.Matrix} This Matrix object.
*/
copyFrom: function (matrix) {
this.a = matrix.a;
this.b = matrix.b;
this.c = matrix.c;
this.d = matrix.d;
this.tx = matrix.tx;
this.ty = matrix.ty;
return this;
},
/**
* Creates a Float32 Array with values populated from this Matrix object.
*
* @method Phaser.Matrix#toArray
* @param {boolean} [transpose=false] - Whether the values in the array are transposed or not.
* @param {Float32Array} [array] - If provided the values will be set into this array, otherwise a new Float32Array is created.
* @return {Float32Array} The newly created array which contains the matrix.
*/
toArray: function (transpose, array) {
if (array === undefined) { array = new Float32Array(9); }
if (transpose)
{
array[0] = this.a;
array[1] = this.b;
array[2] = 0;
array[3] = this.c;
array[4] = this.d;
array[5] = 0;
array[6] = this.tx;
array[7] = this.ty;
array[8] = 1;
}
else
{
array[0] = this.a;
array[1] = this.c;
array[2] = this.tx;
array[3] = this.b;
array[4] = this.d;
array[5] = this.ty;
array[6] = 0;
array[7] = 0;
array[8] = 1;
}
return array;
},
/**
* Get a new position with the current transformation applied.
*
* Can be used to go from a childs coordinate space to the world coordinate space (e.g. rendering)
*
* @method Phaser.Matrix#apply
* @param {Phaser.Point} pos - The origin Point.
* @param {Phaser.Point} [newPos] - The point that the new position is assigned to. This can be same as input point.
* @return {Phaser.Point} The new point, transformed through this matrix.
*/
apply: function (pos, newPos) {
if (newPos === undefined) { newPos = new Phaser.Point(); }
newPos.x = this.a * pos.x + this.c * pos.y + this.tx;
newPos.y = this.b * pos.x + this.d * pos.y + this.ty;
return newPos;
},
/**
* Get a new position with the inverse of the current transformation applied.
*
* Can be used to go from the world coordinate space to a childs coordinate space. (e.g. input)
*
* @method Phaser.Matrix#applyInverse
* @param {Phaser.Point} pos - The origin Point.
* @param {Phaser.Point} [newPos] - The point that the new position is assigned to. This can be same as input point.
* @return {Phaser.Point} The new point, inverse transformed through this matrix.
*/
applyInverse: function (pos, newPos) {
if (newPos === undefined) { newPos = new Phaser.Point(); }
var id = 1 / (this.a * this.d + this.c * -this.b);
var x = pos.x;
var y = pos.y;
newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id;
newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id;
return newPos;
},
/**
* Translates the matrix on the x and y.
* This is the same as Matrix.tx += x.
*
* @method Phaser.Matrix#translate
* @param {number} x - The x value to translate on.
* @param {number} y - The y value to translate on.
* @return {Phaser.Matrix} This Matrix object.
*/
translate: function (x, y) {
this.tx += x;
this.ty += y;
return this;
},
/**
* Applies a scale transformation to this matrix.
*
* @method Phaser.Matrix#scale
* @param {number} x - The amount to scale horizontally.
* @param {number} y - The amount to scale vertically.
* @return {Phaser.Matrix} This Matrix object.
*/
scale: function (x, y) {
this.a *= x;
this.d *= y;
this.c *= x;
this.b *= y;
this.tx *= x;
this.ty *= y;
return this;
},
/**
* Applies a rotation transformation to this matrix.
*
* @method Phaser.Matrix#rotate
* @param {number} angle - The angle to rotate by, given in radians.
* @return {Phaser.Matrix} This Matrix object.
*/
rotate: function (angle) {
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var a1 = this.a;
var c1 = this.c;
var tx1 = this.tx;
this.a = a1 * cos-this.b * sin;
this.b = a1 * sin+this.b * cos;
this.c = c1 * cos-this.d * sin;
this.d = c1 * sin+this.d * cos;
this.tx = tx1 * cos - this.ty * sin;
this.ty = tx1 * sin + this.ty * cos;
return this;
},
/**
* Appends the given Matrix to this Matrix.
*
* @method Phaser.Matrix#append
* @param {Phaser.Matrix} matrix - The matrix to append to this one.
* @return {Phaser.Matrix} This Matrix object.
*/
append: function (matrix) {
var a1 = this.a;
var b1 = this.b;
var c1 = this.c;
var d1 = this.d;
this.a = matrix.a * a1 + matrix.b * c1;
this.b = matrix.a * b1 + matrix.b * d1;
this.c = matrix.c * a1 + matrix.d * c1;
this.d = matrix.c * b1 + matrix.d * d1;
this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx;
this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty;
return this;
},
/**
* Resets this Matrix to an identity (default) matrix.
*
* @method Phaser.Matrix#identity
* @return {Phaser.Matrix} This Matrix object.
*/
identity: function () {
return this.setTo(1, 0, 0, 1, 0, 0);
}
};
Phaser.identityMatrix = new Phaser.Matrix();
// Because PIXI uses its own type, we'll replace it with ours to avoid duplicating code or confusion.
PIXI.Matrix = Phaser.Matrix;
PIXI.identityMatrix = Phaser.identityMatrix;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Point object represents a location in a two-dimensional coordinate system, where x represents the horizontal axis and y represents the vertical axis.
* The following code creates a point at (0,0):
* `var myPoint = new Phaser.Point();`
* You can also use them as 2D Vectors and you'll find different vector related methods in this class.
*
* @class Phaser.Point
* @constructor
* @param {number} [x=0] - The horizontal position of this Point.
* @param {number} [y=0] - The vertical position of this Point.
*/
Phaser.Point = function (x, y) {
x = x || 0;
y = y || 0;
/**
* @property {number} x - The x value of the point.
*/
this.x = x;
/**
* @property {number} y - The y value of the point.
*/
this.y = y;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.POINT;
};
Phaser.Point.prototype = {
/**
* Copies the x and y properties from any given object to this Point.
*
* @method Phaser.Point#copyFrom
* @param {any} source - The object to copy from.
* @return {Phaser.Point} This Point object.
*/
copyFrom: function (source) {
return this.setTo(source.x, source.y);
},
/**
* Inverts the x and y values of this Point
*
* @method Phaser.Point#invert
* @return {Phaser.Point} This Point object.
*/
invert: function () {
return this.setTo(this.y, this.x);
},
/**
* Sets the `x` and `y` values of this Point object to the given values.
* If you omit the `y` value then the `x` value will be applied to both, for example:
* `Point.setTo(2)` is the same as `Point.setTo(2, 2)`
*
* @method Phaser.Point#setTo
* @param {number} x - The horizontal value of this point.
* @param {number} [y] - The vertical value of this point. If not given the x value will be used in its place.
* @return {Phaser.Point} This Point object. Useful for chaining method calls.
*/
setTo: function (x, y) {
this.x = x || 0;
this.y = y || ( (y !== 0) ? this.x : 0 );
return this;
},
/**
* Sets the `x` and `y` values of this Point object to the given values.
* If you omit the `y` value then the `x` value will be applied to both, for example:
* `Point.set(2)` is the same as `Point.set(2, 2)`
*
* @method Phaser.Point#set
* @param {number} x - The horizontal value of this point.
* @param {number} [y] - The vertical value of this point. If not given the x value will be used in its place.
* @return {Phaser.Point} This Point object. Useful for chaining method calls.
*/
set: function (x, y) {
this.x = x || 0;
this.y = y || ( (y !== 0) ? this.x : 0 );
return this;
},
/**
* Adds the given x and y values to this Point.
*
* @method Phaser.Point#add
* @param {number} x - The value to add to Point.x.
* @param {number} y - The value to add to Point.y.
* @return {Phaser.Point} This Point object. Useful for chaining method calls.
*/
add: function (x, y) {
this.x += x;
this.y += y;
return this;
},
/**
* Subtracts the given x and y values from this Point.
*
* @method Phaser.Point#subtract
* @param {number} x - The value to subtract from Point.x.
* @param {number} y - The value to subtract from Point.y.
* @return {Phaser.Point} This Point object. Useful for chaining method calls.
*/
subtract: function (x, y) {
this.x -= x;
this.y -= y;
return this;
},
/**
* Multiplies Point.x and Point.y by the given x and y values. Sometimes known as `Scale`.
*
* @method Phaser.Point#multiply
* @param {number} x - The value to multiply Point.x by.
* @param {number} y - The value to multiply Point.x by.
* @return {Phaser.Point} This Point object. Useful for chaining method calls.
*/
multiply: function (x, y) {
this.x *= x;
this.y *= y;
return this;
},
/**
* Divides Point.x and Point.y by the given x and y values.
*
* @method Phaser.Point#divide
* @param {number} x - The value to divide Point.x by.
* @param {number} y - The value to divide Point.x by.
* @return {Phaser.Point} This Point object. Useful for chaining method calls.
*/
divide: function (x, y) {
this.x /= x;
this.y /= y;
return this;
},
/**
* Clamps the x value of this Point to be between the given min and max.
*
* @method Phaser.Point#clampX
* @param {number} min - The minimum value to clamp this Point to.
* @param {number} max - The maximum value to clamp this Point to.
* @return {Phaser.Point} This Point object.
*/
clampX: function (min, max) {
this.x = Phaser.Math.clamp(this.x, min, max);
return this;
},
/**
* Clamps the y value of this Point to be between the given min and max
*
* @method Phaser.Point#clampY
* @param {number} min - The minimum value to clamp this Point to.
* @param {number} max - The maximum value to clamp this Point to.
* @return {Phaser.Point} This Point object.
*/
clampY: function (min, max) {
this.y = Phaser.Math.clamp(this.y, min, max);
return this;
},
/**
* Clamps this Point object values to be between the given min and max.
*
* @method Phaser.Point#clamp
* @param {number} min - The minimum value to clamp this Point to.
* @param {number} max - The maximum value to clamp this Point to.
* @return {Phaser.Point} This Point object.
*/
clamp: function (min, max) {
this.x = Phaser.Math.clamp(this.x, min, max);
this.y = Phaser.Math.clamp(this.y, min, max);
return this;
},
/**
* Creates a copy of the given Point.
*
* @method Phaser.Point#clone
* @param {Phaser.Point} [output] Optional Point object. If given the values will be set into this object, otherwise a brand new Point object will be created and returned.
* @return {Phaser.Point} The new Point object.
*/
clone: function (output) {
if (output === undefined || output === null)
{
output = new Phaser.Point(this.x, this.y);
}
else
{
output.setTo(this.x, this.y);
}
return output;
},
/**
* Copies the x and y properties from this Point to any given object.
*
* @method Phaser.Point#copyTo
* @param {any} dest - The object to copy to.
* @return {object} The dest object.
*/
copyTo: function (dest) {
dest.x = this.x;
dest.y = this.y;
return dest;
},
/**
* Returns the distance of this Point object to the given object (can be a Circle, Point or anything with x/y properties)
*
* @method Phaser.Point#distance
* @param {object} dest - The target object. Must have visible x and y properties that represent the center of the object.
* @param {boolean} [round] - Round the distance to the nearest integer (default false).
* @return {number} The distance between this Point object and the destination Point object.
*/
distance: function (dest, round) {
return Phaser.Point.distance(this, dest, round);
},
/**
* Determines whether the given objects x/y values are equal to this Point object.
*
* @method Phaser.Point#equals
* @param {Phaser.Point|any} a - The object to compare with this Point.
* @return {boolean} A value of true if the x and y points are equal, otherwise false.
*/
equals: function (a) {
return (a.x === this.x && a.y === this.y);
},
/**
* Returns the angle between this Point object and another object with public x and y properties.
*
* @method Phaser.Point#angle
* @param {Phaser.Point|any} a - The object to get the angle from this Point to.
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)?
* @return {number} The angle between the two objects.
*/
angle: function (a, asDegrees) {
if (asDegrees === undefined) { asDegrees = false; }
if (asDegrees)
{
return Phaser.Math.radToDeg(Math.atan2(a.y - this.y, a.x - this.x));
}
else
{
return Math.atan2(a.y - this.y, a.x - this.x);
}
},
/**
* Rotates this Point around the x/y coordinates given to the desired angle.
*
* @method Phaser.Point#rotate
* @param {number} x - The x coordinate of the anchor point.
* @param {number} y - The y coordinate of the anchor point.
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the Point to.
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)?
* @param {number} [distance] - An optional distance constraint between the Point and the anchor.
* @return {Phaser.Point} The modified point object.
*/
rotate: function (x, y, angle, asDegrees, distance) {
return Phaser.Point.rotate(this, x, y, angle, asDegrees, distance);
},
/**
* Calculates the length of the Point object.
*
* @method Phaser.Point#getMagnitude
* @return {number} The length of the Point.
*/
getMagnitude: function () {
return Math.sqrt((this.x * this.x) + (this.y * this.y));
},
/**
* Calculates the length squared of the Point object.
*
* @method Phaser.Point#getMagnitudeSq
* @return {number} The length ^ 2 of the Point.
*/
getMagnitudeSq: function () {
return (this.x * this.x) + (this.y * this.y);
},
/**
* Alters the length of the Point without changing the direction.
*
* @method Phaser.Point#setMagnitude
* @param {number} magnitude - The desired magnitude of the resulting Point.
* @return {Phaser.Point} This Point object.
*/
setMagnitude: function (magnitude) {
return this.normalize().multiply(magnitude, magnitude);
},
/**
* Alters the Point object so that its length is 1, but it retains the same direction.
*
* @method Phaser.Point#normalize
* @return {Phaser.Point} This Point object.
*/
normalize: function () {
if (!this.isZero())
{
var m = this.getMagnitude();
this.x /= m;
this.y /= m;
}
return this;
},
/**
* Determine if this point is at 0,0.
*
* @method Phaser.Point#isZero
* @return {boolean} True if this Point is 0,0, otherwise false.
*/
isZero: function () {
return (this.x === 0 && this.y === 0);
},
/**
* The dot product of this and another Point object.
*
* @method Phaser.Point#dot
* @param {Phaser.Point} a - The Point object to get the dot product combined with this Point.
* @return {number} The result.
*/
dot: function (a) {
return ((this.x * a.x) + (this.y * a.y));
},
/**
* The cross product of this and another Point object.
*
* @method Phaser.Point#cross
* @param {Phaser.Point} a - The Point object to get the cross product combined with this Point.
* @return {number} The result.
*/
cross: function (a) {
return ((this.x * a.y) - (this.y * a.x));
},
/**
* Make this Point perpendicular (90 degrees rotation)
*
* @method Phaser.Point#perp
* @return {Phaser.Point} This Point object.
*/
perp: function () {
return this.setTo(-this.y, this.x);
},
/**
* Make this Point perpendicular (-90 degrees rotation)
*
* @method Phaser.Point#rperp
* @return {Phaser.Point} This Point object.
*/
rperp: function () {
return this.setTo(this.y, -this.x);
},
/**
* Right-hand normalize (make unit length) this Point.
*
* @method Phaser.Point#normalRightHand
* @return {Phaser.Point} This Point object.
*/
normalRightHand: function () {
return this.setTo(this.y * -1, this.x);
},
/**
* Math.floor() both the x and y properties of this Point.
*
* @method Phaser.Point#floor
* @return {Phaser.Point} This Point object.
*/
floor: function () {
return this.setTo(Math.floor(this.x), Math.floor(this.y));
},
/**
* Math.ceil() both the x and y properties of this Point.
*
* @method Phaser.Point#ceil
* @return {Phaser.Point} This Point object.
*/
ceil: function () {
return this.setTo(Math.ceil(this.x), Math.ceil(this.y));
},
/**
* Returns a string representation of this object.
*
* @method Phaser.Point#toString
* @return {string} A string representation of the instance.
*/
toString: function () {
return '[{Point (x=' + this.x + ' y=' + this.y + ')}]';
}
};
Phaser.Point.prototype.constructor = Phaser.Point;
/**
* Adds the coordinates of two points together to create a new point.
*
* @method Phaser.Point.add
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.add = function (a, b, out) {
if (out === undefined) { out = new Phaser.Point(); }
out.x = a.x + b.x;
out.y = a.y + b.y;
return out;
};
/**
* Subtracts the coordinates of two points to create a new point.
*
* @method Phaser.Point.subtract
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.subtract = function (a, b, out) {
if (out === undefined) { out = new Phaser.Point(); }
out.x = a.x - b.x;
out.y = a.y - b.y;
return out;
};
/**
* Multiplies the coordinates of two points to create a new point.
*
* @method Phaser.Point.multiply
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.multiply = function (a, b, out) {
if (out === undefined) { out = new Phaser.Point(); }
out.x = a.x * b.x;
out.y = a.y * b.y;
return out;
};
/**
* Divides the coordinates of two points to create a new point.
*
* @method Phaser.Point.divide
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.divide = function (a, b, out) {
if (out === undefined) { out = new Phaser.Point(); }
out.x = a.x / b.x;
out.y = a.y / b.y;
return out;
};
/**
* Determines whether the two given Point objects are equal. They are considered equal if they have the same x and y values.
*
* @method Phaser.Point.equals
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @return {boolean} A value of true if the Points are equal, otherwise false.
*/
Phaser.Point.equals = function (a, b) {
return (a.x === b.x && a.y === b.y);
};
/**
* Returns the angle between two Point objects.
*
* @method Phaser.Point.angle
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @return {number} The angle between the two Points.
*/
Phaser.Point.angle = function (a, b) {
// return Math.atan2(a.x * b.y - a.y * b.x, a.x * b.x + a.y * b.y);
return Math.atan2(a.y - b.y, a.x - b.x);
};
/**
* Creates a negative Point.
*
* @method Phaser.Point.negative
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.negative = function (a, out) {
if (out === undefined) { out = new Phaser.Point(); }
return out.setTo(-a.x, -a.y);
};
/**
* Adds two 2D Points together and multiplies the result by the given scalar.
*
* @method Phaser.Point.multiplyAdd
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {number} s - The scaling value.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.multiplyAdd = function (a, b, s, out) {
if (out === undefined) { out = new Phaser.Point(); }
return out.setTo(a.x + b.x * s, a.y + b.y * s);
};
/**
* Interpolates the two given Points, based on the `f` value (between 0 and 1) and returns a new Point.
*
* @method Phaser.Point.interpolate
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {number} f - The level of interpolation between the two points. Indicates where the new point will be, along the line between pt1 and pt2. If f=1, pt1 is returned; if f=0, pt2 is returned.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.interpolate = function (a, b, f, out) {
if (out === undefined) { out = new Phaser.Point(); }
return out.setTo(a.x + (b.x - a.x) * f, a.y + (b.y - a.y) * f);
};
/**
* Return a perpendicular vector (90 degrees rotation)
*
* @method Phaser.Point.perp
* @param {Phaser.Point} a - The Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.perp = function (a, out) {
if (out === undefined) { out = new Phaser.Point(); }
return out.setTo(-a.y, a.x);
};
/**
* Return a perpendicular vector (-90 degrees rotation)
*
* @method Phaser.Point.rperp
* @param {Phaser.Point} a - The Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.rperp = function (a, out) {
if (out === undefined) { out = new Phaser.Point(); }
return out.setTo(a.y, -a.x);
};
/**
* Returns the euclidian distance of this Point object to the given object (can be a Circle, Point or anything with x/y properties).
*
* @method Phaser.Point.distance
* @param {object} a - The target object. Must have visible x and y properties that represent the center of the object.
* @param {object} b - The target object. Must have visible x and y properties that represent the center of the object.
* @param {boolean} [round=false] - Round the distance to the nearest integer.
* @return {number} The distance between this Point object and the destination Point object.
*/
Phaser.Point.distance = function (a, b, round) {
var distance = Phaser.Math.distance(a.x, a.y, b.x, b.y);
return round ? Math.round(distance) : distance;
};
/**
* Project two Points onto another Point.
*
* @method Phaser.Point.project
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.project = function (a, b, out) {
if (out === undefined) { out = new Phaser.Point(); }
var amt = a.dot(b) / b.getMagnitudeSq();
if (amt !== 0)
{
out.setTo(amt * b.x, amt * b.y);
}
return out;
};
/**
* Project two Points onto a Point of unit length.
*
* @method Phaser.Point.projectUnit
* @param {Phaser.Point} a - The first Point object.
* @param {Phaser.Point} b - The second Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.projectUnit = function (a, b, out) {
if (out === undefined) { out = new Phaser.Point(); }
var amt = a.dot(b);
if (amt !== 0)
{
out.setTo(amt * b.x, amt * b.y);
}
return out;
};
/**
* Right-hand normalize (make unit length) a Point.
*
* @method Phaser.Point.normalRightHand
* @param {Phaser.Point} a - The Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.normalRightHand = function (a, out) {
if (out === undefined) { out = new Phaser.Point(); }
return out.setTo(a.y * -1, a.x);
};
/**
* Normalize (make unit length) a Point.
*
* @method Phaser.Point.normalize
* @param {Phaser.Point} a - The Point object.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.normalize = function (a, out) {
if (out === undefined) { out = new Phaser.Point(); }
var m = a.getMagnitude();
if (m !== 0)
{
out.setTo(a.x / m, a.y / m);
}
return out;
};
/**
* Rotates a Point object, or any object with exposed x/y properties, around the given coordinates by
* the angle specified. If the angle between the point and coordinates was 45 deg and the angle argument
* is 45 deg then the resulting angle will be 90 deg, as the angle argument is added to the current angle.
*
* The distance allows you to specify a distance constraint for the rotation between the point and the
* coordinates. If none is given the distance between the two is calculated and used.
*
* @method Phaser.Point.rotate
* @param {Phaser.Point} a - The Point object to rotate.
* @param {number} x - The x coordinate of the anchor point
* @param {number} y - The y coordinate of the anchor point
* @param {number} angle - The angle in radians (unless asDegrees is true) to rotate the Point by.
* @param {boolean} [asDegrees=false] - Is the given angle in radians (false) or degrees (true)?
* @param {number} [distance] - An optional distance constraint between the Point and the anchor.
* @return {Phaser.Point} The modified point object.
*/
Phaser.Point.rotate = function (a, x, y, angle, asDegrees, distance) {
if (asDegrees) { angle = Phaser.Math.degToRad(angle); }
if (distance === undefined)
{
a.subtract(x, y);
var s = Math.sin(angle);
var c = Math.cos(angle);
var tx = c * a.x - s * a.y;
var ty = s * a.x + c * a.y;
a.x = tx + x;
a.y = ty + y;
}
else
{
var t = angle + Math.atan2(a.y - y, a.x - x);
a.x = x + distance * Math.cos(t);
a.y = y + distance * Math.sin(t);
}
return a;
};
/**
* Calculates centroid (or midpoint) from an array of points. If only one point is provided, that point is returned.
*
* @method Phaser.Point.centroid
* @param {Phaser.Point[]} points - The array of one or more points.
* @param {Phaser.Point} [out] - Optional Point to store the value in, if not supplied a new Point object will be created.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.centroid = function (points, out) {
if (out === undefined) { out = new Phaser.Point(); }
if (Object.prototype.toString.call(points) !== '[object Array]')
{
throw new Error("Phaser.Point. Parameter 'points' must be an array");
}
var pointslength = points.length;
if (pointslength < 1)
{
throw new Error("Phaser.Point. Parameter 'points' array must not be empty");
}
if (pointslength === 1)
{
out.copyFrom(points[0]);
return out;
}
for (var i = 0; i < pointslength; i++)
{
Phaser.Point.add(out, points[i], out);
}
out.divide(pointslength, pointslength);
return out;
};
/**
* Parses an object for x and/or y properties and returns a new Phaser.Point with matching values.
* If the object doesn't contain those properties a Point with x/y of zero will be returned.
*
* @method Phaser.Point.parse
* @static
* @param {object} obj - The object to parse.
* @param {string} [xProp='x'] - The property used to set the Point.x value.
* @param {string} [yProp='y'] - The property used to set the Point.y value.
* @return {Phaser.Point} The new Point object.
*/
Phaser.Point.parse = function(obj, xProp, yProp) {
xProp = xProp || 'x';
yProp = yProp || 'y';
var point = new Phaser.Point();
if (obj[xProp])
{
point.x = parseInt(obj[xProp], 10);
}
if (obj[yProp])
{
point.y = parseInt(obj[yProp], 10);
}
return point;
};
// Because PIXI uses its own Point, we'll replace it with ours to avoid duplicating code or confusion.
PIXI.Point = Phaser.Point;
/**
* @author Richard Davey <rich@photonstorm.com>
* @author Adrien Brault <adrien.brault@gmail.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a new Polygon.
*
* The points can be set from a variety of formats:
*
* - An array of Point objects: `[new Phaser.Point(x1, y1), ...]`
* - An array of objects with public x/y properties: `[obj1, obj2, ...]`
* - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]`
* - As separate Point arguments: `setTo(new Phaser.Point(x1, y1), ...)`
* - As separate objects with public x/y properties arguments: `setTo(obj1, obj2, ...)`
* - As separate arguments representing point coordinates: `setTo(x1,y1, x2,y2, ...)`
*
* @class Phaser.Polygon
* @constructor
* @param {Phaser.Point[]|number[]|...Phaser.Point|...number} points - The points to set.
*/
Phaser.Polygon = function () {
/**
* @property {number} area - The area of this Polygon.
*/
this.area = 0;
/**
* @property {array} _points - An array of Points that make up this Polygon.
* @private
*/
this._points = [];
if (arguments.length > 0)
{
this.setTo.apply(this, arguments);
}
/**
* @property {boolean} closed - Is the Polygon closed or not?
*/
this.closed = true;
/**
* @property {boolean} flattened - Has this Polygon been flattened by a call to `Polygon.flatten` ?
*/
this.flattened = false;
/**
* @property {number} type - The base object type.
*/
this.type = Phaser.POLYGON;
};
Phaser.Polygon.prototype = {
/**
* Export the points as an array of flat numbers, following the sequence [ x,y, x,y, x,y ]
*
* @method Phaser.Polygon#toNumberArray
* @param {array} [output] - The array to append the points to. If not specified a new array will be created.
* @return {array} The flattened array.
*/
toNumberArray: function (output) {
if (output === undefined) { output = []; }
for (var i = 0; i < this._points.length; i++)
{
if (typeof this._points[i] === 'number')
{
output.push(this._points[i]);
output.push(this._points[i + 1]);
i++;
}
else
{
output.push(this._points[i].x);
output.push(this._points[i].y);
}
}
return output;
},
/**
* Flattens this Polygon so the points are a sequence of numbers.
* Any Point objects found are removed and replaced with two numbers.
* Also sets the Polygon.flattened property to `true`.
*
* @method Phaser.Polygon#flatten
* @return {Phaser.Polygon} This Polygon object
*/
flatten: function () {
this._points = this.toNumberArray();
this.flattened = true;
return this;
},
/**
* Creates a copy of the given Polygon.
* This is a deep clone, the resulting copy contains new Phaser.Point objects
*
* @method Phaser.Polygon#clone
* @param {Phaser.Polygon} [output=(new Polygon)] - The polygon to update. If not specified a new polygon will be created.
* @return {Phaser.Polygon} The cloned (`output`) polygon object.
*/
clone: function (output) {
var points = this._points.slice();
if (output === undefined || output === null)
{
output = new Phaser.Polygon(points);
}
else
{
output.setTo(points);
}
return output;
},
/**
* Checks whether the x and y coordinates are contained within this polygon.
*
* @method Phaser.Polygon#contains
* @param {number} x - The X value of the coordinate to test.
* @param {number} y - The Y value of the coordinate to test.
* @return {boolean} True if the coordinates are within this polygon, otherwise false.
*/
contains: function (x, y) {
// Adapted from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html by Jonas Raoni Soares Silva
var inside = false;
if (this.flattened)
{
for (var i = -2, j = this._points.length - 2; (i += 2) < this._points.length; j = i)
{
var ix = this._points[i];
var iy = this._points[i + 1];
var jx = this._points[j];
var jy = this._points[j + 1];
if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix))
{
inside = !inside;
}
}
}
else
{
for (var i = -1, j = this._points.length - 1; ++i < this._points.length; j = i)
{
var ix = this._points[i].x;
var iy = this._points[i].y;
var jx = this._points[j].x;
var jy = this._points[j].y;
if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix))
{
inside = !inside;
}
}
}
return inside;
},
/**
* Sets this Polygon to the given points.
*
* The points can be set from a variety of formats:
*
* - An array of Point objects: `[new Phaser.Point(x1, y1), ...]`
* - An array of objects with public x/y properties: `[obj1, obj2, ...]`
* - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]`
* - An array of arrays with two elements representing x/y coordinates: `[[x1, y1], [x2, y2], ...]`
* - As separate Point arguments: `setTo(new Phaser.Point(x1, y1), ...)`
* - As separate objects with public x/y properties arguments: `setTo(obj1, obj2, ...)`
* - As separate arguments representing point coordinates: `setTo(x1,y1, x2,y2, ...)`
*
* `setTo` may also be called without any arguments to remove all points.
*
* @method Phaser.Polygon#setTo
* @param {Phaser.Point[]|number[]|...Phaser.Point|...number} points - The points to set.
* @return {Phaser.Polygon} This Polygon object
*/
setTo: function (points) {
this.area = 0;
this._points = [];
if (arguments.length > 0)
{
// If points isn't an array, use arguments as the array
if (!Array.isArray(points))
{
points = Array.prototype.slice.call(arguments);
}
var y0 = Number.MAX_VALUE;
// Allows for mixed-type arguments
for (var i = 0, len = points.length; i < len; i++)
{
if (typeof points[i] === 'number')
{
var p = new PIXI.Point(points[i], points[i + 1]);
i++;
}
else if (Array.isArray(points[i]))
{
var p = new PIXI.Point(points[i][0], points[i][1]);
}
else
{
var p = new PIXI.Point(points[i].x, points[i].y);
}
this._points.push(p);
// Lowest boundary
if (p.y < y0)
{
y0 = p.y;
}
}
this.calculateArea(y0);
}
return this;
},
/**
* Calcuates the area of the Polygon. This is available in the property Polygon.area
*
* @method Phaser.Polygon#calculateArea
* @private
* @param {number} y0 - The lowest boundary
* @return {number} The area of the Polygon.
*/
calculateArea: function (y0) {
var p1;
var p2;
var avgHeight;
var width;
for (var i = 0, len = this._points.length; i < len; i++)
{
p1 = this._points[i];
if (i === len - 1)
{
p2 = this._points[0];
}
else
{
p2 = this._points[i + 1];
}
avgHeight = ((p1.y - y0) + (p2.y - y0)) / 2;
width = p1.x - p2.x;
this.area += avgHeight * width;
}
return this.area;
}
};
Phaser.Polygon.prototype.constructor = Phaser.Polygon;
/**
* Sets and modifies the points of this polygon.
*
* See {@link Phaser.Polygon#setTo setTo} for the different kinds of arrays formats that can be assigned.
*
* @name Phaser.Polygon#points
* @property {Phaser.Point[]} points - The array of vertex points.
* @deprecated Use `setTo`.
*/
Object.defineProperty(Phaser.Polygon.prototype, 'points', {
get: function() {
return this._points;
},
set: function(points) {
if (points != null)
{
this.setTo(points);
}
else
{
// Clear the points
this.setTo();
}
}
});
// Because PIXI uses its own type, we'll replace it with ours to avoid duplicating code or confusion.
PIXI.Polygon = Phaser.Polygon;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Creates a new Rectangle object with the top-left corner specified by the x and y parameters and with the specified width and height parameters.
* If you call this function without parameters, a Rectangle with x, y, width, and height properties set to 0 is created.
*
* @class Phaser.Rectangle
* @constructor
* @param {number} x - The x coordinate of the top-left corner of the Rectangle.
* @param {number} y - The y coordinate of the top-left corner of the Rectangle.
* @param {number} width - The width of the Rectangle. Should always be either zero or a positive value.
* @param {number} height - The height of the Rectangle. Should always be either zero or a positive value.
*/
Phaser.Rectangle = function (x, y, width, height) {
x = x || 0;
y = y || 0;
width = width || 0;
height = height || 0;
/**
* @property {number} x - The x coordinate of the top-left corner of the Rectangle.
*/
this.x = x;
/**
* @property {number} y - The y coordinate of the top-left corner of the Rectangle.
*/
this.y = y;
/**
* @property {number} width - The width of the Rectangle. This value should never be set to a negative.
*/
this.width = width;
/**
* @property {number} height - The height of the Rectangle. This value should never be set to a negative.
*/
this.height = height;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.RECTANGLE;
};
Phaser.Rectangle.prototype = {
/**
* Adjusts the location of the Rectangle object, as determined by its top-left corner, by the specified amounts.
* @method Phaser.Rectangle#offset
* @param {number} dx - Moves the x value of the Rectangle object by this amount.
* @param {number} dy - Moves the y value of the Rectangle object by this amount.
* @return {Phaser.Rectangle} This Rectangle object.
*/
offset: function (dx, dy) {
this.x += dx;
this.y += dy;
return this;
},
/**
* Adjusts the location of the Rectangle object using a Point object as a parameter. This method is similar to the Rectangle.offset() method, except that it takes a Point object as a parameter.
* @method Phaser.Rectangle#offsetPoint
* @param {Phaser.Point} point - A Point object to use to offset this Rectangle object.
* @return {Phaser.Rectangle} This Rectangle object.
*/
offsetPoint: function (point) {
return this.offset(point.x, point.y);
},
/**
* Sets the members of Rectangle to the specified values.
* @method Phaser.Rectangle#setTo
* @param {number} x - The x coordinate of the top-left corner of the Rectangle.
* @param {number} y - The y coordinate of the top-left corner of the Rectangle.
* @param {number} width - The width of the Rectangle. Should always be either zero or a positive value.
* @param {number} height - The height of the Rectangle. Should always be either zero or a positive value.
* @return {Phaser.Rectangle} This Rectangle object
*/
setTo: function (x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
return this;
},
/**
* Scales the width and height of this Rectangle by the given amounts.
*
* @method Phaser.Rectangle#scale
* @param {number} x - The amount to scale the width of the Rectangle by. A value of 0.5 would reduce by half, a value of 2 would double the width, etc.
* @param {number} [y] - The amount to scale the height of the Rectangle by. A value of 0.5 would reduce by half, a value of 2 would double the height, etc.
* @return {Phaser.Rectangle} This Rectangle object
*/
scale: function (x, y) {
if (y === undefined) { y = x; }
this.width *= x;
this.height *= y;
return this;
},
/**
* Centers this Rectangle so that the center coordinates match the given x and y values.
*
* @method Phaser.Rectangle#centerOn
* @param {number} x - The x coordinate to place the center of the Rectangle at.
* @param {number} y - The y coordinate to place the center of the Rectangle at.
* @return {Phaser.Rectangle} This Rectangle object
*/
centerOn: function (x, y) {
this.centerX = x;
this.centerY = y;
return this;
},
/**
* Runs Math.floor() on both the x and y values of this Rectangle.
* @method Phaser.Rectangle#floor
*/
floor: function () {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
},
/**
* Runs Math.floor() on the x, y, width and height values of this Rectangle.
* @method Phaser.Rectangle#floorAll
*/
floorAll: function () {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
this.width = Math.floor(this.width);
this.height = Math.floor(this.height);
},
/**
* Runs Math.ceil() on both the x and y values of this Rectangle.
* @method Phaser.Rectangle#ceil
*/
ceil: function () {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
},
/**
* Runs Math.ceil() on the x, y, width and height values of this Rectangle.
* @method Phaser.Rectangle#ceilAll
*/
ceilAll: function () {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
this.width = Math.ceil(this.width);
this.height = Math.ceil(this.height);
},
/**
* Copies the x, y, width and height properties from any given object to this Rectangle.
* @method Phaser.Rectangle#copyFrom
* @param {any} source - The object to copy from.
* @return {Phaser.Rectangle} This Rectangle object.
*/
copyFrom: function (source) {
return this.setTo(source.x, source.y, source.width, source.height);
},
/**
* Copies the x, y, width and height properties from this Rectangle to any given object.
* @method Phaser.Rectangle#copyTo
* @param {any} source - The object to copy to.
* @return {object} This object.
*/
copyTo: function (dest) {
dest.x = this.x;
dest.y = this.y;
dest.width = this.width;
dest.height = this.height;
return dest;
},
/**
* Increases the size of the Rectangle object by the specified amounts. The center point of the Rectangle object stays the same, and its size increases to the left and right by the dx value, and to the top and the bottom by the dy value.
* @method Phaser.Rectangle#inflate
* @param {number} dx - The amount to be added to the left side of the Rectangle.
* @param {number} dy - The amount to be added to the bottom side of the Rectangle.
* @return {Phaser.Rectangle} This Rectangle object.
*/
inflate: function (dx, dy) {
return Phaser.Rectangle.inflate(this, dx, dy);
},
/**
* The size of the Rectangle object, expressed as a Point object with the values of the width and height properties.
* @method Phaser.Rectangle#size
* @param {Phaser.Point} [output] - Optional Point object. If given the values will be set into the object, otherwise a brand new Point object will be created and returned.
* @return {Phaser.Point} The size of the Rectangle object.
*/
size: function (output) {
return Phaser.Rectangle.size(this, output);
},
/**
* Resize the Rectangle by providing a new width and height.
* The x and y positions remain unchanged.
*
* @method Phaser.Rectangle#resize
* @param {number} width - The width of the Rectangle. Should always be either zero or a positive value.
* @param {number} height - The height of the Rectangle. Should always be either zero or a positive value.
* @return {Phaser.Rectangle} This Rectangle object
*/
resize: function (width, height) {
this.width = width;
this.height = height;
return this;
},
/**
* Returns a new Rectangle object with the same values for the x, y, width, and height properties as the original Rectangle object.
* @method Phaser.Rectangle#clone
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the values will be set into the object, otherwise a brand new Rectangle object will be created and returned.
* @return {Phaser.Rectangle}
*/
clone: function (output) {
return Phaser.Rectangle.clone(this, output);
},
/**
* Determines whether the specified coordinates are contained within the region defined by this Rectangle object.
* @method Phaser.Rectangle#contains
* @param {number} x - The x coordinate of the point to test.
* @param {number} y - The y coordinate of the point to test.
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false.
*/
contains: function (x, y) {
return Phaser.Rectangle.contains(this, x, y);
},
/**
* Determines whether the first Rectangle object is fully contained within the second Rectangle object.
* A Rectangle object is said to contain another if the second Rectangle object falls entirely within the boundaries of the first.
* @method Phaser.Rectangle#containsRect
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false.
*/
containsRect: function (b) {
return Phaser.Rectangle.containsRect(b, this);
},
/**
* Determines whether the two Rectangles are equal.
* This method compares the x, y, width and height properties of each Rectangle.
* @method Phaser.Rectangle#equals
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @return {boolean} A value of true if the two Rectangles have exactly the same values for the x, y, width and height properties; otherwise false.
*/
equals: function (b) {
return Phaser.Rectangle.equals(this, b);
},
/**
* If the Rectangle object specified in the toIntersect parameter intersects with this Rectangle object, returns the area of intersection as a Rectangle object. If the Rectangles do not intersect, this method returns an empty Rectangle object with its properties set to 0.
* @method Phaser.Rectangle#intersection
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @param {Phaser.Rectangle} out - Optional Rectangle object. If given the intersection values will be set into this object, otherwise a brand new Rectangle object will be created and returned.
* @return {Phaser.Rectangle} A Rectangle object that equals the area of intersection. If the Rectangles do not intersect, this method returns an empty Rectangle object; that is, a Rectangle with its x, y, width, and height properties set to 0.
*/
intersection: function (b, out) {
return Phaser.Rectangle.intersection(this, b, out);
},
/**
* Determines whether this Rectangle and another given Rectangle intersect with each other.
* This method checks the x, y, width, and height properties of the two Rectangles.
*
* @method Phaser.Rectangle#intersects
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @return {boolean} A value of true if the specified object intersects with this Rectangle object; otherwise false.
*/
intersects: function (b) {
return Phaser.Rectangle.intersects(this, b);
},
/**
* Determines whether the coordinates given intersects (overlaps) with this Rectangle.
*
* @method Phaser.Rectangle#intersectsRaw
* @param {number} left - The x coordinate of the left of the area.
* @param {number} right - The right coordinate of the area.
* @param {number} top - The y coordinate of the area.
* @param {number} bottom - The bottom coordinate of the area.
* @param {number} tolerance - A tolerance value to allow for an intersection test with padding, default to 0
* @return {boolean} A value of true if the specified object intersects with the Rectangle; otherwise false.
*/
intersectsRaw: function (left, right, top, bottom, tolerance) {
return Phaser.Rectangle.intersectsRaw(this, left, right, top, bottom, tolerance);
},
/**
* Adds two Rectangles together to create a new Rectangle object, by filling in the horizontal and vertical space between the two Rectangles.
* @method Phaser.Rectangle#union
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @param {Phaser.Rectangle} [out] - Optional Rectangle object. If given the new values will be set into this object, otherwise a brand new Rectangle object will be created and returned.
* @return {Phaser.Rectangle} A Rectangle object that is the union of the two Rectangles.
*/
union: function (b, out) {
return Phaser.Rectangle.union(this, b, out);
},
/**
* Returns a uniformly distributed random point from anywhere within this Rectangle.
*
* @method Phaser.Rectangle#random
* @param {Phaser.Point|object} [out] - A Phaser.Point, or any object with public x/y properties, that the values will be set in.
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object.
* @return {Phaser.Point} An object containing the random point in its `x` and `y` properties.
*/
random: function (out) {
if (out === undefined) { out = new Phaser.Point(); }
out.x = this.randomX;
out.y = this.randomY;
return out;
},
/**
* Returns a point based on the given position constant, which can be one of:
*
* `Phaser.TOP_LEFT`, `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`,
* `Phaser.CENTER`, `Phaser.RIGHT_CENTER`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER`
* and `Phaser.BOTTOM_RIGHT`.
*
* This method returns the same values as calling Rectangle.bottomLeft, etc, but those
* calls always create a new Point object, where-as this one allows you to use your own.
*
* @method Phaser.Rectangle#getPoint
* @param {integer} [position] - One of the Phaser position constants, such as `Phaser.TOP_RIGHT`.
* @param {Phaser.Point} [out] - A Phaser.Point that the values will be set in.
* If no object is provided a new Phaser.Point object will be created. In high performance areas avoid this by re-using an existing object.
* @return {Phaser.Point} An object containing the point in its `x` and `y` properties.
*/
getPoint: function (position, out) {
if (out === undefined) { out = new Phaser.Point(); }
switch (position)
{
default:
case Phaser.TOP_LEFT:
return out.set(this.x, this.y);
case Phaser.TOP_CENTER:
return out.set(this.centerX, this.y);
case Phaser.TOP_RIGHT:
return out.set(this.right, this.y);
case Phaser.LEFT_CENTER:
return out.set(this.x, this.centerY);
case Phaser.CENTER:
return out.set(this.centerX, this.centerY);
case Phaser.RIGHT_CENTER:
return out.set(this.right, this.centerY);
case Phaser.BOTTOM_LEFT:
return out.set(this.x, this.bottom);
case Phaser.BOTTOM_CENTER:
return out.set(this.centerX, this.bottom);
case Phaser.BOTTOM_RIGHT:
return out.set(this.right, this.bottom);
}
},
/**
* Returns a string representation of this object.
* @method Phaser.Rectangle#toString
* @return {string} A string representation of the instance.
*/
toString: function () {
return "[{Rectangle (x=" + this.x + " y=" + this.y + " width=" + this.width + " height=" + this.height + " empty=" + this.empty + ")}]";
}
};
/**
* @name Phaser.Rectangle#halfWidth
* @property {number} halfWidth - Half of the width of the Rectangle.
* @readonly
*/
Object.defineProperty(Phaser.Rectangle.prototype, "halfWidth", {
get: function () {
return Math.round(this.width / 2);
}
});
/**
* @name Phaser.Rectangle#halfHeight
* @property {number} halfHeight - Half of the height of the Rectangle.
* @readonly
*/
Object.defineProperty(Phaser.Rectangle.prototype, "halfHeight", {
get: function () {
return Math.round(this.height / 2);
}
});
/**
* The sum of the y and height properties. Changing the bottom property of a Rectangle object has no effect on the x, y and width properties, but does change the height property.
* @name Phaser.Rectangle#bottom
* @property {number} bottom - The sum of the y and height properties.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "bottom", {
get: function () {
return this.y + this.height;
},
set: function (value) {
if (value <= this.y)
{
this.height = 0;
}
else
{
this.height = value - this.y;
}
}
});
/**
* The location of the Rectangles bottom left corner as a Point object.
* @name Phaser.Rectangle#bottomLeft
* @property {Phaser.Point} bottomLeft - Gets or sets the location of the Rectangles bottom left corner as a Point object.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "bottomLeft", {
get: function () {
return new Phaser.Point(this.x, this.bottom);
},
set: function (value) {
this.x = value.x;
this.bottom = value.y;
}
});
/**
* The location of the Rectangles bottom right corner as a Point object.
* @name Phaser.Rectangle#bottomRight
* @property {Phaser.Point} bottomRight - Gets or sets the location of the Rectangles bottom right corner as a Point object.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "bottomRight", {
get: function () {
return new Phaser.Point(this.right, this.bottom);
},
set: function (value) {
this.right = value.x;
this.bottom = value.y;
}
});
/**
* The x coordinate of the left of the Rectangle. Changing the left property of a Rectangle object has no effect on the y and height properties. However it does affect the width property, whereas changing the x value does not affect the width property.
* @name Phaser.Rectangle#left
* @property {number} left - The x coordinate of the left of the Rectangle.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "left", {
get: function () {
return this.x;
},
set: function (value) {
if (value >= this.right) {
this.width = 0;
} else {
this.width = this.right - value;
}
this.x = value;
}
});
/**
* The sum of the x and width properties. Changing the right property of a Rectangle object has no effect on the x, y and height properties, however it does affect the width property.
* @name Phaser.Rectangle#right
* @property {number} right - The sum of the x and width properties.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "right", {
get: function () {
return this.x + this.width;
},
set: function (value) {
if (value <= this.x) {
this.width = 0;
} else {
this.width = value - this.x;
}
}
});
/**
* The volume of the Rectangle derived from width * height.
* @name Phaser.Rectangle#volume
* @property {number} volume - The volume of the Rectangle derived from width * height.
* @readonly
*/
Object.defineProperty(Phaser.Rectangle.prototype, "volume", {
get: function () {
return this.width * this.height;
}
});
/**
* The perimeter size of the Rectangle. This is the sum of all 4 sides.
* @name Phaser.Rectangle#perimeter
* @property {number} perimeter - The perimeter size of the Rectangle. This is the sum of all 4 sides.
* @readonly
*/
Object.defineProperty(Phaser.Rectangle.prototype, "perimeter", {
get: function () {
return (this.width * 2) + (this.height * 2);
}
});
/**
* The x coordinate of the center of the Rectangle.
* @name Phaser.Rectangle#centerX
* @property {number} centerX - The x coordinate of the center of the Rectangle.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "centerX", {
get: function () {
return this.x + this.halfWidth;
},
set: function (value) {
this.x = value - this.halfWidth;
}
});
/**
* The y coordinate of the center of the Rectangle.
* @name Phaser.Rectangle#centerY
* @property {number} centerY - The y coordinate of the center of the Rectangle.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "centerY", {
get: function () {
return this.y + this.halfHeight;
},
set: function (value) {
this.y = value - this.halfHeight;
}
});
/**
* A random value between the left and right values (inclusive) of the Rectangle.
*
* @name Phaser.Rectangle#randomX
* @property {number} randomX - A random value between the left and right values (inclusive) of the Rectangle.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "randomX", {
get: function () {
return this.x + (Math.random() * this.width);
}
});
/**
* A random value between the top and bottom values (inclusive) of the Rectangle.
*
* @name Phaser.Rectangle#randomY
* @property {number} randomY - A random value between the top and bottom values (inclusive) of the Rectangle.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "randomY", {
get: function () {
return this.y + (Math.random() * this.height);
}
});
/**
* The y coordinate of the top of the Rectangle. Changing the top property of a Rectangle object has no effect on the x and width properties.
* However it does affect the height property, whereas changing the y value does not affect the height property.
* @name Phaser.Rectangle#top
* @property {number} top - The y coordinate of the top of the Rectangle.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "top", {
get: function () {
return this.y;
},
set: function (value) {
if (value >= this.bottom) {
this.height = 0;
this.y = value;
} else {
this.height = (this.bottom - value);
}
}
});
/**
* The location of the Rectangles top left corner as a Point object.
* @name Phaser.Rectangle#topLeft
* @property {Phaser.Point} topLeft - The location of the Rectangles top left corner as a Point object.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "topLeft", {
get: function () {
return new Phaser.Point(this.x, this.y);
},
set: function (value) {
this.x = value.x;
this.y = value.y;
}
});
/**
* The location of the Rectangles top right corner as a Point object.
* @name Phaser.Rectangle#topRight
* @property {Phaser.Point} topRight - The location of the Rectangles top left corner as a Point object.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "topRight", {
get: function () {
return new Phaser.Point(this.x + this.width, this.y);
},
set: function (value) {
this.right = value.x;
this.y = value.y;
}
});
/**
* Determines whether or not this Rectangle object is empty. A Rectangle object is empty if its width or height is less than or equal to 0.
* If set to true then all of the Rectangle properties are set to 0.
* @name Phaser.Rectangle#empty
* @property {boolean} empty - Gets or sets the Rectangles empty state.
*/
Object.defineProperty(Phaser.Rectangle.prototype, "empty", {
get: function () {
return (!this.width || !this.height);
},
set: function (value) {
if (value === true)
{
this.setTo(0, 0, 0, 0);
}
}
});
Phaser.Rectangle.prototype.constructor = Phaser.Rectangle;
/**
* Increases the size of the Rectangle object by the specified amounts. The center point of the Rectangle object stays the same, and its size increases to the left and right by the dx value, and to the top and the bottom by the dy value.
* @method Phaser.Rectangle.inflate
* @param {Phaser.Rectangle} a - The Rectangle object.
* @param {number} dx - The amount to be added to the left side of the Rectangle.
* @param {number} dy - The amount to be added to the bottom side of the Rectangle.
* @return {Phaser.Rectangle} This Rectangle object.
*/
Phaser.Rectangle.inflate = function (a, dx, dy) {
a.x -= dx;
a.width += 2 * dx;
a.y -= dy;
a.height += 2 * dy;
return a;
};
/**
* Increases the size of the Rectangle object. This method is similar to the Rectangle.inflate() method except it takes a Point object as a parameter.
* @method Phaser.Rectangle.inflatePoint
* @param {Phaser.Rectangle} a - The Rectangle object.
* @param {Phaser.Point} point - The x property of this Point object is used to increase the horizontal dimension of the Rectangle object. The y property is used to increase the vertical dimension of the Rectangle object.
* @return {Phaser.Rectangle} The Rectangle object.
*/
Phaser.Rectangle.inflatePoint = function (a, point) {
return Phaser.Rectangle.inflate(a, point.x, point.y);
};
/**
* The size of the Rectangle object, expressed as a Point object with the values of the width and height properties.
* @method Phaser.Rectangle.size
* @param {Phaser.Rectangle} a - The Rectangle object.
* @param {Phaser.Point} [output] - Optional Point object. If given the values will be set into the object, otherwise a brand new Point object will be created and returned.
* @return {Phaser.Point} The size of the Rectangle object
*/
Phaser.Rectangle.size = function (a, output) {
if (output === undefined || output === null)
{
output = new Phaser.Point(a.width, a.height);
}
else
{
output.setTo(a.width, a.height);
}
return output;
};
/**
* Returns a new Rectangle object with the same values for the x, y, width, and height properties as the original Rectangle object.
* @method Phaser.Rectangle.clone
* @param {Phaser.Rectangle} a - The Rectangle object.
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the values will be set into the object, otherwise a brand new Rectangle object will be created and returned.
* @return {Phaser.Rectangle}
*/
Phaser.Rectangle.clone = function (a, output) {
if (output === undefined || output === null)
{
output = new Phaser.Rectangle(a.x, a.y, a.width, a.height);
}
else
{
output.setTo(a.x, a.y, a.width, a.height);
}
return output;
};
/**
* Determines whether the specified coordinates are contained within the region defined by this Rectangle object.
* @method Phaser.Rectangle.contains
* @param {Phaser.Rectangle} a - The Rectangle object.
* @param {number} x - The x coordinate of the point to test.
* @param {number} y - The y coordinate of the point to test.
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false.
*/
Phaser.Rectangle.contains = function (a, x, y) {
if (a.width <= 0 || a.height <= 0)
{
return false;
}
return (x >= a.x && x < a.right && y >= a.y && y < a.bottom);
};
/**
* Determines whether the specified coordinates are contained within the region defined by the given raw values.
* @method Phaser.Rectangle.containsRaw
* @param {number} rx - The x coordinate of the top left of the area.
* @param {number} ry - The y coordinate of the top left of the area.
* @param {number} rw - The width of the area.
* @param {number} rh - The height of the area.
* @param {number} x - The x coordinate of the point to test.
* @param {number} y - The y coordinate of the point to test.
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false.
*/
Phaser.Rectangle.containsRaw = function (rx, ry, rw, rh, x, y) {
return (x >= rx && x < (rx + rw) && y >= ry && y < (ry + rh));
};
/**
* Determines whether the specified point is contained within the rectangular region defined by this Rectangle object. This method is similar to the Rectangle.contains() method, except that it takes a Point object as a parameter.
* @method Phaser.Rectangle.containsPoint
* @param {Phaser.Rectangle} a - The Rectangle object.
* @param {Phaser.Point} point - The point object being checked. Can be Point or any object with .x and .y values.
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false.
*/
Phaser.Rectangle.containsPoint = function (a, point) {
return Phaser.Rectangle.contains(a, point.x, point.y);
};
/**
* Determines whether the first Rectangle object is fully contained within the second Rectangle object.
* A Rectangle object is said to contain another if the second Rectangle object falls entirely within the boundaries of the first.
* @method Phaser.Rectangle.containsRect
* @param {Phaser.Rectangle} a - The first Rectangle object.
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @return {boolean} A value of true if the Rectangle object contains the specified point; otherwise false.
*/
Phaser.Rectangle.containsRect = function (a, b) {
// If the given rect has a larger volume than this one then it can never contain it
if (a.volume > b.volume)
{
return false;
}
return (a.x >= b.x && a.y >= b.y && a.right < b.right && a.bottom < b.bottom);
};
/**
* Determines whether the two Rectangles are equal.
* This method compares the x, y, width and height properties of each Rectangle.
* @method Phaser.Rectangle.equals
* @param {Phaser.Rectangle} a - The first Rectangle object.
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @return {boolean} A value of true if the two Rectangles have exactly the same values for the x, y, width and height properties; otherwise false.
*/
Phaser.Rectangle.equals = function (a, b) {
return (a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height);
};
/**
* Determines if the two objects (either Rectangles or Rectangle-like) have the same width and height values under strict equality.
* @method Phaser.Rectangle.sameDimensions
* @param {Rectangle-like} a - The first Rectangle object.
* @param {Rectangle-like} b - The second Rectangle object.
* @return {boolean} True if the object have equivalent values for the width and height properties.
*/
Phaser.Rectangle.sameDimensions = function (a, b) {
return (a.width === b.width && a.height === b.height);
};
/**
* If the Rectangle object specified in the toIntersect parameter intersects with this Rectangle object, returns the area of intersection as a Rectangle object. If the Rectangles do not intersect, this method returns an empty Rectangle object with its properties set to 0.
* @method Phaser.Rectangle.intersection
* @param {Phaser.Rectangle} a - The first Rectangle object.
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the intersection values will be set into this object, otherwise a brand new Rectangle object will be created and returned.
* @return {Phaser.Rectangle} A Rectangle object that equals the area of intersection. If the Rectangles do not intersect, this method returns an empty Rectangle object; that is, a Rectangle with its x, y, width, and height properties set to 0.
*/
Phaser.Rectangle.intersection = function (a, b, output) {
if (output === undefined)
{
output = new Phaser.Rectangle();
}
if (Phaser.Rectangle.intersects(a, b))
{
output.x = Math.max(a.x, b.x);
output.y = Math.max(a.y, b.y);
output.width = Math.min(a.right, b.right) - output.x;
output.height = Math.min(a.bottom, b.bottom) - output.y;
}
return output;
};
/**
* Determines whether the two Rectangles intersect with each other.
* This method checks the x, y, width, and height properties of the Rectangles.
* @method Phaser.Rectangle.intersects
* @param {Phaser.Rectangle} a - The first Rectangle object.
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @return {boolean} A value of true if the specified object intersects with this Rectangle object; otherwise false.
*/
Phaser.Rectangle.intersects = function (a, b) {
if (a.width <= 0 || a.height <= 0 || b.width <= 0 || b.height <= 0)
{
return false;
}
return !(a.right < b.x || a.bottom < b.y || a.x > b.right || a.y > b.bottom);
};
/**
* Determines whether the object specified intersects (overlaps) with the given values.
* @method Phaser.Rectangle.intersectsRaw
* @param {number} left - The x coordinate of the left of the area.
* @param {number} right - The right coordinate of the area.
* @param {number} top - The y coordinate of the area.
* @param {number} bottom - The bottom coordinate of the area.
* @param {number} tolerance - A tolerance value to allow for an intersection test with padding, default to 0
* @return {boolean} A value of true if the specified object intersects with the Rectangle; otherwise false.
*/
Phaser.Rectangle.intersectsRaw = function (a, left, right, top, bottom, tolerance) {
if (tolerance === undefined) { tolerance = 0; }
return !(left > a.right + tolerance || right < a.left - tolerance || top > a.bottom + tolerance || bottom < a.top - tolerance);
};
/**
* Adds two Rectangles together to create a new Rectangle object, by filling in the horizontal and vertical space between the two Rectangles.
* @method Phaser.Rectangle.union
* @param {Phaser.Rectangle} a - The first Rectangle object.
* @param {Phaser.Rectangle} b - The second Rectangle object.
* @param {Phaser.Rectangle} [output] - Optional Rectangle object. If given the new values will be set into this object, otherwise a brand new Rectangle object will be created and returned.
* @return {Phaser.Rectangle} A Rectangle object that is the union of the two Rectangles.
*/
Phaser.Rectangle.union = function (a, b, output) {
if (output === undefined)
{
output = new Phaser.Rectangle();
}
return output.setTo(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.max(a.right, b.right) - Math.min(a.left, b.left), Math.max(a.bottom, b.bottom) - Math.min(a.top, b.top));
};
/**
* Calculates the Axis Aligned Bounding Box (or aabb) from an array of points.
*
* @method Phaser.Rectangle#aabb
* @param {Phaser.Point[]} points - The array of one or more points.
* @param {Phaser.Rectangle} [out] - Optional Rectangle to store the value in, if not supplied a new Rectangle object will be created.
* @return {Phaser.Rectangle} The new Rectangle object.
* @static
*/
Phaser.Rectangle.aabb = function(points, out) {
if (out === undefined) {
out = new Phaser.Rectangle();
}
var xMax = Number.NEGATIVE_INFINITY,
xMin = Number.POSITIVE_INFINITY,
yMax = Number.NEGATIVE_INFINITY,
yMin = Number.POSITIVE_INFINITY;
points.forEach(function(point) {
if (point.x > xMax) {
xMax = point.x;
}
if (point.x < xMin) {
xMin = point.x;
}
if (point.y > yMax) {
yMax = point.y;
}
if (point.y < yMin) {
yMin = point.y;
}
});
out.setTo(xMin, yMin, xMax - xMin, yMax - yMin);
return out;
};
// Because PIXI uses its own Rectangle, we'll replace it with ours to avoid duplicating code or confusion.
PIXI.Rectangle = Phaser.Rectangle;
PIXI.EmptyRectangle = new Phaser.Rectangle(0, 0, 0, 0);
/**
* @author Mat Groves http://matgroves.com/
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Rounded Rectangle object is an area defined by its position and has nice rounded corners,
* as indicated by its top-left corner point (x, y) and by its width and its height.
*
* @class Phaser.RoundedRectangle
* @constructor
* @param {number} [x=0] - The x coordinate of the top-left corner of the Rectangle.
* @param {number} [y=0] - The y coordinate of the top-left corner of the Rectangle.
* @param {number} [width=0] - The width of the Rectangle. Should always be either zero or a positive value.
* @param {number} [height=0] - The height of the Rectangle. Should always be either zero or a positive value.
* @param {number} [radius=20] - Controls the radius of the rounded corners.
*/
Phaser.RoundedRectangle = function(x, y, width, height, radius)
{
if (x === undefined) { x = 0; }
if (y === undefined) { y = 0; }
if (width === undefined) { width = 0; }
if (height === undefined) { height = 0; }
if (radius === undefined) { radius = 20; }
/**
* @property {number} x - The x coordinate of the top-left corner of the Rectangle.
*/
this.x = x;
/**
* @property {number} y - The y coordinate of the top-left corner of the Rectangle.
*/
this.y = y;
/**
* @property {number} width - The width of the Rectangle. This value should never be set to a negative.
*/
this.width = width;
/**
* @property {number} height - The height of the Rectangle. This value should never be set to a negative.
*/
this.height = height;
/**
* @property {number} radius - The radius of the rounded corners.
*/
this.radius = radius || 20;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.ROUNDEDRECTANGLE;
};
Phaser.RoundedRectangle.prototype = {
/**
* Returns a new RoundedRectangle object with the same values for the x, y, width, height and
* radius properties as this RoundedRectangle object.
*
* @method Phaser.RoundedRectangle#clone
* @return {Phaser.RoundedRectangle}
*/
clone: function () {
return new Phaser.RoundedRectangle(this.x, this.y, this.width, this.height, this.radius);
},
/**
* Determines whether the specified coordinates are contained within the region defined by this Rounded Rectangle object.
*
* @method Phaser.RoundedRectangle#contains
* @param {number} x - The x coordinate of the point to test.
* @param {number} y - The y coordinate of the point to test.
* @return {boolean} A value of true if the RoundedRectangle Rectangle object contains the specified point; otherwise false.
*/
contains: function (x, y) {
if (this.width <= 0 || this.height <= 0)
{
return false;
}
var x1 = this.x;
if (x >= x1 && x <= x1 + this.width)
{
var y1 = this.y;
if (y >= y1 && y <= y1 + this.height)
{
return true;
}
}
return false;
}
};
Phaser.RoundedRectangle.prototype.constructor = Phaser.RoundedRectangle;
// Because PIXI uses its own type, we'll replace it with ours to avoid duplicating code or confusion.
PIXI.RoundedRectangle = Phaser.RoundedRectangle;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Camera is your view into the game world. It has a position and size and renders only those objects within its field of view.
* The game automatically creates a single Stage sized camera on boot. Move the camera around the world with Phaser.Camera.x/y
*
* @class Phaser.Camera
* @constructor
* @param {Phaser.Game} game - Game reference to the currently running game.
* @param {number} id - Not being used at the moment, will be when Phaser supports multiple camera
* @param {number} x - Position of the camera on the X axis
* @param {number} y - Position of the camera on the Y axis
* @param {number} width - The width of the view rectangle
* @param {number} height - The height of the view rectangle
*/
Phaser.Camera = function (game, id, x, y, width, height) {
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
/**
* @property {Phaser.World} world - A reference to the game world.
*/
this.world = game.world;
/**
* @property {number} id - Reserved for future multiple camera set-ups.
* @default
*/
this.id = 0;
/**
* Camera view.
* The view into the world we wish to render (by default the game dimensions).
* The x/y values are in world coordinates, not screen coordinates, the width/height is how many pixels to render.
* Sprites outside of this view are not rendered if Sprite.autoCull is set to `true`. Otherwise they are always rendered.
* @property {Phaser.Rectangle} view
*/
this.view = new Phaser.Rectangle(x, y, width, height);
/**
* The Camera is bound to this Rectangle and cannot move outside of it. By default it is enabled and set to the size of the World.
* The Rectangle can be located anywhere in the world and updated as often as you like. If you don't wish the Camera to be bound
* at all then set this to null. The values can be anything and are in World coordinates, with 0,0 being the top-left of the world.
*
* @property {Phaser.Rectangle} bounds - The Rectangle in which the Camera is bounded. Set to null to allow for movement anywhere.
*/
this.bounds = new Phaser.Rectangle(x, y, width, height);
/**
* @property {Phaser.Rectangle} deadzone - Moving inside this Rectangle will not cause the camera to move.
*/
this.deadzone = null;
/**
* @property {boolean} visible - Whether this camera is visible or not.
* @default
*/
this.visible = true;
/**
* @property {boolean} roundPx - If a Camera has roundPx set to `true` it will call `view.floor` as part of its update loop, keeping its boundary to integer values. Set this to `false` to disable this from happening.
* @default
*/
this.roundPx = true;
/**
* @property {boolean} atLimit - Whether this camera is flush with the World Bounds or not.
*/
this.atLimit = { x: false, y: false };
/**
* @property {Phaser.Sprite} target - If the camera is tracking a Sprite, this is a reference to it, otherwise null.
* @default
*/
this.target = null;
/**
* @property {PIXI.DisplayObject} displayObject - The display object to which all game objects are added. Set by World.boot.
*/
this.displayObject = null;
/**
* @property {Phaser.Point} scale - The scale of the display object to which all game objects are added. Set by World.boot.
*/
this.scale = null;
/**
* @property {number} totalInView - The total number of Sprites with `autoCull` set to `true` that are visible by this Camera.
* @readonly
*/
this.totalInView = 0;
/**
* The linear interpolation value to use when following a target.
* The default values of 1 means the camera will instantly snap to the target coordinates.
* A lower value, such as 0.1 means the camera will more slowly track the target, giving
* a smooth transition. You can set the horizontal and vertical values independently, and also
* adjust this value in real-time during your game.
* @property {Phaser.Point} lerp
* @default
*/
this.lerp = new Phaser.Point(1, 1);
/**
* @property {Phaser.Signal} onShakeComplete - This signal is dispatched when the camera shake effect completes.
*/
this.onShakeComplete = new Phaser.Signal();
/**
* @property {Phaser.Signal} onFlashComplete - This signal is dispatched when the camera flash effect completes.
*/
this.onFlashComplete = new Phaser.Signal();
/**
* This signal is dispatched when the camera fade effect completes.
* When the fade effect completes you will be left with the screen black (or whatever
* color you faded to). In order to reset this call `Camera.resetFX`. This is called
* automatically when you change State.
* @property {Phaser.Signal} onFadeComplete
*/
this.onFadeComplete = new Phaser.Signal();
/**
* The Graphics object used to handle camera fx such as fade and flash.
* @property {Phaser.Graphics} fx
* @protected
*/
this.fx = null;
/**
* @property {Phaser.Point} _targetPosition - Internal point used to calculate target position.
* @private
*/
this._targetPosition = new Phaser.Point();
/**
* @property {number} edge - Edge property.
* @private
* @default
*/
this._edge = 0;
/**
* @property {Phaser.Point} position - Current position of the camera in world.
* @private
* @default
*/
this._position = new Phaser.Point();
/**
* @property {Object} _shake - The shake effect container.
* @private
*/
this._shake = {
intensity: 0,
duration: 0,
horizontal: false,
vertical: false,
shakeBounds: true,
x: 0,
y: 0
};
/**
* @property {number} _fxDuration - FX duration timer.
* @private
*/
this._fxDuration = 0;
/**
* @property {number} _fxType - The FX type running.
* @private
*/
this._fxType = 0;
};
/**
* @constant
* @type {number}
*/
Phaser.Camera.FOLLOW_LOCKON = 0;
/**
* @constant
* @type {number}
*/
Phaser.Camera.FOLLOW_PLATFORMER = 1;
/**
* @constant
* @type {number}
*/
Phaser.Camera.FOLLOW_TOPDOWN = 2;
/**
* @constant
* @type {number}
*/
Phaser.Camera.FOLLOW_TOPDOWN_TIGHT = 3;
/**
* @constant
* @type {number}
*/
Phaser.Camera.SHAKE_BOTH = 4;
/**
* @constant
* @type {number}
*/
Phaser.Camera.SHAKE_HORIZONTAL = 5;
/**
* @constant
* @type {number}
*/
Phaser.Camera.SHAKE_VERTICAL = 6;
/**
* @constant
* @type {boolean}
*/
Phaser.Camera.ENABLE_FX = true;
Phaser.Camera.prototype = {
/**
* Called automatically by Phaser.World.
*
* @method Phaser.Camera#boot
* @private
*/
boot: function () {
this.displayObject = this.game.world;
this.scale = this.game.world.scale;
this.game.camera = this;
if (Phaser.Graphics && Phaser.Camera.ENABLE_FX)
{
this.fx = new Phaser.Graphics(this.game);
this.game.stage.addChild(this.fx);
}
},
/**
* Camera preUpdate. Sets the total view counter to zero.
*
* @method Phaser.Camera#preUpdate
*/
preUpdate: function () {
this.totalInView = 0;
},
/**
* Tell the camera which sprite to follow.
*
* You can set the follow type and a linear interpolation value.
* Use low lerp values (such as 0.1) to automatically smooth the camera motion.
*
* If you find you're getting a slight "jitter" effect when following a Sprite it's probably to do with sub-pixel rendering of the Sprite position.
* This can be disabled by setting `game.renderer.renderSession.roundPixels = true` to force full pixel rendering.
*
* @method Phaser.Camera#follow
* @param {Phaser.Sprite|Phaser.Image|Phaser.Text} target - The object you want the camera to track. Set to null to not follow anything.
* @param {number} [style] - Leverage one of the existing "deadzone" presets. If you use a custom deadzone, ignore this parameter and manually specify the deadzone after calling follow().
* @param {float} [lerpX=1] - A value between 0 and 1. This value specifies the amount of linear interpolation to use when horizontally tracking the target. The closer the value to 1, the faster the camera will track.
* @param {float} [lerpY=1] - A value between 0 and 1. This value specifies the amount of linear interpolation to use when vertically tracking the target. The closer the value to 1, the faster the camera will track.
*/
follow: function (target, style, lerpX, lerpY) {
if (style === undefined) { style = Phaser.Camera.FOLLOW_LOCKON; }
if (lerpX === undefined) { lerpX = 1; }
if (lerpY === undefined) { lerpY = 1; }
this.target = target;
this.lerp.set(lerpX, lerpY);
var helper;
switch (style) {
case Phaser.Camera.FOLLOW_PLATFORMER:
var w = this.width / 8;
var h = this.height / 3;
this.deadzone = new Phaser.Rectangle((this.width - w) / 2, (this.height - h) / 2 - h * 0.25, w, h);
break;
case Phaser.Camera.FOLLOW_TOPDOWN:
helper = Math.max(this.width, this.height) / 4;
this.deadzone = new Phaser.Rectangle((this.width - helper) / 2, (this.height - helper) / 2, helper, helper);
break;
case Phaser.Camera.FOLLOW_TOPDOWN_TIGHT:
helper = Math.max(this.width, this.height) / 8;
this.deadzone = new Phaser.Rectangle((this.width - helper) / 2, (this.height - helper) / 2, helper, helper);
break;
case Phaser.Camera.FOLLOW_LOCKON:
this.deadzone = null;
break;
default:
this.deadzone = null;
break;
}
},
/**
* Sets the Camera follow target to null, stopping it from following an object if it's doing so.
*
* @method Phaser.Camera#unfollow
*/
unfollow: function () {
this.target = null;
},
/**
* Move the camera focus on a display object instantly.
* @method Phaser.Camera#focusOn
* @param {any} displayObject - The display object to focus the camera on. Must have visible x/y properties.
*/
focusOn: function (displayObject) {
this.setPosition(Math.round(displayObject.x - this.view.halfWidth), Math.round(displayObject.y - this.view.halfHeight));
},
/**
* Move the camera focus on a location instantly.
* @method Phaser.Camera#focusOnXY
* @param {number} x - X position.
* @param {number} y - Y position.
*/
focusOnXY: function (x, y) {
this.setPosition(Math.round(x - this.view.halfWidth), Math.round(y - this.view.halfHeight));
},
/**
* This creates a camera shake effect. It works by applying a random amount of additional
* spacing on the x and y axis each frame. You can control the intensity and duration
* of the effect, and if it should effect both axis or just one.
*
* When the shake effect ends the signal Camera.onShakeComplete is dispatched.
*
* @method Phaser.Camera#shake
* @param {float} [intensity=0.05] - The intensity of the camera shake. Given as a percentage of the camera size representing the maximum distance that the camera can move while shaking.
* @param {number} [duration=500] - The duration of the shake effect in milliseconds.
* @param {boolean} [force=true] - If a camera shake effect is already running and force is true it will replace the previous effect, resetting the duration.
* @param {number} [direction=Phaser.Camera.SHAKE_BOTH] - The directions in which the camera can shake. Either Phaser.Camera.SHAKE_BOTH, Phaser.Camera.SHAKE_HORIZONTAL or Phaser.Camera.SHAKE_VERTICAL.
* @param {boolean} [shakeBounds=true] - Is the effect allowed to shake the camera beyond its bounds (if set?).
* @return {boolean} True if the shake effect was started, otherwise false.
*/
shake: function (intensity, duration, force, direction, shakeBounds) {
if (intensity === undefined) { intensity = 0.05; }
if (duration === undefined) { duration = 500; }
if (force === undefined) { force = true; }
if (direction === undefined) { direction = Phaser.Camera.SHAKE_BOTH; }
if (shakeBounds === undefined) { shakeBounds = true; }
if (!force && this._shake.duration > 0)
{
// Can't reset an already running shake
return false;
}
this._shake.intensity = intensity;
this._shake.duration = duration;
this._shake.shakeBounds = shakeBounds;
this._shake.x = 0;
this._shake.y = 0;
this._shake.horizontal = (direction === Phaser.Camera.SHAKE_BOTH || direction === Phaser.Camera.SHAKE_HORIZONTAL);
this._shake.vertical = (direction === Phaser.Camera.SHAKE_BOTH || direction === Phaser.Camera.SHAKE_VERTICAL);
return true;
},
/**
* This creates a camera flash effect. It works by filling the game with the solid fill
* color specified, and then fading it away to alpha 0 over the duration given.
*
* You can use this for things such as hit feedback effects.
*
* When the effect ends the signal Camera.onFlashComplete is dispatched.
*
* @method Phaser.Camera#flash
* @param {numer} [color=0xffffff] - The color of the flash effect. I.e. 0xffffff for white, 0xff0000 for red, etc.
* @param {number} [duration=500] - The duration of the flash effect in milliseconds.
* @param {boolean} [force=false] - If a camera flash or fade effect is already running and force is true it will replace the previous effect, resetting the duration.
* @param {numer} [alpha=1] - The alpha value of the color applied to the flash effect.
* @return {boolean} True if the effect was started, otherwise false.
*/
flash: function (color, duration, force, alpha) {
if (color === undefined) { color = 0xffffff; }
if (duration === undefined) { duration = 500; }
if (force === undefined) { force = false; }
if (alpha === undefined) { alpha = 1; }
if (!this.fx || (!force && this._fxDuration > 0))
{
return false;
}
this.fx.clear();
this.fx.beginFill(color, alpha);
this.fx.drawRect(0, 0, this.width, this.height);
this.fx.endFill();
this.fx.alpha = 1;
this._fxDuration = duration;
this._fxType = 0;
return true;
},
/**
* This creates a camera fade effect. It works by filling the game with the
* color specified, over the duration given, ending with a solid fill.
*
* You can use this for things such as transitioning to a new scene.
*
* The game will be left 'filled' at the end of this effect, likely obscuring
* everything. In order to reset it you can call `Camera.resetFX` and it will clear the
* fade. Or you can call `Camera.flash` with the same color as the fade, and it will
* reverse the process, bringing the game back into view again.
*
* When the effect ends the signal Camera.onFadeComplete is dispatched.
*
* @method Phaser.Camera#fade
* @param {numer} [color=0x000000] - The color the game will fade to. I.e. 0x000000 for black, 0xff0000 for red, etc.
* @param {number} [duration=500] - The duration of the fade in milliseconds.
* @param {boolean} [force=false] - If a camera flash or fade effect is already running and force is true it will replace the previous effect, resetting the duration.
* @param {numer} [alpha=1] - The alpha value of the color applied to the fade effect.
* @return {boolean} True if the effect was started, otherwise false.
*/
fade: function (color, duration, force, alpha) {
if (color === undefined) { color = 0x000000; }
if (duration === undefined) { duration = 500; }
if (force === undefined) { force = false; }
if (alpha === undefined) { alpha = 1; }
if (!this.fx || (!force && this._fxDuration > 0))
{
return false;
}
this.fx.clear();
this.fx.beginFill(color, alpha);
this.fx.drawRect(0, 0, this.width, this.height);
this.fx.endFill();
this.fx.alpha = 0;
this._fxDuration = duration;
this._fxType = 1;
return true;
},
/**
* The camera update loop. This is called automatically by the core game loop.
*
* @method Phaser.Camera#update
* @protected
*/
update: function () {
if (this._fxDuration > 0)
{
this.updateFX();
}
if (this._shake.duration > 0)
{
this.updateShake();
}
if (this.bounds)
{
this.checkBounds();
}
if (this.roundPx)
{
this.view.floor();
this._shake.x = Math.floor(this._shake.x);
this._shake.y = Math.floor(this._shake.y);
}
this.displayObject.position.x = -this.view.x;
this.displayObject.position.y = -this.view.y;
},
/**
* Update the camera flash and fade effects.
*
* @method Phaser.Camera#updateFX
* @private
*/
updateFX: function () {
if (this._fxType === 0)
{
// flash
this.fx.alpha -= this.game.time.elapsedMS / this._fxDuration;
if (this.fx.alpha <= 0)
{
this._fxDuration = 0;
this.fx.alpha = 0;
this.onFlashComplete.dispatch();
}
}
else
{
// fade
this.fx.alpha += this.game.time.elapsedMS / this._fxDuration;
if (this.fx.alpha >= 1)
{
this._fxDuration = 0;
this.fx.alpha = 1;
this.onFadeComplete.dispatch();
}
}
},
/**
* Update the camera shake effect.
*
* @method Phaser.Camera#updateShake
* @private
*/
updateShake: function () {
this._shake.duration -= this.game.time.elapsedMS;
if (this._shake.duration <= 0)
{
this.onShakeComplete.dispatch();
this._shake.x = 0;
this._shake.y = 0;
}
else
{
if (this._shake.horizontal)
{
this._shake.x = this.game.rnd.frac() * this._shake.intensity * this.view.width * 2 - this._shake.intensity * this.view.width;
}
if (this._shake.vertical)
{
this._shake.y = this.game.rnd.frac() * this._shake.intensity * this.view.height * 2 - this._shake.intensity * this.view.height;
}
}
},
/**
* Internal method that handles tracking a sprite.
*
* @method Phaser.Camera#updateTarget
* @private
*/
updateTarget: function () {
this._targetPosition.x = this.view.x + this.target.worldPosition.x;
this._targetPosition.y = this.view.y + this.target.worldPosition.y;
if (this.deadzone)
{
this._edge = this._targetPosition.x - this.view.x;
if (this._edge < this.deadzone.left)
{
this.view.x = this.game.math.linear(this.view.x, this._targetPosition.x - this.deadzone.left, this.lerp.x);
}
else if (this._edge > this.deadzone.right)
{
this.view.x = this.game.math.linear(this.view.x, this._targetPosition.x - this.deadzone.right, this.lerp.x);
}
this._edge = this._targetPosition.y - this.view.y;
if (this._edge < this.deadzone.top)
{
this.view.y = this.game.math.linear(this.view.y, this._targetPosition.y - this.deadzone.top, this.lerp.y);
}
else if (this._edge > this.deadzone.bottom)
{
this.view.y = this.game.math.linear(this.view.y, this._targetPosition.y - this.deadzone.bottom, this.lerp.y);
}
}
else
{
this.view.x = this.game.math.linear(this.view.x, this._targetPosition.x - this.view.halfWidth, this.lerp.x);
this.view.y = this.game.math.linear(this.view.y, this._targetPosition.y - this.view.halfHeight, this.lerp.y);
}
if (this.bounds)
{
this.checkBounds();
}
if (this.roundPx)
{
this.view.floor();
}
this.displayObject.position.x = -this.view.x;
this.displayObject.position.y = -this.view.y;
},
/**
* Update the Camera bounds to match the game world.
*
* @method Phaser.Camera#setBoundsToWorld
*/
setBoundsToWorld: function () {
if (this.bounds)
{
this.bounds.copyFrom(this.game.world.bounds);
}
},
/**
* Method called to ensure the camera doesn't venture outside of the game world.
* Called automatically by Camera.update.
*
* @method Phaser.Camera#checkBounds
* @protected
*/
checkBounds: function () {
this.atLimit.x = false;
this.atLimit.y = false;
var vx = this.view.x + this._shake.x;
var vw = this.view.right + this._shake.x;
var vy = this.view.y + this._shake.y;
var vh = this.view.bottom + this._shake.y;
// Make sure we didn't go outside the cameras bounds
if (vx <= this.bounds.x * this.scale.x)
{
this.atLimit.x = true;
this.view.x = this.bounds.x * this.scale.x;
if (!this._shake.shakeBounds)
{
// The camera is up against the bounds, so reset the shake
this._shake.x = 0;
}
}
if (vw >= this.bounds.right * this.scale.x)
{
this.atLimit.x = true;
this.view.x = (this.bounds.right * this.scale.x) - this.width;
if (!this._shake.shakeBounds)
{
// The camera is up against the bounds, so reset the shake
this._shake.x = 0;
}
}
if (vy <= this.bounds.top * this.scale.y)
{
this.atLimit.y = true;
this.view.y = this.bounds.top * this.scale.y;
if (!this._shake.shakeBounds)
{
// The camera is up against the bounds, so reset the shake
this._shake.y = 0;
}
}
if (vh >= this.bounds.bottom * this.scale.y)
{
this.atLimit.y = true;
this.view.y = (this.bounds.bottom * this.scale.y) - this.height;
if (!this._shake.shakeBounds)
{
// The camera is up against the bounds, so reset the shake
this._shake.y = 0;
}
}
},
/**
* A helper function to set both the X and Y properties of the camera at once
* without having to use game.camera.x and game.camera.y.
*
* @method Phaser.Camera#setPosition
* @param {number} x - X position.
* @param {number} y - Y position.
*/
setPosition: function (x, y) {
this.view.x = x;
this.view.y = y;
if (this.bounds)
{
this.checkBounds();
}
},
/**
* Sets the size of the view rectangle given the width and height in parameters.
*
* @method Phaser.Camera#setSize
* @param {number} width - The desired width.
* @param {number} height - The desired height.
*/
setSize: function (width, height) {
this.view.width = width;
this.view.height = height;
},
/**
* Resets the camera back to 0,0 and un-follows any object it may have been tracking.
* Also immediately resets any camera effects that may have been running such as
* shake, flash or fade.
*
* @method Phaser.Camera#reset
*/
reset: function () {
this.target = null;
this.view.x = 0;
this.view.y = 0;
this._shake.duration = 0;
this.resetFX();
},
/**
* Resets any active FX, such as a fade or flash and immediately clears it.
* Useful to calling after a fade in order to remove the fade from the Stage.
*
* @method Phaser.Camera#resetFX
*/
resetFX: function () {
if (this.fx)
{
this.fx.clear();
this.fx.alpha = 0;
}
this._fxDuration = 0;
}
};
Phaser.Camera.prototype.constructor = Phaser.Camera;
/**
* The Cameras x coordinate. This value is automatically clamped if it falls outside of the World bounds.
* @name Phaser.Camera#x
* @property {number} x - Gets or sets the cameras x position.
*/
Object.defineProperty(Phaser.Camera.prototype, "x", {
get: function () {
return this.view.x;
},
set: function (value) {
this.view.x = value;
if (this.bounds)
{
this.checkBounds();
}
}
});
/**
* The Cameras y coordinate. This value is automatically clamped if it falls outside of the World bounds.
* @name Phaser.Camera#y
* @property {number} y - Gets or sets the cameras y position.
*/
Object.defineProperty(Phaser.Camera.prototype, "y", {
get: function () {
return this.view.y;
},
set: function (value) {
this.view.y = value;
if (this.bounds)
{
this.checkBounds();
}
}
});
/**
* The Cameras position. This value is automatically clamped if it falls outside of the World bounds.
* @name Phaser.Camera#position
* @property {Phaser.Point} position - Gets or sets the cameras xy position using Phaser.Point object.
*/
Object.defineProperty(Phaser.Camera.prototype, "position", {
get: function () {
this._position.set(this.view.x, this.view.y);
return this._position;
},
set: function (value) {
if (typeof value.x !== "undefined") { this.view.x = value.x; }
if (typeof value.y !== "undefined") { this.view.y = value.y; }
if (this.bounds)
{
this.checkBounds();
}
}
});
/**
* The Cameras width. By default this is the same as the Game size and should not be adjusted for now.
* @name Phaser.Camera#width
* @property {number} width - Gets or sets the cameras width.
*/
Object.defineProperty(Phaser.Camera.prototype, "width", {
get: function () {
return this.view.width;
},
set: function (value) {
this.view.width = value;
}
});
/**
* The Cameras height. By default this is the same as the Game size and should not be adjusted for now.
* @name Phaser.Camera#height
* @property {number} height - Gets or sets the cameras height.
*/
Object.defineProperty(Phaser.Camera.prototype, "height", {
get: function () {
return this.view.height;
},
set: function (value) {
this.view.height = value;
}
});
/**
* The Cameras shake intensity.
* @name Phaser.Camera#shakeIntensity
* @property {number} shakeIntensity - Gets or sets the cameras shake intensity.
*/
Object.defineProperty(Phaser.Camera.prototype, "shakeIntensity", {
get: function () {
return this._shake.intensity;
},
set: function (value) {
this._shake.intensity = value;
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is a base State class which can be extended if you are creating your own game.
* It provides quick access to common functions such as the camera, cache, input, match, sound and more.
*
* @class Phaser.State
* @constructor
*/
Phaser.State = function () {
/**
* @property {Phaser.Game} game - This is a reference to the currently running Game.
*/
this.game = null;
/**
* @property {string} key - The string based identifier given to the State when added into the State Manager.
*/
this.key = '';
/**
* @property {Phaser.GameObjectFactory} add - A reference to the GameObjectFactory which can be used to add new objects to the World.
*/
this.add = null;
/**
* @property {Phaser.GameObjectCreator} make - A reference to the GameObjectCreator which can be used to make new objects.
*/
this.make = null;
/**
* @property {Phaser.Camera} camera - A handy reference to World.camera.
*/
this.camera = null;
/**
* @property {Phaser.Cache} cache - A reference to the game cache which contains any loaded or generated assets, such as images, sound and more.
*/
this.cache = null;
/**
* @property {Phaser.Input} input - A reference to the Input Manager.
*/
this.input = null;
/**
* @property {Phaser.Loader} load - A reference to the Loader, which you mostly use in the preload method of your state to load external assets.
*/
this.load = null;
/**
* @property {Phaser.Math} math - A reference to Math class with lots of helpful functions.
*/
this.math = null;
/**
* @property {Phaser.SoundManager} sound - A reference to the Sound Manager which can create, play and stop sounds, as well as adjust global volume.
*/
this.sound = null;
/**
* @property {Phaser.ScaleManager} scale - A reference to the Scale Manager which controls the way the game scales on different displays.
*/
this.scale = null;
/**
* @property {Phaser.Stage} stage - A reference to the Stage.
*/
this.stage = null;
/**
* @property {Phaser.StateManager} stage - A reference to the State Manager, which controls state changes.
*/
this.state = null;
/**
* @property {Phaser.Time} time - A reference to the game clock and timed events system.
*/
this.time = null;
/**
* @property {Phaser.TweenManager} tweens - A reference to the tween manager.
*/
this.tweens = null;
/**
* @property {Phaser.World} world - A reference to the game world. All objects live in the Game World and its size is not bound by the display resolution.
*/
this.world = null;
/**
* @property {Phaser.Particles} particles - The Particle Manager. It is called during the core gameloop and updates any Particle Emitters it has created.
*/
this.particles = null;
/**
* @property {Phaser.Physics} physics - A reference to the physics manager which looks after the different physics systems available within Phaser.
*/
this.physics = null;
/**
* @property {Phaser.RandomDataGenerator} rnd - A reference to the seeded and repeatable random data generator.
*/
this.rnd = null;
};
Phaser.State.prototype = {
/**
* init is the very first function called when your State starts up. It's called before preload, create or anything else.
* If you need to route the game away to another State you could do so here, or if you need to prepare a set of variables
* or objects before the preloading starts.
*
* @method Phaser.State#init
*/
init: function () {
},
/**
* preload is called first. Normally you'd use this to load your game assets (or those needed for the current State)
* You shouldn't create any objects in this method that require assets that you're also loading in this method, as
* they won't yet be available.
*
* @method Phaser.State#preload
*/
preload: function () {
},
/**
* loadUpdate is called during the Loader process. This only happens if you've set one or more assets to load in the preload method.
*
* @method Phaser.State#loadUpdate
*/
loadUpdate: function () {
},
/**
* loadRender is called during the Loader process. This only happens if you've set one or more assets to load in the preload method.
* The difference between loadRender and render is that any objects you render in this method you must be sure their assets exist.
*
* @method Phaser.State#loadRender
*/
loadRender: function () {
},
/**
* create is called once preload has completed, this includes the loading of any assets from the Loader.
* If you don't have a preload method then create is the first method called in your State.
*
* @method Phaser.State#create
*/
create: function () {
},
/**
* The update method is left empty for your own use.
* It is called during the core game loop AFTER debug, physics, plugins and the Stage have had their preUpdate methods called.
* It is called BEFORE Stage, Tweens, Sounds, Input, Physics, Particles and Plugins have had their postUpdate methods called.
*
* @method Phaser.State#update
*/
update: function () {
},
/**
* The preRender method is called after all Game Objects have been updated, but before any rendering takes place.
*
* @method Phaser.State#preRender
*/
preRender: function () {
},
/**
* Nearly all display objects in Phaser render automatically, you don't need to tell them to render.
* However the render method is called AFTER the game renderer and plugins have rendered, so you're able to do any
* final post-processing style effects here. Note that this happens before plugins postRender takes place.
*
* @method Phaser.State#render
*/
render: function () {
},
/**
* If your game is set to Scalemode RESIZE then each time the browser resizes it will call this function, passing in the new width and height.
*
* @method Phaser.State#resize
*/
resize: function () {
},
/**
* This method will be called if the core game loop is paused.
*
* @method Phaser.State#paused
*/
paused: function () {
},
/**
* This method will be called when the core game loop resumes from a paused state.
*
* @method Phaser.State#resumed
*/
resumed: function () {
},
/**
* pauseUpdate is called while the game is paused instead of preUpdate, update and postUpdate.
*
* @method Phaser.State#pauseUpdate
*/
pauseUpdate: function () {
},
/**
* This method will be called when the State is shutdown (i.e. you switch to another state from this one).
*
* @method Phaser.State#shutdown
*/
shutdown: function () {
}
};
Phaser.State.prototype.constructor = Phaser.State;
/* jshint newcap: false */
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The State Manager is responsible for loading, setting up and switching game states.
*
* @class Phaser.StateManager
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {Phaser.State|Object} [pendingState=null] - A State object to seed the manager with.
*/
Phaser.StateManager = function (game, pendingState) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {object} states - The object containing Phaser.States.
*/
this.states = {};
/**
* @property {Phaser.State} _pendingState - The state to be switched to in the next frame.
* @private
*/
this._pendingState = null;
if (typeof pendingState !== 'undefined' && pendingState !== null)
{
this._pendingState = pendingState;
}
/**
* @property {boolean} _clearWorld - Clear the world when we switch state?
* @private
*/
this._clearWorld = false;
/**
* @property {boolean} _clearCache - Clear the cache when we switch state?
* @private
*/
this._clearCache = false;
/**
* @property {boolean} _created - Flag that sets if the State has been created or not.
* @private
*/
this._created = false;
/**
* @property {any[]} _args - Temporary container when you pass vars from one State to another.
* @private
*/
this._args = [];
/**
* @property {string} current - The current active State object.
* @default
*/
this.current = '';
/**
* onStateChange is a Phaser.Signal that is dispatched whenever the game changes state.
*
* It is dispatched only when the new state is started, which isn't usually at the same time as StateManager.start
* is called because state swapping is done in sync with the game loop. It is dispatched *before* any of the new states
* methods (such as preload and create) are called, and *after* the previous states shutdown method has been run.
*
* The callback you specify is sent two parameters: the string based key of the new state,
* and the second parameter is the string based key of the old / previous state.
*
* @property {Phaser.Signal} onStateChange
*/
this.onStateChange = new Phaser.Signal();
/**
* @property {function} onInitCallback - This is called when the state is set as the active state.
* @default
*/
this.onInitCallback = null;
/**
* @property {function} onPreloadCallback - This is called when the state starts to load assets.
* @default
*/
this.onPreloadCallback = null;
/**
* @property {function} onCreateCallback - This is called when the state preload has finished and creation begins.
* @default
*/
this.onCreateCallback = null;
/**
* @property {function} onUpdateCallback - This is called when the state is updated, every game loop. It doesn't happen during preload (@see onLoadUpdateCallback).
* @default
*/
this.onUpdateCallback = null;
/**
* @property {function} onRenderCallback - This is called post-render. It doesn't happen during preload (see onLoadRenderCallback).
* @default
*/
this.onRenderCallback = null;
/**
* @property {function} onResizeCallback - This is called if ScaleManager.scalemode is RESIZE and a resize event occurs. It's passed the new width and height.
* @default
*/
this.onResizeCallback = null;
/**
* @property {function} onPreRenderCallback - This is called before the state is rendered and before the stage is cleared but after all game objects have had their final properties adjusted.
* @default
*/
this.onPreRenderCallback = null;
/**
* @property {function} onLoadUpdateCallback - This is called when the State is updated during the preload phase.
* @default
*/
this.onLoadUpdateCallback = null;
/**
* @property {function} onLoadRenderCallback - This is called when the State is rendered during the preload phase.
* @default
*/
this.onLoadRenderCallback = null;
/**
* @property {function} onPausedCallback - This is called when the game is paused.
* @default
*/
this.onPausedCallback = null;
/**
* @property {function} onResumedCallback - This is called when the game is resumed from a paused state.
* @default
*/
this.onResumedCallback = null;
/**
* @property {function} onPauseUpdateCallback - This is called every frame while the game is paused.
* @default
*/
this.onPauseUpdateCallback = null;
/**
* @property {function} onShutDownCallback - This is called when the state is shut down (i.e. swapped to another state).
* @default
*/
this.onShutDownCallback = null;
};
Phaser.StateManager.prototype = {
/**
* The Boot handler is called by Phaser.Game when it first starts up.
* @method Phaser.StateManager#boot
* @private
*/
boot: function () {
this.game.onPause.add(this.pause, this);
this.game.onResume.add(this.resume, this);
if (this._pendingState !== null && typeof this._pendingState !== 'string')
{
this.add('default', this._pendingState, true);
}
},
/**
* Adds a new State into the StateManager. You must give each State a unique key by which you'll identify it.
* The State can be either a Phaser.State object (or an object that extends it), a plain JavaScript object or a function.
* If a function is given a new state object will be created by calling it.
*
* @method Phaser.StateManager#add
* @param {string} key - A unique key you use to reference this state, i.e. "MainMenu", "Level1".
* @param {Phaser.State|object|function} state - The state you want to switch to.
* @param {boolean} [autoStart=false] - If true the State will be started immediately after adding it.
*/
add: function (key, state, autoStart) {
if (autoStart === undefined) { autoStart = false; }
var newState;
if (state instanceof Phaser.State)
{
newState = state;
}
else if (typeof state === 'object')
{
newState = state;
newState.game = this.game;
}
else if (typeof state === 'function')
{
newState = new state(this.game);
}
this.states[key] = newState;
if (autoStart)
{
if (this.game.isBooted)
{
this.start(key);
}
else
{
this._pendingState = key;
}
}
return newState;
},
/**
* Delete the given state.
* @method Phaser.StateManager#remove
* @param {string} key - A unique key you use to reference this state, i.e. "MainMenu", "Level1".
*/
remove: function (key) {
if (this.current === key)
{
this.callbackContext = null;
this.onInitCallback = null;
this.onShutDownCallback = null;
this.onPreloadCallback = null;
this.onLoadRenderCallback = null;
this.onLoadUpdateCallback = null;
this.onCreateCallback = null;
this.onUpdateCallback = null;
this.onPreRenderCallback = null;
this.onRenderCallback = null;
this.onResizeCallback = null;
this.onPausedCallback = null;
this.onResumedCallback = null;
this.onPauseUpdateCallback = null;
}
delete this.states[key];
},
/**
* Start the given State. If a State is already running then State.shutDown will be called (if it exists) before switching to the new State.
*
* @method Phaser.StateManager#start
* @param {string} key - The key of the state you want to start.
* @param {boolean} [clearWorld=true] - Clear everything in the world? This clears the World display list fully (but not the Stage, so if you've added your own objects to the Stage they will need managing directly)
* @param {boolean} [clearCache=false] - Clear the Game.Cache? This purges out all loaded assets. The default is false and you must have clearWorld=true if you want to clearCache as well.
* @param {...*} parameter - Additional parameters that will be passed to the State.init function (if it has one).
*/
start: function (key, clearWorld, clearCache) {
if (clearWorld === undefined) { clearWorld = true; }
if (clearCache === undefined) { clearCache = false; }
if (this.checkState(key))
{
// Place the state in the queue. It will be started the next time the game loop begins.
this._pendingState = key;
this._clearWorld = clearWorld;
this._clearCache = clearCache;
if (arguments.length > 3)
{
this._args = Array.prototype.splice.call(arguments, 3);
}
}
},
/**
* Restarts the current State. State.shutDown will be called (if it exists) before the State is restarted.
*
* @method Phaser.StateManager#restart
* @param {boolean} [clearWorld=true] - Clear everything in the world? This clears the World display list fully (but not the Stage, so if you've added your own objects to the Stage they will need managing directly)
* @param {boolean} [clearCache=false] - Clear the Game.Cache? This purges out all loaded assets. The default is false and you must have clearWorld=true if you want to clearCache as well.
* @param {...*} parameter - Additional parameters that will be passed to the State.init function if it has one.
*/
restart: function (clearWorld, clearCache) {
if (clearWorld === undefined) { clearWorld = true; }
if (clearCache === undefined) { clearCache = false; }
// Place the state in the queue. It will be started the next time the game loop starts.
this._pendingState = this.current;
this._clearWorld = clearWorld;
this._clearCache = clearCache;
if (arguments.length > 2)
{
this._args = Array.prototype.slice.call(arguments, 2);
}
},
/**
* Used by onInit and onShutdown when those functions don't exist on the state
* @method Phaser.StateManager#dummy
* @private
*/
dummy: function () {
},
/**
* preUpdate is called right at the start of the game loop. It is responsible for changing to a new state that was requested previously.
*
* @method Phaser.StateManager#preUpdate
*/
preUpdate: function () {
if (this._pendingState && this.game.isBooted)
{
var previousStateKey = this.current;
// Already got a state running?
this.clearCurrentState();
this.setCurrentState(this._pendingState);
this.onStateChange.dispatch(this.current, previousStateKey);
if (this.current !== this._pendingState)
{
return;
}
else
{
this._pendingState = null;
}
// If StateManager.start has been called from the init of a State that ALSO has a preload, then
// onPreloadCallback will be set, but must be ignored
if (this.onPreloadCallback)
{
this.game.load.reset(true);
this.onPreloadCallback.call(this.callbackContext, this.game);
// Is the loader empty?
if (this.game.load.totalQueuedFiles() === 0 && this.game.load.totalQueuedPacks() === 0)
{
this.loadComplete();
}
else
{
// Start the loader going as we have something in the queue
this.game.load.start();
}
}
else
{
// No init? Then there was nothing to load either
this.loadComplete();
}
}
},
/**
* This method clears the current State, calling its shutdown callback. The process also removes any active tweens,
* resets the camera, resets input, clears physics, removes timers and if set clears the world and cache too.
*
* @method Phaser.StateManager#clearCurrentState
*/
clearCurrentState: function () {
if (this.current)
{
if (this.onShutDownCallback)
{
this.onShutDownCallback.call(this.callbackContext, this.game);
}
this.game.tweens.removeAll();
this.game.camera.reset();
this.game.input.reset(true);
this.game.physics.clear();
this.game.time.removeAll();
this.game.scale.reset(this._clearWorld);
if (this.game.debug)
{
this.game.debug.reset();
}
if (this._clearWorld)
{
this.game.world.shutdown();
if (this._clearCache)
{
this.game.cache.destroy();
}
}
}
},
/**
* Checks if a given phaser state is valid. A State is considered valid if it has at least one of the core functions: preload, create, update or render.
*
* @method Phaser.StateManager#checkState
* @param {string} key - The key of the state you want to check.
* @return {boolean} true if the State has the required functions, otherwise false.
*/
checkState: function (key) {
if (this.states[key])
{
if (this.states[key]['preload'] || this.states[key]['create'] || this.states[key]['update'] || this.states[key]['render'])
{
return true;
}
else
{
console.warn("Invalid Phaser State object given. Must contain at least a one of the required functions: preload, create, update or render");
return false;
}
}
else
{
console.warn("Phaser.StateManager - No state found with the key: " + key);
return false;
}
},
/**
* Links game properties to the State given by the key.
*
* @method Phaser.StateManager#link
* @param {string} key - State key.
* @protected
*/
link: function (key) {
this.states[key].game = this.game;
this.states[key].add = this.game.add;
this.states[key].make = this.game.make;
this.states[key].camera = this.game.camera;
this.states[key].cache = this.game.cache;
this.states[key].input = this.game.input;
this.states[key].load = this.game.load;
this.states[key].math = this.game.math;
this.states[key].sound = this.game.sound;
this.states[key].scale = this.game.scale;
this.states[key].state = this;
this.states[key].stage = this.game.stage;
this.states[key].time = this.game.time;
this.states[key].tweens = this.game.tweens;
this.states[key].world = this.game.world;
this.states[key].particles = this.game.particles;
this.states[key].rnd = this.game.rnd;
this.states[key].physics = this.game.physics;
this.states[key].key = key;
},
/**
* Nulls all State level Phaser properties, including a reference to Game.
*
* @method Phaser.StateManager#unlink
* @param {string} key - State key.
* @protected
*/
unlink: function (key) {
if (this.states[key])
{
this.states[key].game = null;
this.states[key].add = null;
this.states[key].make = null;
this.states[key].camera = null;
this.states[key].cache = null;
this.states[key].input = null;
this.states[key].load = null;
this.states[key].math = null;
this.states[key].sound = null;
this.states[key].scale = null;
this.states[key].state = null;
this.states[key].stage = null;
this.states[key].time = null;
this.states[key].tweens = null;
this.states[key].world = null;
this.states[key].particles = null;
this.states[key].rnd = null;
this.states[key].physics = null;
}
},
/**
* Sets the current State. Should not be called directly (use StateManager.start)
*
* @method Phaser.StateManager#setCurrentState
* @param {string} key - State key.
* @private
*/
setCurrentState: function (key) {
this.callbackContext = this.states[key];
this.link(key);
// Used when the state is set as being the current active state
this.onInitCallback = this.states[key]['init'] || this.dummy;
this.onPreloadCallback = this.states[key]['preload'] || null;
this.onLoadRenderCallback = this.states[key]['loadRender'] || null;
this.onLoadUpdateCallback = this.states[key]['loadUpdate'] || null;
this.onCreateCallback = this.states[key]['create'] || null;
this.onUpdateCallback = this.states[key]['update'] || null;
this.onPreRenderCallback = this.states[key]['preRender'] || null;
this.onRenderCallback = this.states[key]['render'] || null;
this.onResizeCallback = this.states[key]['resize'] || null;
this.onPausedCallback = this.states[key]['paused'] || null;
this.onResumedCallback = this.states[key]['resumed'] || null;
this.onPauseUpdateCallback = this.states[key]['pauseUpdate'] || null;
// Used when the state is no longer the current active state
this.onShutDownCallback = this.states[key]['shutdown'] || this.dummy;
// Reset the physics system, but not on the first state start
if (this.current !== '')
{
this.game.physics.reset();
}
this.current = key;
this._created = false;
// At this point key and pendingState should equal each other
this.onInitCallback.apply(this.callbackContext, this._args);
// If they no longer do then the init callback hit StateManager.start
if (key === this._pendingState)
{
this._args = [];
}
this.game._kickstart = true;
},
/**
* Gets the current State.
*
* @method Phaser.StateManager#getCurrentState
* @return {Phaser.State}
* @public
*/
getCurrentState: function() {
return this.states[this.current];
},
/**
* @method Phaser.StateManager#loadComplete
* @protected
*/
loadComplete: function () {
// Make sure to do load-update one last time before state is set to _created
if (this._created === false && this.onLoadUpdateCallback)
{
this.onLoadUpdateCallback.call(this.callbackContext, this.game);
}
if (this._created === false && this.onCreateCallback)
{
this._created = true;
this.onCreateCallback.call(this.callbackContext, this.game);
}
else
{
this._created = true;
}
},
/**
* @method Phaser.StateManager#pause
* @protected
*/
pause: function () {
if (this._created && this.onPausedCallback)
{
this.onPausedCallback.call(this.callbackContext, this.game);
}
},
/**
* @method Phaser.StateManager#resume
* @protected
*/
resume: function () {
if (this._created && this.onResumedCallback)
{
this.onResumedCallback.call(this.callbackContext, this.game);
}
},
/**
* @method Phaser.StateManager#update
* @protected
*/
update: function () {
if (this._created)
{
if (this.onUpdateCallback)
{
this.onUpdateCallback.call(this.callbackContext, this.game);
}
}
else
{
if (this.onLoadUpdateCallback)
{
this.onLoadUpdateCallback.call(this.callbackContext, this.game);
}
}
},
/**
* @method Phaser.StateManager#pauseUpdate
* @protected
*/
pauseUpdate: function () {
if (this._created)
{
if (this.onPauseUpdateCallback)
{
this.onPauseUpdateCallback.call(this.callbackContext, this.game);
}
}
else
{
if (this.onLoadUpdateCallback)
{
this.onLoadUpdateCallback.call(this.callbackContext, this.game);
}
}
},
/**
* @method Phaser.StateManager#preRender
* @protected
* @param {number} elapsedTime - The time elapsed since the last update.
*/
preRender: function (elapsedTime) {
if (this._created && this.onPreRenderCallback)
{
this.onPreRenderCallback.call(this.callbackContext, this.game, elapsedTime);
}
},
/**
* @method Phaser.StateManager#resize
* @protected
*/
resize: function (width, height) {
if (this.onResizeCallback)
{
this.onResizeCallback.call(this.callbackContext, width, height);
}
},
/**
* @method Phaser.StateManager#render
* @protected
*/
render: function () {
if (this._created)
{
if (this.onRenderCallback)
{
if (this.game.renderType === Phaser.CANVAS)
{
this.game.context.save();
this.game.context.setTransform(1, 0, 0, 1, 0, 0);
this.onRenderCallback.call(this.callbackContext, this.game);
this.game.context.restore();
}
else
{
this.onRenderCallback.call(this.callbackContext, this.game);
}
}
}
else
{
if (this.onLoadRenderCallback)
{
this.onLoadRenderCallback.call(this.callbackContext, this.game);
}
}
},
/**
* Removes all StateManager callback references to the State object, nulls the game reference and clears the States object.
* You don't recover from this without rebuilding the Phaser instance again.
* @method Phaser.StateManager#destroy
*/
destroy: function () {
this._clearWorld = true;
this._clearCache = true;
this.clearCurrentState();
this.callbackContext = null;
this.onInitCallback = null;
this.onShutDownCallback = null;
this.onPreloadCallback = null;
this.onLoadRenderCallback = null;
this.onLoadUpdateCallback = null;
this.onCreateCallback = null;
this.onUpdateCallback = null;
this.onRenderCallback = null;
this.onPausedCallback = null;
this.onResumedCallback = null;
this.onPauseUpdateCallback = null;
this.game = null;
this.states = {};
this._pendingState = null;
this.current = '';
}
};
Phaser.StateManager.prototype.constructor = Phaser.StateManager;
/**
* @name Phaser.StateManager#created
* @property {boolean} created - True if the current state has had its `create` method run (if it has one, if not this is true by default).
* @readOnly
*/
Object.defineProperty(Phaser.StateManager.prototype, "created", {
get: function () {
return this._created;
}
});
/**
* "It's like nailing jelly to a kitten" - Gary Penn
*/
/**
* @author Miller Medeiros http://millermedeiros.github.com/js-signals/
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Signals are what Phaser uses to handle events and event dispatching.
* You can listen for a Signal by binding a callback / function to it.
* This is done by using either `Signal.add` or `Signal.addOnce`.
*
* For example you can listen for a touch or click event from the Input Manager
* by using its `onDown` Signal:
*
* `game.input.onDown.add(function() { ... });`
*
* Rather than inline your function, you can pass a reference:
*
* `game.input.onDown.add(clicked, this);`
* `function clicked () { ... }`
*
* In this case the second argument (`this`) is the context in which your function should be called.
*
* Now every time the InputManager dispatches the `onDown` signal (or event), your function
* will be called.
*
* Very often a Signal will send arguments to your function.
* This is specific to the Signal itself.
* If you're unsure then check the documentation, or failing that simply do:
*
* `Signal.add(function() { console.log(arguments); })`
*
* and it will log all of the arguments your function received from the Signal.
*
* Sprites have lots of default signals you can listen to in their Events class, such as:
*
* `sprite.events.onKilled`
*
* Which is called automatically whenever the Sprite is killed.
* There are lots of other events, see the Events component for a list.
*
* As well as listening to pre-defined Signals you can also create your own:
*
* `var mySignal = new Phaser.Signal();`
*
* This creates a new Signal. You can bind a callback to it:
*
* `mySignal.add(myCallback, this);`
*
* and then finally when ready you can dispatch the Signal:
*
* `mySignal.dispatch(your arguments);`
*
* And your callback will be invoked. See the dispatch method for more details.
*
* @class Phaser.Signal
* @constructor
*/
Phaser.Signal = function () {};
Phaser.Signal.prototype = {
/**
* @property {?Array.<Phaser.SignalBinding>} _bindings - Internal variable.
* @private
*/
_bindings: null,
/**
* @property {any} _prevParams - Internal variable.
* @private
*/
_prevParams: null,
/**
* Memorize the previously dispatched event?
*
* If an event has been memorized it is automatically dispatched when a new listener is added with {@link #add} or {@link #addOnce}.
* Use {@link #forget} to clear any currently memorized event.
*
* @property {boolean} memorize
*/
memorize: false,
/**
* @property {boolean} _shouldPropagate
* @private
*/
_shouldPropagate: true,
/**
* Is the Signal active? Only active signals will broadcast dispatched events.
*
* Setting this property during a dispatch will only affect the next dispatch. To stop the propagation of a signal from a listener use {@link #halt}.
*
* @property {boolean} active
* @default
*/
active: true,
/**
* @property {function} _boundDispatch - The bound dispatch function, if any.
* @private
*/
_boundDispatch: false,
/**
* @method Phaser.Signal#validateListener
* @param {function} listener - Signal handler function.
* @param {string} fnName - Function name.
* @private
*/
validateListener: function (listener, fnName) {
if (typeof listener !== 'function')
{
throw new Error('Phaser.Signal: listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName));
}
},
/**
* @method Phaser.Signal#_registerListener
* @private
* @param {function} listener - Signal handler function.
* @param {boolean} isOnce - Should the listener only be called once?
* @param {object} [listenerContext] - The context under which the listener is invoked.
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0).
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener.
*/
_registerListener: function (listener, isOnce, listenerContext, priority, args) {
var prevIndex = this._indexOfListener(listener, listenerContext);
var binding;
if (prevIndex !== -1)
{
binding = this._bindings[prevIndex];
if (binding.isOnce() !== isOnce)
{
throw new Error('You cannot add' + (isOnce ? '' : 'Once') + '() then add' + (!isOnce ? '' : 'Once') + '() the same listener without removing the relationship first.');
}
}
else
{
binding = new Phaser.SignalBinding(this, listener, isOnce, listenerContext, priority, args);
this._addBinding(binding);
}
if (this.memorize && this._prevParams)
{
binding.execute(this._prevParams);
}
return binding;
},
/**
* @method Phaser.Signal#_addBinding
* @private
* @param {Phaser.SignalBinding} binding - An Object representing the binding between the Signal and listener.
*/
_addBinding: function (binding) {
if (!this._bindings)
{
this._bindings = [];
}
// Simplified insertion sort
var n = this._bindings.length;
do {
n--;
}
while (this._bindings[n] && binding._priority <= this._bindings[n]._priority);
this._bindings.splice(n + 1, 0, binding);
},
/**
* @method Phaser.Signal#_indexOfListener
* @private
* @param {function} listener - Signal handler function.
* @param {object} [context=null] - Signal handler function.
* @return {number} The index of the listener within the private bindings array.
*/
_indexOfListener: function (listener, context) {
if (!this._bindings)
{
return -1;
}
if (context === undefined) { context = null; }
var n = this._bindings.length;
var cur;
while (n--)
{
cur = this._bindings[n];
if (cur._listener === listener && cur.context === context)
{
return n;
}
}
return -1;
},
/**
* Check if a specific listener is attached.
*
* @method Phaser.Signal#has
* @param {function} listener - Signal handler function.
* @param {object} [context] - Context on which listener will be executed (object that should represent the `this` variable inside listener function).
* @return {boolean} If Signal has the specified listener.
*/
has: function (listener, context) {
return this._indexOfListener(listener, context) !== -1;
},
/**
* Add an event listener for this signal.
*
* An event listener is a callback with a related context and priority.
*
* You can optionally provide extra arguments which will be passed to the callback after any internal parameters.
*
* For example: `Phaser.Key.onDown` when dispatched will send the Phaser.Key object that caused the signal as the first parameter.
* Any arguments you've specified after `priority` will be sent as well:
*
* `fireButton.onDown.add(shoot, this, 0, 'lazer', 100);`
*
* When onDown dispatches it will call the `shoot` callback passing it: `Phaser.Key, 'lazer', 100`.
*
* Where the first parameter is the one that Key.onDown dispatches internally and 'lazer',
* and the value 100 were the custom arguments given in the call to 'add'.
*
* @method Phaser.Signal#add
* @param {function} listener - The function to call when this Signal is dispatched.
* @param {object} [listenerContext] - The context under which the listener will be executed (i.e. the object that should represent the `this` variable).
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added (default = 0)
* @param {...any} [args=(none)] - Additional arguments to pass to the callback (listener) function. They will be appended after any arguments usually dispatched.
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener.
*/
add: function (listener, listenerContext, priority) {
this.validateListener(listener, 'add');
var args = [];
if (arguments.length > 3)
{
for (var i = 3; i < arguments.length; i++)
{
args.push(arguments[i]);
}
}
return this._registerListener(listener, false, listenerContext, priority, args);
},
/**
* Add a one-time listener - the listener is automatically removed after the first execution.
*
* If there is as {@link Phaser.Signal#memorize memorized} event then it will be dispatched and
* the listener will be removed immediately.
*
* @method Phaser.Signal#addOnce
* @param {function} listener - The function to call when this Signal is dispatched.
* @param {object} [listenerContext] - The context under which the listener will be executed (i.e. the object that should represent the `this` variable).
* @param {number} [priority] - The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added (default = 0)
* @param {...any} [args=(none)] - Additional arguments to pass to the callback (listener) function. They will be appended after any arguments usually dispatched.
* @return {Phaser.SignalBinding} An Object representing the binding between the Signal and listener.
*/
addOnce: function (listener, listenerContext, priority) {
this.validateListener(listener, 'addOnce');
var args = [];
if (arguments.length > 3)
{
for (var i = 3; i < arguments.length; i++)
{
args.push(arguments[i]);
}
}
return this._registerListener(listener, true, listenerContext, priority, args);
},
/**
* Remove a single event listener.
*
* @method Phaser.Signal#remove
* @param {function} listener - Handler function that should be removed.
* @param {object} [context=null] - Execution context (since you can add the same handler multiple times if executing in a different context).
* @return {function} Listener handler function.
*/
remove: function (listener, context) {
this.validateListener(listener, 'remove');
var i = this._indexOfListener(listener, context);
if (i !== -1)
{
this._bindings[i]._destroy(); //no reason to a Phaser.SignalBinding exist if it isn't attached to a signal
this._bindings.splice(i, 1);
}
return listener;
},
/**
* Remove all event listeners.
*
* @method Phaser.Signal#removeAll
* @param {object} [context=null] - If specified only listeners for the given context will be removed.
*/
removeAll: function (context) {
if (context === undefined) { context = null; }
if (!this._bindings)
{
return;
}
var n = this._bindings.length;
while (n--)
{
if (context)
{
if (this._bindings[n].context === context)
{
this._bindings[n]._destroy();
this._bindings.splice(n, 1);
}
}
else
{
this._bindings[n]._destroy();
}
}
if (!context)
{
this._bindings.length = 0;
}
},
/**
* Gets the total number of listeners attached to this Signal.
*
* @method Phaser.Signal#getNumListeners
* @return {integer} Number of listeners attached to the Signal.
*/
getNumListeners: function () {
return this._bindings ? this._bindings.length : 0;
},
/**
* Stop propagation of the event, blocking the dispatch to next listener on the queue.
*
* This should be called only during event dispatch as calling it before/after dispatch won't affect another broadcast.
* See {@link #active} to enable/disable the signal entirely.
*
* @method Phaser.Signal#halt
*/
halt: function () {
this._shouldPropagate = false;
},
/**
* Dispatch / broadcast the event to all listeners.
*
* To create an instance-bound dispatch for this Signal, use {@link #boundDispatch}.
*
* @method Phaser.Signal#dispatch
* @param {any} [params] - Parameters that should be passed to each handler.
*/
dispatch: function () {
if (!this.active || !this._bindings)
{
return;
}
var paramsArr = Array.prototype.slice.call(arguments);
var n = this._bindings.length;
var bindings;
if (this.memorize)
{
this._prevParams = paramsArr;
}
if (!n)
{
// Should come after memorize
return;
}
bindings = this._bindings.slice(); //clone array in case add/remove items during dispatch
this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch.
//execute all callbacks until end of the list or until a callback returns `false` or stops propagation
//reverse loop since listeners with higher priority will be added at the end of the list
do {
n--;
}
while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false);
},
/**
* Forget the currently {@link Phaser.Signal#memorize memorized} event, if any.
*
* @method Phaser.Signal#forget
*/
forget: function() {
if (this._prevParams)
{
this._prevParams = null;
}
},
/**
* Dispose the signal - no more events can be dispatched.
*
* This removes all event listeners and clears references to external objects.
* Calling methods on a disposed objects results in undefined behavior.
*
* @method Phaser.Signal#dispose
*/
dispose: function () {
this.removeAll();
this._bindings = null;
if (this._prevParams)
{
this._prevParams = null;
}
},
/**
* A string representation of the object.
*
* @method Phaser.Signal#toString
* @return {string} String representation of the object.
*/
toString: function () {
return '[Phaser.Signal active:'+ this.active +' numListeners:'+ this.getNumListeners() +']';
}
};
/**
* Create a `dispatch` function that maintains a binding to the original Signal context.
*
* Use the resulting value if the dispatch function needs to be passed somewhere
* or called independently of the Signal object.
*
* @memberof Phaser.Signal
* @property {function} boundDispatch
*/
Object.defineProperty(Phaser.Signal.prototype, "boundDispatch", {
get: function () {
var _this = this;
return this._boundDispatch || (this._boundDispatch = function () {
return _this.dispatch.apply(_this, arguments);
});
}
});
Phaser.Signal.prototype.constructor = Phaser.Signal;
/**
* @author Miller Medeiros http://millermedeiros.github.com/js-signals/
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Object that represents a binding between a Signal and a listener function.
* This is an internal constructor and shouldn't be created directly.
* Inspired by Joa Ebert AS3 SignalBinding and Robert Penner's Slot classes.
*
* @class Phaser.SignalBinding
* @constructor
* @param {Phaser.Signal} signal - Reference to Signal object that listener is currently bound to.
* @param {function} listener - Handler function bound to the signal.
* @param {boolean} isOnce - If binding should be executed just once.
* @param {object} [listenerContext=null] - Context on which listener will be executed (object that should represent the `this` variable inside listener function).
* @param {number} [priority] - The priority level of the event listener. (default = 0).
* @param {...any} [args=(none)] - Additional arguments to pass to the callback (listener) function. They will be appended after any arguments usually dispatched.
*/
Phaser.SignalBinding = function (signal, listener, isOnce, listenerContext, priority, args) {
/**
* @property {Phaser.Game} _listener - Handler function bound to the signal.
* @private
*/
this._listener = listener;
if (isOnce)
{
this._isOnce = true;
}
if (listenerContext != null) /* not null/undefined */
{
this.context = listenerContext;
}
/**
* @property {Phaser.Signal} _signal - Reference to Signal object that listener is currently bound to.
* @private
*/
this._signal = signal;
if (priority)
{
this._priority = priority;
}
if (args && args.length)
{
this._args = args;
}
};
Phaser.SignalBinding.prototype = {
/**
* @property {?object} context - Context on which listener will be executed (object that should represent the `this` variable inside listener function).
*/
context: null,
/**
* @property {boolean} _isOnce - If binding should be executed just once.
* @private
*/
_isOnce: false,
/**
* @property {number} _priority - Listener priority.
* @private
*/
_priority: 0,
/**
* @property {array} _args - Listener arguments.
* @private
*/
_args: null,
/**
* @property {number} callCount - The number of times the handler function has been called.
*/
callCount: 0,
/**
* If binding is active and should be executed.
* @property {boolean} active
* @default
*/
active: true,
/**
* Default parameters passed to listener during `Signal.dispatch` and `SignalBinding.execute` (curried parameters).
* @property {array|null} params
* @default
*/
params: null,
/**
* Call listener passing arbitrary parameters.
* If binding was added using `Signal.addOnce()` it will be automatically removed from signal dispatch queue, this method is used internally for the signal dispatch.
* @method Phaser.SignalBinding#execute
* @param {any[]} [paramsArr] - Array of parameters that should be passed to the listener.
* @return {any} Value returned by the listener.
*/
execute: function(paramsArr) {
var handlerReturn, params;
if (this.active && !!this._listener)
{
params = this.params ? this.params.concat(paramsArr) : paramsArr;
if (this._args)
{
params = params.concat(this._args);
}
handlerReturn = this._listener.apply(this.context, params);
this.callCount++;
if (this._isOnce)
{
this.detach();
}
}
return handlerReturn;
},
/**
* Detach binding from signal.
* alias to: @see mySignal.remove(myBinding.getListener());
* @method Phaser.SignalBinding#detach
* @return {function|null} Handler function bound to the signal or `null` if binding was previously detached.
*/
detach: function () {
return this.isBound() ? this._signal.remove(this._listener, this.context) : null;
},
/**
* @method Phaser.SignalBinding#isBound
* @return {boolean} True if binding is still bound to the signal and has a listener.
*/
isBound: function () {
return (!!this._signal && !!this._listener);
},
/**
* @method Phaser.SignalBinding#isOnce
* @return {boolean} If SignalBinding will only be executed once.
*/
isOnce: function () {
return this._isOnce;
},
/**
* @method Phaser.SignalBinding#getListener
* @return {function} Handler function bound to the signal.
*/
getListener: function () {
return this._listener;
},
/**
* @method Phaser.SignalBinding#getSignal
* @return {Phaser.Signal} Signal that listener is currently bound to.
*/
getSignal: function () {
return this._signal;
},
/**
* Delete instance properties
* @method Phaser.SignalBinding#_destroy
* @private
*/
_destroy: function () {
delete this._signal;
delete this._listener;
delete this.context;
},
/**
* @method Phaser.SignalBinding#toString
* @return {string} String representation of the object.
*/
toString: function () {
return '[Phaser.SignalBinding isOnce:' + this._isOnce +', isBound:'+ this.isBound() +', active:' + this.active + ']';
}
};
Phaser.SignalBinding.prototype.constructor = Phaser.SignalBinding;
/**
* @author Richard Davey <rich@photonstorm.com>
* @author Mat Groves (@Doormat23)
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is a base Filter class to use for any Phaser filter development.
* If you want to make a custom filter, this should be your base class.
*
* The default uniforms, types and values for all Filters are:
*
* ```
* resolution: { type: '2f', value: { x: 256, y: 256 }}
* time: { type: '1f', value: 0 }
* mouse: { type: '2f', value: { x: 0.0, y: 0.0 } }
* date: { type: '4fv', value: [ d.getFullYear(), d.getMonth(), d.getDate(), d.getHours() *60 * 60 + d.getMinutes() * 60 + d.getSeconds() ] }
* sampleRate: { type: '1f', value: 44100.0 }
* iChannel0: { type: 'sampler2D', value: null, textureData: { repeat: true } }
* iChannel1: { type: 'sampler2D', value: null, textureData: { repeat: true } }
* iChannel2: { type: 'sampler2D', value: null, textureData: { repeat: true } }
* iChannel3: { type: 'sampler2D', value: null, textureData: { repeat: true } }
* ```
*
* The vast majority of filters (including all of those that ship with Phaser) use fragment shaders, and
* therefore only work in WebGL and are not supported by Canvas at all.
*
* @class Phaser.Filter
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {object} [uniforms] - Uniform mappings object. The uniforms are added on the default uniforms, or replace them if the keys are the same.
* @param {Array|string} [fragmentSrc] - The fragment shader code. Either an array, one element per line of code, or a string.
*/
Phaser.Filter = function (game, uniforms, fragmentSrc) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {number} type - The const type of this object, either Phaser.WEBGL_FILTER or Phaser.CANVAS_FILTER.
* @default
*/
this.type = Phaser.WEBGL_FILTER;
/**
* An array of passes - some filters contain a few steps this array simply stores the steps in a linear fashion.
* For example the blur filter has two passes blurX and blurY.
* @property {array} passes - An array of filter objects.
* @private
*/
this.passes = [ this ];
/**
* @property {array} shaders - Array an array of shaders.
* @private
*/
this.shaders = [];
/**
* @property {boolean} dirty - Internal PIXI var.
* @default
*/
this.dirty = true;
/**
* @property {number} padding - Internal PIXI var.
* @default
*/
this.padding = 0;
/**
* @property {Phaser.Point} prevPoint - The previous position of the pointer (we don't update the uniform if the same)
*/
this.prevPoint = new Phaser.Point();
/*
* The supported types are: 1f, 1fv, 1i, 2f, 2fv, 2i, 2iv, 3f, 3fv, 3i, 3iv, 4f, 4fv, 4i, 4iv, mat2, mat3, mat4 and sampler2D.
*/
var d = new Date();
/**
* @property {object} uniforms - Default uniform mappings. Compatible with ShaderToy and GLSLSandbox.
*/
this.uniforms = {
resolution: { type: '2f', value: { x: 256, y: 256 }},
time: { type: '1f', value: 0 },
mouse: { type: '2f', value: { x: 0.0, y: 0.0 } },
date: { type: '4fv', value: [ d.getFullYear(), d.getMonth(), d.getDate(), d.getHours() *60 * 60 + d.getMinutes() * 60 + d.getSeconds() ] },
sampleRate: { type: '1f', value: 44100.0 },
iChannel0: { type: 'sampler2D', value: null, textureData: { repeat: true } },
iChannel1: { type: 'sampler2D', value: null, textureData: { repeat: true } },
iChannel2: { type: 'sampler2D', value: null, textureData: { repeat: true } },
iChannel3: { type: 'sampler2D', value: null, textureData: { repeat: true } }
};
// Copy over / replace any passed in the constructor
if (uniforms)
{
for (var key in uniforms)
{
this.uniforms[key] = uniforms[key];
}
}
// If fragmentSrc is a string, split it based on new-lines into an array
if (typeof fragmentSrc === 'string')
{
fragmentSrc = fragmentSrc.split('\n');
}
/**
* @property {array|string} fragmentSrc - The fragment shader code.
*/
this.fragmentSrc = fragmentSrc || [];
};
Phaser.Filter.prototype = {
/**
* This should be over-ridden. Will receive a variable number of arguments.
*
* @method Phaser.Filter#init
*/
init: function () {
// This should be over-ridden. Will receive a variable number of arguments.
},
/**
* Set the resolution uniforms on the filter.
*
* @method Phaser.Filter#setResolution
* @param {number} width - The width of the display.
* @param {number} height - The height of the display.
*/
setResolution: function (width, height) {
this.uniforms.resolution.value.x = width;
this.uniforms.resolution.value.y = height;
},
/**
* Updates the filter.
*
* @method Phaser.Filter#update
* @param {Phaser.Pointer} [pointer] - A Pointer object to use for the filter. The coordinates are mapped to the mouse uniform.
*/
update: function (pointer) {
if (pointer)
{
var x = pointer.x / this.game.width;
var y = 1 - pointer.y / this.game.height;
if (x !== this.prevPoint.x || y !== this.prevPoint.y)
{
this.uniforms.mouse.value.x = x.toFixed(2);
this.uniforms.mouse.value.y = y.toFixed(2);
this.prevPoint.set(x, y);
}
}
this.uniforms.time.value = this.game.time.totalElapsedSeconds();
},
/**
* Creates a new Phaser.Image object using a blank texture and assigns
* this Filter to it. The image is then added to the world.
*
* If you don't provide width and height values then Filter.width and Filter.height are used.
*
* If you do provide width and height values then this filter will be resized to match those
* values.
*
* @method Phaser.Filter#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} [width] - The width of the Image. If not specified (or null) it will use Filter.width. If specified Filter.width will be set to this value.
* @param {number} [height] - The height of the Image. If not specified (or null) it will use Filter.height. If specified Filter.height will be set to this value.
* @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.
* @return {Phaser.Image} The newly added Image object.
*/
addToWorld: function (x, y, width, height, anchorX, anchorY) {
if (anchorX === undefined) { anchorX = 0; }
if (anchorY === undefined) { anchorY = 0; }
if (width !== undefined && width !== null)
{
this.width = width;
}
else
{
width = this.width;
}
if (height !== undefined && height !== null)
{
this.height = height;
}
else
{
height = this.height;
}
var image = this.game.add.image(x, y, '__default');
image.width = width;
image.height = height;
image.anchor.set(anchorX, anchorY);
image.filters = [ this ];
return image;
},
/**
* Syncs the uniforms between the class object and the shaders.
*
* @method Phaser.Filter#syncUniforms
*/
syncUniforms: function () {
for (var i = 0; i < this.shaders.length; i++)
{
this.shaders[i].dirty = true;
}
},
/**
* Clear down this Filter and null out references to game.
*
* @method Phaser.Filter#destroy
*/
destroy: function () {
this.passes.length = 0;
this.shaders.length = 0;
this.fragmentSrc.length = 0;
this.game = null;
this.uniforms = null;
this.prevPoint = null;
}
};
Phaser.Filter.prototype.constructor = Phaser.Filter;
/**
* @name Phaser.Filter#width
* @property {number} width - The width (resolution uniform)
*/
Object.defineProperty(Phaser.Filter.prototype, 'width', {
get: function () {
return this.uniforms.resolution.value.x;
},
set: function (value) {
this.uniforms.resolution.value.x = value;
}
});
/**
* @name Phaser.Filter#height
* @property {number} height - The height (resolution uniform)
*/
Object.defineProperty(Phaser.Filter.prototype, 'height', {
get: function () {
return this.uniforms.resolution.value.y;
},
set: function (value) {
this.uniforms.resolution.value.y = value;
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is a base Plugin template to use for any Phaser plugin development.
*
* @class Phaser.Plugin
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {any} parent - The object that owns this plugin, usually Phaser.PluginManager.
*/
Phaser.Plugin = function (game, parent) {
if (parent === undefined) { parent = null; }
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {any} parent - The parent of this plugin. If added to the PluginManager the parent will be set to that, otherwise it will be null.
*/
this.parent = parent;
/**
* @property {boolean} active - A Plugin with active=true has its preUpdate and update methods called by the parent, otherwise they are skipped.
* @default
*/
this.active = false;
/**
* @property {boolean} visible - A Plugin with visible=true has its render and postRender methods called by the parent, otherwise they are skipped.
* @default
*/
this.visible = false;
/**
* @property {boolean} hasPreUpdate - A flag to indicate if this plugin has a preUpdate method.
* @default
*/
this.hasPreUpdate = false;
/**
* @property {boolean} hasUpdate - A flag to indicate if this plugin has an update method.
* @default
*/
this.hasUpdate = false;
/**
* @property {boolean} hasPostUpdate - A flag to indicate if this plugin has a postUpdate method.
* @default
*/
this.hasPostUpdate = false;
/**
* @property {boolean} hasRender - A flag to indicate if this plugin has a render method.
* @default
*/
this.hasRender = false;
/**
* @property {boolean} hasPostRender - A flag to indicate if this plugin has a postRender method.
* @default
*/
this.hasPostRender = false;
};
Phaser.Plugin.prototype = {
/**
* Pre-update is called at the very start of the update cycle, before any other subsystems have been updated (including Physics).
* It is only called if active is set to true.
* @method Phaser.Plugin#preUpdate
*/
preUpdate: function () {
},
/**
* Update is called after all the core subsystems (Input, Tweens, Sound, etc) and the State have updated, but before the render.
* It is only called if active is set to true.
* @method Phaser.Plugin#update
*/
update: function () {
},
/**
* Render is called right after the Game Renderer completes, but before the State.render.
* It is only called if visible is set to true.
* @method Phaser.Plugin#render
*/
render: function () {
},
/**
* Post-render is called after the Game Renderer and State.render have run.
* It is only called if visible is set to true.
* @method Phaser.Plugin#postRender
*/
postRender: function () {
},
/**
* Clear down this Plugin and null out references
* @method Phaser.Plugin#destroy
*/
destroy: function () {
this.game = null;
this.parent = null;
this.active = false;
this.visible = false;
}
};
Phaser.Plugin.prototype.constructor = Phaser.Plugin;
/* jshint newcap: false */
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Plugin Manager is responsible for the loading, running and unloading of Phaser Plugins.
*
* @class Phaser.PluginManager
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.PluginManager = function(game) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {Phaser.Plugin[]} plugins - An array of all the plugins being managed by this PluginManager.
*/
this.plugins = [];
/**
* @property {number} _len - Internal cache var.
* @private
*/
this._len = 0;
/**
* @property {number} _i - Internal cache var.
* @private
*/
this._i = 0;
};
Phaser.PluginManager.prototype = {
/**
* Add a new Plugin into the PluginManager.
* The Plugin must have 2 properties: game and parent. Plugin.game is set to the game reference the PluginManager uses, and parent is set to the PluginManager.
*
* @method Phaser.PluginManager#add
* @param {object|Phaser.Plugin} plugin - The Plugin to add into the PluginManager. This can be a function or an existing object.
* @param {...*} parameter - Additional arguments that will be passed to the Plugin.init method.
* @return {Phaser.Plugin} The Plugin that was added to the manager.
*/
add: function (plugin) {
var args = Array.prototype.slice.call(arguments, 1);
var result = false;
// Prototype?
if (typeof plugin === 'function')
{
plugin = new plugin(this.game, this);
}
else
{
plugin.game = this.game;
plugin.parent = this;
}
// Check for methods now to avoid having to do this every loop
if (typeof plugin['preUpdate'] === 'function')
{
plugin.hasPreUpdate = true;
result = true;
}
if (typeof plugin['update'] === 'function')
{
plugin.hasUpdate = true;
result = true;
}
if (typeof plugin['postUpdate'] === 'function')
{
plugin.hasPostUpdate = true;
result = true;
}
if (typeof plugin['render'] === 'function')
{
plugin.hasRender = true;
result = true;
}
if (typeof plugin['postRender'] === 'function')
{
plugin.hasPostRender = true;
result = true;
}
// The plugin must have at least one of the above functions to be added to the PluginManager.
if (result)
{
if (plugin.hasPreUpdate || plugin.hasUpdate || plugin.hasPostUpdate)
{
plugin.active = true;
}
if (plugin.hasRender || plugin.hasPostRender)
{
plugin.visible = true;
}
this._len = this.plugins.push(plugin);
// Allows plugins to run potentially destructive code outside of the constructor, and only if being added to the PluginManager
if (typeof plugin['init'] === 'function')
{
plugin.init.apply(plugin, args);
}
return plugin;
}
else
{
return null;
}
},
/**
* Remove a Plugin from the PluginManager. It calls Plugin.destroy on the plugin before removing it from the manager.
*
* @method Phaser.PluginManager#remove
* @param {Phaser.Plugin} plugin - The plugin to be removed.
* @param {boolean} [destroy=true] - Call destroy on the plugin that is removed?
*/
remove: function (plugin, destroy) {
if (destroy === undefined) { destroy = true; }
this._i = this._len;
while (this._i--)
{
if (this.plugins[this._i] === plugin)
{
if (destroy)
{
plugin.destroy();
}
this.plugins.splice(this._i, 1);
this._len--;
return;
}
}
},
/**
* Remove all Plugins from the PluginManager. It calls Plugin.destroy on every plugin before removing it from the manager.
*
* @method Phaser.PluginManager#removeAll
*/
removeAll: function() {
this._i = this._len;
while (this._i--)
{
this.plugins[this._i].destroy();
}
this.plugins.length = 0;
this._len = 0;
},
/**
* Pre-update is called at the very start of the update cycle, before any other subsystems have been updated (including Physics).
* It only calls plugins who have active=true.
*
* @method Phaser.PluginManager#preUpdate
*/
preUpdate: function () {
this._i = this._len;
while (this._i--)
{
if (this.plugins[this._i].active && this.plugins[this._i].hasPreUpdate)
{
this.plugins[this._i].preUpdate();
}
}
},
/**
* Update is called after all the core subsystems (Input, Tweens, Sound, etc) and the State have updated, but before the render.
* It only calls plugins who have active=true.
*
* @method Phaser.PluginManager#update
*/
update: function () {
this._i = this._len;
while (this._i--)
{
if (this.plugins[this._i].active && this.plugins[this._i].hasUpdate)
{
this.plugins[this._i].update();
}
}
},
/**
* PostUpdate is the last thing to be called before the world render.
* In particular, it is called after the world postUpdate, which means the camera has been adjusted.
* It only calls plugins who have active=true.
*
* @method Phaser.PluginManager#postUpdate
*/
postUpdate: function () {
this._i = this._len;
while (this._i--)
{
if (this.plugins[this._i].active && this.plugins[this._i].hasPostUpdate)
{
this.plugins[this._i].postUpdate();
}
}
},
/**
* Render is called right after the Game Renderer completes, but before the State.render.
* It only calls plugins who have visible=true.
*
* @method Phaser.PluginManager#render
*/
render: function () {
this._i = this._len;
while (this._i--)
{
if (this.plugins[this._i].visible && this.plugins[this._i].hasRender)
{
this.plugins[this._i].render();
}
}
},
/**
* Post-render is called after the Game Renderer and State.render have run.
* It only calls plugins who have visible=true.
*
* @method Phaser.PluginManager#postRender
*/
postRender: function () {
this._i = this._len;
while (this._i--)
{
if (this.plugins[this._i].visible && this.plugins[this._i].hasPostRender)
{
this.plugins[this._i].postRender();
}
}
},
/**
* Clear down this PluginManager, calls destroy on every plugin and nulls out references.
*
* @method Phaser.PluginManager#destroy
*/
destroy: function () {
this.removeAll();
this.game = null;
}
};
Phaser.PluginManager.prototype.constructor = Phaser.PluginManager;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Stage controls root level display objects upon which everything is displayed.
* It also handles browser visibility handling and the pausing due to loss of focus.
*
* @class Phaser.Stage
* @extends PIXI.DisplayObjectContainer
* @constructor
* @param {Phaser.Game} game - Game reference to the currently running game.
*/
Phaser.Stage = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
PIXI.DisplayObjectContainer.call(this);
/**
* @property {string} name - The name of this object.
* @default
*/
this.name = '_stage_root';
/**
* By default if the browser tab loses focus the game will pause.
* You can stop that behavior by setting this property to true.
* Note that the browser can still elect to pause your game if it wishes to do so,
* for example swapping to another browser tab. This will cause the RAF callback to halt,
* effectively pausing your game, even though no in-game pause event is triggered if you enable this property.
* @property {boolean} disableVisibilityChange
* @default
*/
this.disableVisibilityChange = false;
/**
* @property {boolean} exists - If exists is true the Stage and all children are updated, otherwise it is skipped.
* @default
*/
this.exists = true;
/**
* @property {PIXI.Matrix} worldTransform - Current transform of the object based on world (parent) factors
* @private
* @readOnly
*/
this.worldTransform = new PIXI.Matrix();
/**
* @property {Phaser.Stage} stage - The stage reference (the Stage is its own stage)
* @private
* @readOnly
*/
this.stage = this;
/**
* @property {number} currentRenderOrderID - Reset each frame, keeps a count of the total number of objects updated.
*/
this.currentRenderOrderID = 0;
/**
* @property {string} hiddenVar - The page visibility API event name.
* @private
*/
this._hiddenVar = 'hidden';
/**
* @property {function} _onChange - The blur/focus event handler.
* @private
*/
this._onChange = null;
/**
* @property {number} _bgColor - Stage background color object. Populated by setBackgroundColor.
* @private
*/
this._bgColor = { r: 0, g: 0, b: 0, a: 0, color: 0, rgba: '#000000' };
if (!this.game.transparent)
{
// transparent = 0,0,0,0 - otherwise r,g,b,1
this._bgColor.a = 1;
}
if (game.config)
{
this.parseConfig(game.config);
}
};
Phaser.Stage.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
Phaser.Stage.prototype.constructor = Phaser.Stage;
/**
* Parses a Game configuration object.
*
* @method Phaser.Stage#parseConfig
* @protected
* @param {object} config -The configuration object to parse.
*/
Phaser.Stage.prototype.parseConfig = function (config) {
if (config['disableVisibilityChange'])
{
this.disableVisibilityChange = config['disableVisibilityChange'];
}
if (config['backgroundColor'])
{
this.setBackgroundColor(config['backgroundColor']);
}
};
/**
* Initialises the stage and adds the event listeners.
* @method Phaser.Stage#boot
* @private
*/
Phaser.Stage.prototype.boot = function () {
Phaser.DOM.getOffset(this.game.canvas, this.offset);
Phaser.Canvas.setUserSelect(this.game.canvas, 'none');
Phaser.Canvas.setTouchAction(this.game.canvas, 'none');
this.checkVisibility();
};
/**
* This is called automatically after the plugins preUpdate and before the State.update.
* Most objects have preUpdate methods and it's where initial movement and positioning is done.
*
* @method Phaser.Stage#preUpdate
*/
Phaser.Stage.prototype.preUpdate = function () {
this.currentRenderOrderID = 0;
// This can't loop in reverse, we need the renderOrderID to be in sequence
for (var i = 0; i < this.children.length; i++)
{
this.children[i].preUpdate();
}
};
/**
* This is called automatically after the State.update, but before particles or plugins update.
*
* @method Phaser.Stage#update
*/
Phaser.Stage.prototype.update = function () {
// Goes in reverse, because it's highly likely the child will destroy itself in `update`
var i = this.children.length;
while (i--)
{
this.children[i].update();
}
};
/**
* This is called automatically before the renderer runs and after the plugins have updated.
* In postUpdate this is where all the final physics calculations and object positioning happens.
* The objects are processed in the order of the display list.
*
* @method Phaser.Stage#postUpdate
*/
Phaser.Stage.prototype.postUpdate = function () {
// Apply the camera shake, fade, bounds, etc
this.game.camera.update();
// Camera target first?
if (this.game.camera.target)
{
this.game.camera.target.postUpdate();
this.updateTransform();
this.game.camera.updateTarget();
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i].postUpdate();
}
this.updateTransform();
};
/**
* Updates the transforms for all objects on the display list.
* This overrides the Pixi default as we don't need the interactionManager, but do need the game property check.
*
* @method Phaser.Stage#updateTransform
*/
Phaser.Stage.prototype.updateTransform = function () {
this.worldAlpha = 1;
for (var i = 0; i < this.children.length; i++)
{
this.children[i].updateTransform();
}
};
/**
* Starts a page visibility event listener running, or window.onpagehide/onpageshow if not supported by the browser.
* Also listens for window.onblur and window.onfocus.
*
* @method Phaser.Stage#checkVisibility
*/
Phaser.Stage.prototype.checkVisibility = function () {
if (document.hidden !== undefined)
{
this._hiddenVar = 'visibilitychange';
}
else if (document.webkitHidden !== undefined)
{
this._hiddenVar = 'webkitvisibilitychange';
}
else if (document.mozHidden !== undefined)
{
this._hiddenVar = 'mozvisibilitychange';
}
else if (document.msHidden !== undefined)
{
this._hiddenVar = 'msvisibilitychange';
}
else
{
this._hiddenVar = null;
}
var _this = this;
this._onChange = function (event) {
return _this.visibilityChange(event);
};
// Does browser support it? If not (like in IE9 or old Android) we need to fall back to blur/focus
if (this._hiddenVar)
{
document.addEventListener(this._hiddenVar, this._onChange, false);
}
window.onblur = this._onChange;
window.onfocus = this._onChange;
window.onpagehide = this._onChange;
window.onpageshow = this._onChange;
if (this.game.device.cocoonJSApp)
{
CocoonJS.App.onSuspended.addEventListener(function () {
Phaser.Stage.prototype.visibilityChange.call(_this, { type: "pause" });
});
CocoonJS.App.onActivated.addEventListener(function () {
Phaser.Stage.prototype.visibilityChange.call(_this, { type: "resume" });
});
}
};
/**
* This method is called when the document visibility is changed.
*
* @method Phaser.Stage#visibilityChange
* @param {Event} event - Its type will be used to decide whether the game should be paused or not.
*/
Phaser.Stage.prototype.visibilityChange = function (event) {
if (event.type === 'pagehide' || event.type === 'blur' || event.type === 'pageshow' || event.type === 'focus')
{
if (event.type === 'pagehide' || event.type === 'blur')
{
this.game.focusLoss(event);
}
else if (event.type === 'pageshow' || event.type === 'focus')
{
this.game.focusGain(event);
}
return;
}
if (this.disableVisibilityChange)
{
return;
}
if (document.hidden || document.mozHidden || document.msHidden || document.webkitHidden || event.type === "pause")
{
this.game.gamePaused(event);
}
else
{
this.game.gameResumed(event);
}
};
/**
* Sets the background color for the Stage.
*
* The color can be given as a hex string (`'#RRGGBB'`), a CSS color string (`'rgb(r,g,b)'`), or a numeric value (`0xRRGGBB`).
*
* An alpha channel is _not_ supported and will be ignored.
*
* If you've set your game to be transparent then calls to setBackgroundColor are ignored.
*
* @method Phaser.Stage#setBackgroundColor
* @param {number|string} color - The color of the background.
*/
Phaser.Stage.prototype.setBackgroundColor = function (color) {
if (this.game.transparent) { return; }
Phaser.Color.valueToColor(color, this._bgColor);
Phaser.Color.updateColor(this._bgColor);
// For gl.clearColor (canvas uses _bgColor.rgba)
this._bgColor.r /= 255;
this._bgColor.g /= 255;
this._bgColor.b /= 255;
this._bgColor.a = 1;
};
/**
* Destroys the Stage and removes event listeners.
*
* @method Phaser.Stage#destroy
*/
Phaser.Stage.prototype.destroy = function () {
if (this._hiddenVar)
{
document.removeEventListener(this._hiddenVar, this._onChange, false);
}
window.onpagehide = null;
window.onpageshow = null;
window.onblur = null;
window.onfocus = null;
};
/**
* @name Phaser.Stage#backgroundColor
* @property {number|string} backgroundColor - Gets and sets the background color of the stage. The color can be given as a number: 0xff0000 or a hex string: '#ff0000'
*/
Object.defineProperty(Phaser.Stage.prototype, "backgroundColor", {
get: function () {
return this._bgColor.color;
},
set: function (color) {
this.setBackgroundColor(color);
}
});
/**
* Enable or disable texture smoothing for all objects on this Stage. Only works for bitmap/image textures. Smoothing is enabled by default.
*
* @name Phaser.Stage#smoothed
* @property {boolean} smoothed - Set to true to smooth all sprites rendered on this Stage, or false to disable smoothing (great for pixel art)
*/
Object.defineProperty(Phaser.Stage.prototype, "smoothed", {
get: function () {
return PIXI.scaleModes.DEFAULT === PIXI.scaleModes.LINEAR;
},
set: function (value) {
if (value)
{
PIXI.scaleModes.DEFAULT = PIXI.scaleModes.LINEAR;
}
else
{
PIXI.scaleModes.DEFAULT = PIXI.scaleModes.NEAREST;
}
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Group is a container for {@link DisplayObject display objects} including {@link Phaser.Sprite Sprites} and {@link Phaser.Image Images}.
*
* Groups form the logical tree structure of the display/scene graph where local transformations are applied to children.
* For instance, all children are also moved/rotated/scaled when the group is moved/rotated/scaled.
*
* In addition, Groups provides support for fast pooling and object recycling.
*
* Groups are also display objects and can be nested as children within other Groups.
*
* @class Phaser.Group
* @extends PIXI.DisplayObjectContainer
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {DisplayObject|null} [parent=(game world)] - The parent Group (or other {@link DisplayObject}) that this group will be added to.
* If undefined/unspecified the Group will be added to the {@link Phaser.Game#world Game World}; if null the Group will not be added to any parent.
* @param {string} [name='group'] - A name for this group. Not used internally but useful for debugging.
* @param {boolean} [addToStage=false] - If true this group will be added directly to the Game.Stage instead of Game.World.
* @param {boolean} [enableBody=false] - If true all Sprites created with {@link #create} or {@link #createMulitple} will have a physics body created on them. Change the body type with {@link #physicsBodyType}.
* @param {integer} [physicsBodyType=0] - The physics body type to use when physics bodies are automatically added. See {@link #physicsBodyType} for values.
*/
Phaser.Group = function (game, parent, name, addToStage, enableBody, physicsBodyType) {
if (addToStage === undefined) { addToStage = false; }
if (enableBody === undefined) { enableBody = false; }
if (physicsBodyType === undefined) { physicsBodyType = Phaser.Physics.ARCADE; }
/**
* A reference to the currently running Game.
* @property {Phaser.Game} game
* @protected
*/
this.game = game;
if (parent === undefined)
{
parent = game.world;
}
/**
* A name for this group. Not used internally but useful for debugging.
* @property {string} name
*/
this.name = name || 'group';
/**
* The z-depth value of this object within its parent container/Group - the World is a Group as well.
* This value must be unique for each child in a Group.
* @property {integer} z
* @readOnly
*/
this.z = 0;
PIXI.DisplayObjectContainer.call(this);
if (addToStage)
{
this.game.stage.addChild(this);
this.z = this.game.stage.children.length;
}
else
{
if (parent)
{
parent.addChild(this);
this.z = parent.children.length;
}
}
/**
* Internal Phaser Type value.
* @property {integer} type
* @protected
*/
this.type = Phaser.GROUP;
/**
* @property {number} physicsType - The const physics body type of this object.
* @readonly
*/
this.physicsType = Phaser.GROUP;
/**
* The alive property is useful for Groups that are children of other Groups and need to be included/excluded in checks like forEachAlive.
* @property {boolean} alive
* @default
*/
this.alive = true;
/**
* If exists is true the group is updated, otherwise it is skipped.
* @property {boolean} exists
* @default
*/
this.exists = true;
/**
* A group with `ignoreDestroy` set to `true` ignores all calls to its `destroy` method.
* @property {boolean} ignoreDestroy
* @default
*/
this.ignoreDestroy = false;
/**
* A Group is that has `pendingDestroy` set to `true` is flagged to have its destroy method
* called on the next logic update.
* You can set it directly to flag the Group to be destroyed on its next update.
*
* This is extremely useful if you wish to destroy a Group from within one of its own callbacks
* or a callback of one of its children.
*
* @property {boolean} pendingDestroy
*/
this.pendingDestroy = false;
/**
* The type of objects that will be created when using {@link #create} or {@link #createMultiple}.
*
* Any object may be used but it should extend either Sprite or Image and accept the same constructor arguments:
* when a new object is created it is passed the following parameters to its constructor: `(game, x, y, key, frame)`.
*
* @property {object} classType
* @default {@link Phaser.Sprite}
*/
this.classType = Phaser.Sprite;
/**
* The current display object that the group cursor is pointing to, if any. (Can be set manually.)
*
* The cursor is a way to iterate through the children in a Group using {@link #next} and {@link #previous}.
* @property {?DisplayObject} cursor
*/
this.cursor = null;
/**
* A Group with `inputEnableChildren` set to `true` will automatically call `inputEnabled = true`
* on any children _added_ to, or _created by_, this Group.
*
* If there are children already in the Group at the time you set this property, they are not changed.
*
* @property {boolean} inputEnableChildren
* @default
*/
this.inputEnableChildren = false;
/**
* This Signal is dispatched whenever a child of this Group emits an onInputDown signal as a result
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to
* every child Sprite.
*
* This Signal is sent 2 arguments: A reference to the Sprite that triggered the signal, and
* a reference to the Pointer that caused it.
*
* @property {Phaser.Signal} onChildInputDown
*/
this.onChildInputDown = new Phaser.Signal();
/**
* This Signal is dispatched whenever a child of this Group emits an onInputUp signal as a result
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to
* every child Sprite.
*
* This Signal is sent 3 arguments: A reference to the Sprite that triggered the signal,
* a reference to the Pointer that caused it, and a boolean value `isOver` that tells you if the Pointer
* is still over the Sprite or not.
*
* @property {Phaser.Signal} onChildInputUp
*/
this.onChildInputUp = new Phaser.Signal();
/**
* This Signal is dispatched whenever a child of this Group emits an onInputOver signal as a result
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to
* every child Sprite.
*
* This Signal is sent 2 arguments: A reference to the Sprite that triggered the signal, and
* a reference to the Pointer that caused it.
*
* @property {Phaser.Signal} onChildInputOver
*/
this.onChildInputOver = new Phaser.Signal();
/**
* This Signal is dispatched whenever a child of this Group emits an onInputOut signal as a result
* of having been interacted with by a Pointer. You can bind functions to this Signal instead of to
* every child Sprite.
*
* This Signal is sent 2 arguments: A reference to the Sprite that triggered the signal, and
* a reference to the Pointer that caused it.
*
* @property {Phaser.Signal} onChildInputOut
*/
this.onChildInputOut = new Phaser.Signal();
/**
* If true all Sprites created by, or added to this group, will have a physics body enabled on them.
*
* If there are children already in the Group at the time you set this property, they are not changed.
*
* The default body type is controlled with {@link #physicsBodyType}.
* @property {boolean} enableBody
*/
this.enableBody = enableBody;
/**
* If true when a physics body is created (via {@link #enableBody}) it will create a physics debug object as well.
*
* This only works for P2 bodies.
* @property {boolean} enableBodyDebug
* @default
*/
this.enableBodyDebug = false;
/**
* If {@link #enableBody} is true this is the type of physics body that is created on new Sprites.
*
* The valid values are {@link Phaser.Physics.ARCADE}, {@link Phaser.Physics.P2JS}, {@link Phaser.Physics.NINJA}, etc.
* @property {integer} physicsBodyType
*/
this.physicsBodyType = physicsBodyType;
/**
* If this Group contains Arcade Physics Sprites you can set a custom sort direction via this property.
*
* It should be set to one of the Phaser.Physics.Arcade sort direction constants:
*
* Phaser.Physics.Arcade.SORT_NONE
* Phaser.Physics.Arcade.LEFT_RIGHT
* Phaser.Physics.Arcade.RIGHT_LEFT
* Phaser.Physics.Arcade.TOP_BOTTOM
* Phaser.Physics.Arcade.BOTTOM_TOP
*
* If set to `null` the Group will use whatever Phaser.Physics.Arcade.sortDirection is set to. This is the default behavior.
*
* @property {integer} physicsSortDirection
* @default
*/
this.physicsSortDirection = null;
/**
* This signal is dispatched when the group is destroyed.
* @property {Phaser.Signal} onDestroy
*/
this.onDestroy = new Phaser.Signal();
/**
* @property {integer} cursorIndex - The current index of the Group cursor. Advance it with Group.next.
* @readOnly
*/
this.cursorIndex = 0;
/**
* A Group that is fixed to the camera uses its x/y coordinates as offsets from the top left of the camera. These are stored in Group.cameraOffset.
*
* Note that the cameraOffset values are in addition to any parent in the display list.
* So if this Group was in a Group that has x: 200, then this will be added to the cameraOffset.x
*
* @property {boolean} fixedToCamera
*/
this.fixedToCamera = false;
/**
* If this object is {@link #fixedToCamera} then this stores the x/y position offset relative to the top-left of the camera view.
* If the parent of this Group is also `fixedToCamera` then the offset here is in addition to that and should typically be disabled.
* @property {Phaser.Point} cameraOffset
*/
this.cameraOffset = new Phaser.Point();
/**
* The hash array is an array belonging to this Group into which you can add any of its children via Group.addToHash and Group.removeFromHash.
*
* Only children of this Group can be added to and removed from the hash.
*
* This hash is used automatically by Phaser Arcade Physics in order to perform non z-index based destructive sorting.
* However if you don't use Arcade Physics, or this isn't a physics enabled Group, then you can use the hash to perform your own
* sorting and filtering of Group children without touching their z-index (and therefore display draw order)
*
* @property {array} hash
*/
this.hash = [];
/**
* The property on which children are sorted.
* @property {string} _sortProperty
* @private
*/
this._sortProperty = 'z';
};
Phaser.Group.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
Phaser.Group.prototype.constructor = Phaser.Group;
/**
* A returnType value, as specified in {@link #iterate} eg.
* @constant
* @type {integer}
*/
Phaser.Group.RETURN_NONE = 0;
/**
* A returnType value, as specified in {@link #iterate} eg.
* @constant
* @type {integer}
*/
Phaser.Group.RETURN_TOTAL = 1;
/**
* A returnType value, as specified in {@link #iterate} eg.
* @constant
* @type {integer}
*/
Phaser.Group.RETURN_CHILD = 2;
/**
* A returnType value, as specified in {@link #iterate} eg.
* @constant
* @type {integer}
*/
Phaser.Group.RETURN_ALL = 3;
/**
* A sort ordering value, as specified in {@link #sort} eg.
* @constant
* @type {integer}
*/
Phaser.Group.SORT_ASCENDING = -1;
/**
* A sort ordering value, as specified in {@link #sort} eg.
* @constant
* @type {integer}
*/
Phaser.Group.SORT_DESCENDING = 1;
/**
* Adds an existing object as the top child in this group.
*
* The child is automatically added to the top of the group, and is displayed above every previous child.
*
* Or if the _optional_ index is specified, the child is added at the location specified by the index value,
* this allows you to control child ordering.
*
* If the child was already in this Group, it is simply returned, and nothing else happens to it.
*
* If `Group.enableBody` is set, then a physics body will be created on the object, so long as one does not already exist.
*
* If `Group.inputEnableChildren` is set, then an Input Handler will be created on the object, so long as one does not already exist.
*
* Use {@link #addAt} to control where a child is added. Use {@link #create} to create and add a new child.
*
* @method Phaser.Group#add
* @param {DisplayObject} child - The display object to add as a child.
* @param {boolean} [silent=false] - If true the child will not dispatch the `onAddedToGroup` event.
* @param {integer} [index] - The index within the group to insert the child to. Where 0 is the bottom of the Group.
* @return {DisplayObject} The child that was added to the group.
*/
Phaser.Group.prototype.add = function (child, silent, index) {
if (silent === undefined) { silent = false; }
if (child.parent === this)
{
return child;
}
if (child.body && child.parent && child.parent.hash)
{
child.parent.removeFromHash(child);
}
if (index === undefined)
{
child.z = this.children.length;
this.addChild(child);
}
else
{
this.addChildAt(child, index);
this.updateZ();
}
if (this.enableBody && child.hasOwnProperty('body') && child.body === null)
{
this.game.physics.enable(child, this.physicsBodyType);
}
else if (child.body)
{
this.addToHash(child);
}
if (this.inputEnableChildren && (!child.input || child.inputEnabled))
{
child.inputEnabled = true;
}
if (!silent && child.events)
{
child.events.onAddedToGroup$dispatch(child, this);
}
if (this.cursor === null)
{
this.cursor = child;
}
return child;
};
/**
* Adds an existing object to this group.
*
* The child is added to the group at the location specified by the index value, this allows you to control child ordering.
*
* If `Group.enableBody` is set, then a physics body will be created on the object, so long as one does not already exist.
*
* If `Group.inputEnableChildren` is set, then an Input Handler will be created on the object, so long as one does not already exist.
*
* @method Phaser.Group#addAt
* @param {DisplayObject} child - The display object to add as a child.
* @param {integer} [index=0] - The index within the group to insert the child to.
* @param {boolean} [silent=false] - If true the child will not dispatch the `onAddedToGroup` event.
* @return {DisplayObject} The child that was added to the group.
*/
Phaser.Group.prototype.addAt = function (child, index, silent) {
this.add(child, silent, index);
};
/**
* Adds a child of this Group into the hash array.
* This call will return false if the child is not a child of this Group, or is already in the hash.
*
* @method Phaser.Group#addToHash
* @param {DisplayObject} child - The display object to add to this Groups hash. Must be a member of this Group already and not present in the hash.
* @return {boolean} True if the child was successfully added to the hash, otherwise false.
*/
Phaser.Group.prototype.addToHash = function (child) {
if (child.parent === this)
{
var index = this.hash.indexOf(child);
if (index === -1)
{
this.hash.push(child);
return true;
}
}
return false;
};
/**
* Removes a child of this Group from the hash array.
* This call will return false if the child is not in the hash.
*
* @method Phaser.Group#removeFromHash
* @param {DisplayObject} child - The display object to remove from this Groups hash. Must be a member of this Group and in the hash.
* @return {boolean} True if the child was successfully removed from the hash, otherwise false.
*/
Phaser.Group.prototype.removeFromHash = function (child) {
if (child)
{
var index = this.hash.indexOf(child);
if (index !== -1)
{
this.hash.splice(index, 1);
return true;
}
}
return false;
};
/**
* Adds an array of existing Display Objects to this Group.
*
* The Display Objects are automatically added to the top of this Group, and will render on-top of everything already in this Group.
*
* As well as an array you can also pass another Group as the first argument. In this case all of the children from that
* Group will be removed from it and added into this Group.
*
* If `Group.enableBody` is set, then a physics body will be created on the objects, so long as one does not already exist.
*
* If `Group.inputEnableChildren` is set, then an Input Handler will be created on the objects, so long as one does not already exist.
*
* @method Phaser.Group#addMultiple
* @param {DisplayObject[]|Phaser.Group} children - An array of display objects or a Phaser.Group. If a Group is given then *all* children will be moved from it.
* @param {boolean} [silent=false] - If true the children will not dispatch the `onAddedToGroup` event.
* @return {DisplayObject[]|Phaser.Group} The array of children or Group of children that were added to this Group.
*/
Phaser.Group.prototype.addMultiple = function (children, silent) {
if (children instanceof Phaser.Group)
{
children.moveAll(this, silent);
}
else if (Array.isArray(children))
{
for (var i = 0; i < children.length; i++)
{
this.add(children[i], silent);
}
}
return children;
};
/**
* Returns the child found at the given index within this group.
*
* @method Phaser.Group#getAt
* @param {integer} index - The index to return the child from.
* @return {DisplayObject|integer} The child that was found at the given index, or -1 for an invalid index.
*/
Phaser.Group.prototype.getAt = function (index) {
if (index < 0 || index >= this.children.length)
{
return -1;
}
else
{
return this.getChildAt(index);
}
};
/**
* Creates a new Phaser.Sprite object and adds it to the top of this group.
*
* Use {@link #classType} to change the type of object created.
*
* The child is automatically added to the top of the group, and is displayed above every previous child.
*
* Or if the _optional_ index is specified, the child is added at the location specified by the index value,
* this allows you to control child ordering.
*
* If `Group.enableBody` is set, then a physics body will be created on the object, so long as one does not already exist.
*
* If `Group.inputEnableChildren` is set, then an Input Handler will be created on the object, so long as one does not already exist.
*
* @method Phaser.Group#create
* @param {number} x - The x coordinate to display the newly created Sprite at. The value is in relation to the group.x point.
* @param {number} y - The y coordinate to display the newly created Sprite at. The value is in relation to the group.y point.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - This is the image or texture used by the Sprite 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 Sprite 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.
* @param {boolean} [exists=true] - The default exists state of the Sprite.
* @param {integer} [index] - The index within the group to insert the child to. Where 0 is the bottom of the Group.
* @return {DisplayObject} The child that was created: will be a {@link Phaser.Sprite} unless {@link #classType} has been changed.
*/
Phaser.Group.prototype.create = function (x, y, key, frame, exists, index) {
if (exists === undefined) { exists = true; }
var child = new this.classType(this.game, x, y, key, frame);
child.exists = exists;
child.visible = exists;
child.alive = exists;
return this.add(child, false, index);
};
/**
* Creates multiple Phaser.Sprite objects and adds them to the top of this Group.
*
* This method is useful if you need to quickly generate a pool of sprites, such as bullets.
*
* Use {@link #classType} to change the type of object created.
*
* You can provide an array as the `key` and / or `frame` arguments. When you do this
* it will create `quantity` Sprites for every key (and frame) in the arrays.
*
* For example:
*
* `createMultiple(25, ['ball', 'carrot'])`
*
* In the above code there are 2 keys (ball and carrot) which means that 50 sprites will be
* created in total, 25 of each. You can also have the `frame` as an array:
*
* `createMultiple(5, 'bricks', [0, 1, 2, 3])`
*
* In the above there is one key (bricks), which is a sprite sheet. The frames array tells
* this method to use frames 0, 1, 2 and 3. So in total it will create 20 sprites, because
* the quantity was set to 5, so that is 5 brick sprites of frame 0, 5 brick sprites with
* frame 1, and so on.
*
* If you set both the key and frame arguments to be arrays then understand it will create
* a total quantity of sprites equal to the size of both arrays times each other. I.e.:
*
* `createMultiple(20, ['diamonds', 'balls'], [0, 1, 2])`
*
* The above will create 20 'diamonds' of frame 0, 20 with frame 1 and 20 with frame 2.
* It will then create 20 'balls' of frame 0, 20 with frame 1 and 20 with frame 2.
* In total it will have created 120 sprites.
*
* By default the Sprites will have their `exists` property set to `false`, and they will be
* positioned at 0x0, relative to the `Group.x / y` values.
*
* If `Group.enableBody` is set, then a physics body will be created on the objects, so long as one does not already exist.
*
* If `Group.inputEnableChildren` is set, then an Input Handler will be created on the objects, so long as one does not already exist.
*
* @method Phaser.Group#createMultiple
* @param {integer} quantity - The number of Sprites to create.
* @param {string|array} key - The Cache key of the image that the Sprites will use. Or an Array of keys. See the description for details on how the quantity applies when arrays are used.
* @param {integer|string|array} [frame=0] - If the Sprite image contains multiple frames you can specify which one to use here. Or an Array of frames. See the description for details on how the quantity applies when arrays are used.
* @param {boolean} [exists=false] - The default exists state of the Sprite.
* @return {array} An array containing all of the Sprites that were created.
*/
Phaser.Group.prototype.createMultiple = function (quantity, key, frame, exists) {
if (frame === undefined) { frame = 0; }
if (exists === undefined) { exists = false; }
if (!Array.isArray(key))
{
key = [ key ];
}
if (!Array.isArray(frame))
{
frame = [ frame ];
}
var _this = this;
var children = [];
key.forEach(function(singleKey) {
frame.forEach(function(singleFrame) {
for (var i = 0; i < quantity; i++)
{
children.push(_this.create(0, 0, singleKey, singleFrame, exists));
}
});
});
return children;
};
/**
* Internal method that re-applies all of the children's Z values.
*
* This must be called whenever children ordering is altered so that their `z` indices are correctly updated.
*
* @method Phaser.Group#updateZ
* @protected
*/
Phaser.Group.prototype.updateZ = function () {
var i = this.children.length;
while (i--)
{
this.children[i].z = i;
}
};
/**
* This method iterates through all children in the Group (regardless if they are visible or exist)
* and then changes their position so they are arranged in a Grid formation. Children must have
* the `alignTo` method in order to be positioned by this call. All default Phaser Game Objects have
* this.
*
* The grid dimensions are determined by the first four arguments. The `width` and `height` arguments
* relate to the width and height of the grid respectively.
*
* For example if the Group had 100 children in it:
*
* `Group.align(10, 10, 32, 32)`
*
* This will align all of the children into a grid formation of 10x10, using 32 pixels per
* grid cell. If you want a wider grid, you could do:
*
* `Group.align(25, 4, 32, 32)`
*
* This will align the children into a grid of 25x4, again using 32 pixels per grid cell.
*
* You can choose to set _either_ the `width` or `height` value to -1. Doing so tells the method
* to keep on aligning children until there are no children left. For example if this Group had
* 48 children in it, the following:
*
* `Group.align(-1, 8, 32, 32)`
*
* ... will align the children so that there are 8 children vertically (the second argument),
* and each row will contain 6 sprites, except the last one, which will contain 5 (totaling 48)
*
* You can also do:
*
* `Group.align(10, -1, 32, 32)`
*
* In this case it will create a grid 10 wide, and as tall as it needs to be in order to fit
* all of the children in.
*
* The `position` property allows you to control where in each grid cell the child is positioned.
* This is a constant and can be one of `Phaser.TOP_LEFT` (default), `Phaser.TOP_CENTER`,
* `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`, `Phaser.CENTER`, `Phaser.RIGHT_CENTER`,
* `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER` or `Phaser.BOTTOM_RIGHT`.
*
* The final argument; `offset` lets you start the alignment from a specific child index.
*
* @method Phaser.Group#align
* @param {integer} width - The width of the grid in items (not pixels). Set to -1 for a dynamic width. If -1 then you must set an explicit height value.
* @param {integer} height - The height of the grid in items (not pixels). Set to -1 for a dynamic height. If -1 then you must set an explicit width value.
* @param {integer} cellWidth - The width of each grid cell, in pixels.
* @param {integer} cellHeight - The height of each grid cell, in pixels.
* @param {integer} [position] - The position constant. One of `Phaser.TOP_LEFT` (default), `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`, `Phaser.CENTER`, `Phaser.RIGHT_CENTER`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER` or `Phaser.BOTTOM_RIGHT`.
* @param {integer} [offset=0] - Optional index to start the alignment from. Defaults to zero, the first child in the Group, but can be set to any valid child index value.
* @return {boolean} True if the Group children were aligned, otherwise false.
*/
Phaser.Group.prototype.align = function (width, height, cellWidth, cellHeight, position, offset) {
if (position === undefined) { position = Phaser.TOP_LEFT; }
if (offset === undefined) { offset = 0; }
if (this.children.length === 0 || offset > this.children.length || (width === -1 && height === -1))
{
return false;
}
var r = new Phaser.Rectangle(0, 0, cellWidth, cellHeight);
var w = (width * cellWidth);
var h = (height * cellHeight);
for (var i = offset; i < this.children.length; i++)
{
var child = this.children[i];
if (child['alignIn'])
{
child.alignIn(r, position);
}
else
{
continue;
}
if (width === -1)
{
// We keep laying them out horizontally until we've done them all
r.y += cellHeight;
if (r.y === h)
{
r.x += cellWidth;
r.y = 0;
}
}
else if (height === -1)
{
// We keep laying them out vertically until we've done them all
r.x += cellWidth;
if (r.x === w)
{
r.x = 0;
r.y += cellHeight;
}
}
else
{
// We keep laying them out until we hit the column limit
r.x += cellWidth;
if (r.x === w)
{
r.x = 0;
r.y += cellHeight;
if (r.y === h)
{
// We've hit the column limit, so return, even if there are children left
return true;
}
}
}
}
return true;
};
/**
* Sets the group cursor to the first child in the group.
*
* If the optional index parameter is given it sets the cursor to the object at that index instead.
*
* @method Phaser.Group#resetCursor
* @param {integer} [index=0] - Set the cursor to point to a specific index.
* @return {any} The child the cursor now points to.
*/
Phaser.Group.prototype.resetCursor = function (index) {
if (index === undefined) { index = 0; }
if (index > this.children.length - 1)
{
index = 0;
}
if (this.cursor)
{
this.cursorIndex = index;
this.cursor = this.children[this.cursorIndex];
return this.cursor;
}
};
/**
* Advances the group cursor to the next (higher) object in the group.
*
* If the cursor is at the end of the group (top child) it is moved the start of the group (bottom child).
*
* @method Phaser.Group#next
* @return {any} The child the cursor now points to.
*/
Phaser.Group.prototype.next = function () {
if (this.cursor)
{
// Wrap the cursor?
if (this.cursorIndex >= this.children.length - 1)
{
this.cursorIndex = 0;
}
else
{
this.cursorIndex++;
}
this.cursor = this.children[this.cursorIndex];
return this.cursor;
}
};
/**
* Moves the group cursor to the previous (lower) child in the group.
*
* If the cursor is at the start of the group (bottom child) it is moved to the end (top child).
*
* @method Phaser.Group#previous
* @return {any} The child the cursor now points to.
*/
Phaser.Group.prototype.previous = function () {
if (this.cursor)
{
// Wrap the cursor?
if (this.cursorIndex === 0)
{
this.cursorIndex = this.children.length - 1;
}
else
{
this.cursorIndex--;
}
this.cursor = this.children[this.cursorIndex];
return this.cursor;
}
};
/**
* Swaps the position of two children in this group.
*
* Both children must be in this group, a child cannot be swapped with itself, and unparented children cannot be swapped.
*
* @method Phaser.Group#swap
* @param {any} child1 - The first child to swap.
* @param {any} child2 - The second child to swap.
*/
Phaser.Group.prototype.swap = function (child1, child2) {
this.swapChildren(child1, child2);
this.updateZ();
};
/**
* Brings the given child to the top of this group so it renders above all other children.
*
* @method Phaser.Group#bringToTop
* @param {any} child - The child to bring to the top of this group.
* @return {any} The child that was moved.
*/
Phaser.Group.prototype.bringToTop = function (child) {
if (child.parent === this && this.getIndex(child) < this.children.length)
{
this.remove(child, false, true);
this.add(child, true);
}
return child;
};
/**
* Sends the given child to the bottom of this group so it renders below all other children.
*
* @method Phaser.Group#sendToBack
* @param {any} child - The child to send to the bottom of this group.
* @return {any} The child that was moved.
*/
Phaser.Group.prototype.sendToBack = function (child) {
if (child.parent === this && this.getIndex(child) > 0)
{
this.remove(child, false, true);
this.addAt(child, 0, true);
}
return child;
};
/**
* Moves the given child up one place in this group unless it's already at the top.
*
* @method Phaser.Group#moveUp
* @param {any} child - The child to move up in the group.
* @return {any} The child that was moved.
*/
Phaser.Group.prototype.moveUp = function (child) {
if (child.parent === this && this.getIndex(child) < this.children.length - 1)
{
var a = this.getIndex(child);
var b = this.getAt(a + 1);
if (b)
{
this.swap(child, b);
}
}
return child;
};
/**
* Moves the given child down one place in this group unless it's already at the bottom.
*
* @method Phaser.Group#moveDown
* @param {any} child - The child to move down in the group.
* @return {any} The child that was moved.
*/
Phaser.Group.prototype.moveDown = function (child) {
if (child.parent === this && this.getIndex(child) > 0)
{
var a = this.getIndex(child);
var b = this.getAt(a - 1);
if (b)
{
this.swap(child, b);
}
}
return child;
};
/**
* Positions the child found at the given index within this group to the given x and y coordinates.
*
* @method Phaser.Group#xy
* @param {integer} index - The index of the child in the group to set the position of.
* @param {number} x - The new x position of the child.
* @param {number} y - The new y position of the child.
*/
Phaser.Group.prototype.xy = function (index, x, y) {
if (index < 0 || index > this.children.length)
{
return -1;
}
else
{
this.getChildAt(index).x = x;
this.getChildAt(index).y = y;
}
};
/**
* Reverses all children in this group.
*
* This operation applies only to immediate children and does not propagate to subgroups.
*
* @method Phaser.Group#reverse
*/
Phaser.Group.prototype.reverse = function () {
this.children.reverse();
this.updateZ();
};
/**
* Get the index position of the given child in this group, which should match the child's `z` property.
*
* @method Phaser.Group#getIndex
* @param {any} child - The child to get the index for.
* @return {integer} The index of the child or -1 if it's not a member of this group.
*/
Phaser.Group.prototype.getIndex = function (child) {
return this.children.indexOf(child);
};
/**
* Searches the Group for the first instance of a child with the `name`
* property matching the given argument. Should more than one child have
* the same name only the first instance is returned.
*
* @method Phaser.Group#getByName
* @param {string} name - The name to search for.
* @return {any} The first child with a matching name, or null if none were found.
*/
Phaser.Group.prototype.getByName = function (name) {
for (var i = 0; i < this.children.length; i++)
{
if (this.children[i].name === name)
{
return this.children[i];
}
}
return null;
};
/**
* Replaces a child of this Group with the given newChild. The newChild cannot be a member of this Group.
*
* If `Group.enableBody` is set, then a physics body will be created on the object, so long as one does not already exist.
*
* If `Group.inputEnableChildren` is set, then an Input Handler will be created on the object, so long as one does not already exist.
*
* @method Phaser.Group#replace
* @param {any} oldChild - The child in this group that will be replaced.
* @param {any} newChild - The child to be inserted into this group.
* @return {any} Returns the oldChild that was replaced within this group.
*/
Phaser.Group.prototype.replace = function (oldChild, newChild) {
var index = this.getIndex(oldChild);
if (index !== -1)
{
if (newChild.parent)
{
if (newChild.parent instanceof Phaser.Group)
{
newChild.parent.remove(newChild);
}
else
{
newChild.parent.removeChild(newChild);
}
}
this.remove(oldChild);
this.addAt(newChild, index);
return oldChild;
}
};
/**
* Checks if the child has the given property.
*
* Will scan up to 4 levels deep only.
*
* @method Phaser.Group#hasProperty
* @param {any} child - The child to check for the existence of the property on.
* @param {string[]} key - An array of strings that make up the property.
* @return {boolean} True if the child has the property, otherwise false.
*/
Phaser.Group.prototype.hasProperty = function (child, key) {
var len = key.length;
if (len === 1 && key[0] in child)
{
return true;
}
else if (len === 2 && key[0] in child && key[1] in child[key[0]])
{
return true;
}
else if (len === 3 && key[0] in child && key[1] in child[key[0]] && key[2] in child[key[0]][key[1]])
{
return true;
}
else if (len === 4 && key[0] in child && key[1] in child[key[0]] && key[2] in child[key[0]][key[1]] && key[3] in child[key[0]][key[1]][key[2]])
{
return true;
}
return false;
};
/**
* Sets a property to the given value on the child. The operation parameter controls how the value is set.
*
* The operations are:
* - 0: set the existing value to the given value; if force is `true` a new property will be created if needed
* - 1: will add the given value to the value already present.
* - 2: will subtract the given value from the value already present.
* - 3: will multiply the value already present by the given value.
* - 4: will divide the value already present by the given value.
*
* @method Phaser.Group#setProperty
* @param {any} child - The child to set the property value on.
* @param {array} key - An array of strings that make up the property that will be set.
* @param {any} value - The value that will be set.
* @param {integer} [operation=0] - Controls how the value is assigned. A value of 0 replaces the value with the new one. A value of 1 adds it, 2 subtracts it, 3 multiplies it and 4 divides it.
* @param {boolean} [force=false] - If `force` is true then the property will be set on the child regardless if it already exists or not. If false and the property doesn't exist, nothing will be set.
* @return {boolean} True if the property was set, false if not.
*/
Phaser.Group.prototype.setProperty = function (child, key, value, operation, force) {
if (force === undefined) { force = false; }
operation = operation || 0;
// As ugly as this approach looks, and although it's limited to a depth of only 4, it's much faster than a for loop or object iteration.
// 0 = Equals
// 1 = Add
// 2 = Subtract
// 3 = Multiply
// 4 = Divide
// We can't force a property in and the child doesn't have it, so abort.
// Equally we can't add, subtract, multiply or divide a property value if it doesn't exist, so abort in those cases too.
if (!this.hasProperty(child, key) && (!force || operation > 0))
{
return false;
}
var len = key.length;
if (len === 1)
{
if (operation === 0) { child[key[0]] = value; }
else if (operation === 1) { child[key[0]] += value; }
else if (operation === 2) { child[key[0]] -= value; }
else if (operation === 3) { child[key[0]] *= value; }
else if (operation === 4) { child[key[0]] /= value; }
}
else if (len === 2)
{
if (operation === 0) { child[key[0]][key[1]] = value; }
else if (operation === 1) { child[key[0]][key[1]] += value; }
else if (operation === 2) { child[key[0]][key[1]] -= value; }
else if (operation === 3) { child[key[0]][key[1]] *= value; }
else if (operation === 4) { child[key[0]][key[1]] /= value; }
}
else if (len === 3)
{
if (operation === 0) { child[key[0]][key[1]][key[2]] = value; }
else if (operation === 1) { child[key[0]][key[1]][key[2]] += value; }
else if (operation === 2) { child[key[0]][key[1]][key[2]] -= value; }
else if (operation === 3) { child[key[0]][key[1]][key[2]] *= value; }
else if (operation === 4) { child[key[0]][key[1]][key[2]] /= value; }
}
else if (len === 4)
{
if (operation === 0) { child[key[0]][key[1]][key[2]][key[3]] = value; }
else if (operation === 1) { child[key[0]][key[1]][key[2]][key[3]] += value; }
else if (operation === 2) { child[key[0]][key[1]][key[2]][key[3]] -= value; }
else if (operation === 3) { child[key[0]][key[1]][key[2]][key[3]] *= value; }
else if (operation === 4) { child[key[0]][key[1]][key[2]][key[3]] /= value; }
}
return true;
};
/**
* Checks a property for the given value on the child.
*
* @method Phaser.Group#checkProperty
* @param {any} child - The child to check the property value on.
* @param {array} key - An array of strings that make up the property that will be set.
* @param {any} value - The value that will be checked.
* @param {boolean} [force=false] - If `force` is true then the property will be checked on the child regardless if it already exists or not. If true and the property doesn't exist, false will be returned.
* @return {boolean} True if the property was was equal to value, false if not.
*/
Phaser.Group.prototype.checkProperty = function (child, key, value, force) {
if (force === undefined) { force = false; }
// We can't force a property in and the child doesn't have it, so abort.
if (!Phaser.Utils.getProperty(child, key) && force)
{
return false;
}
if (Phaser.Utils.getProperty(child, key) !== value)
{
return false;
}
return true;
};
/**
* Quickly set a property on a single child of this group to a new value.
*
* The operation parameter controls how the new value is assigned to the property, from simple replacement to addition and multiplication.
*
* @method Phaser.Group#set
* @param {Phaser.Sprite} child - The child to set the property on.
* @param {string} key - The property, as a string, to be set. For example: 'body.velocity.x'
* @param {any} value - The value that will be set.
* @param {boolean} [checkAlive=false] - If set then the child will only be updated if alive=true.
* @param {boolean} [checkVisible=false] - If set then the child will only be updated if visible=true.
* @param {integer} [operation=0] - Controls how the value is assigned. A value of 0 replaces the value with the new one. A value of 1 adds it, 2 subtracts it, 3 multiplies it and 4 divides it.
* @param {boolean} [force=false] - If `force` is true then the property will be set on the child regardless if it already exists or not. If false and the property doesn't exist, nothing will be set.
* @return {boolean} True if the property was set, false if not.
*/
Phaser.Group.prototype.set = function (child, key, value, checkAlive, checkVisible, operation, force) {
if (force === undefined) { force = false; }
key = key.split('.');
if (checkAlive === undefined) { checkAlive = false; }
if (checkVisible === undefined) { checkVisible = false; }
if ((checkAlive === false || (checkAlive && child.alive)) && (checkVisible === false || (checkVisible && child.visible)))
{
return this.setProperty(child, key, value, operation, force);
}
};
/**
* Quickly set the same property across all children of this group to a new value.
*
* This call doesn't descend down children, so if you have a Group inside of this group, the property will be set on the group but not its children.
* If you need that ability please see `Group.setAllChildren`.
*
* The operation parameter controls how the new value is assigned to the property, from simple replacement to addition and multiplication.
*
* @method Phaser.Group#setAll
* @param {string} key - The property, as a string, to be set. For example: 'body.velocity.x'
* @param {any} value - The value that will be set.
* @param {boolean} [checkAlive=false] - If set then only children with alive=true will be updated. This includes any Groups that are children.
* @param {boolean} [checkVisible=false] - If set then only children with visible=true will be updated. This includes any Groups that are children.
* @param {integer} [operation=0] - Controls how the value is assigned. A value of 0 replaces the value with the new one. A value of 1 adds it, 2 subtracts it, 3 multiplies it and 4 divides it.
* @param {boolean} [force=false] - If `force` is true then the property will be set on the child regardless if it already exists or not. If false and the property doesn't exist, nothing will be set.
*/
Phaser.Group.prototype.setAll = function (key, value, checkAlive, checkVisible, operation, force) {
if (checkAlive === undefined) { checkAlive = false; }
if (checkVisible === undefined) { checkVisible = false; }
if (force === undefined) { force = false; }
key = key.split('.');
operation = operation || 0;
for (var i = 0; i < this.children.length; i++)
{
if ((!checkAlive || (checkAlive && this.children[i].alive)) && (!checkVisible || (checkVisible && this.children[i].visible)))
{
this.setProperty(this.children[i], key, value, operation, force);
}
}
};
/**
* Quickly set the same property across all children of this group, and any child Groups, to a new value.
*
* If this group contains other Groups then the same property is set across their children as well, iterating down until it reaches the bottom.
* Unlike with `setAll` the property is NOT set on child Groups itself.
*
* The operation parameter controls how the new value is assigned to the property, from simple replacement to addition and multiplication.
*
* @method Phaser.Group#setAllChildren
* @param {string} key - The property, as a string, to be set. For example: 'body.velocity.x'
* @param {any} value - The value that will be set.
* @param {boolean} [checkAlive=false] - If set then only children with alive=true will be updated. This includes any Groups that are children.
* @param {boolean} [checkVisible=false] - If set then only children with visible=true will be updated. This includes any Groups that are children.
* @param {integer} [operation=0] - Controls how the value is assigned. A value of 0 replaces the value with the new one. A value of 1 adds it, 2 subtracts it, 3 multiplies it and 4 divides it.
* @param {boolean} [force=false] - If `force` is true then the property will be set on the child regardless if it already exists or not. If false and the property doesn't exist, nothing will be set.
*/
Phaser.Group.prototype.setAllChildren = function (key, value, checkAlive, checkVisible, operation, force) {
if (checkAlive === undefined) { checkAlive = false; }
if (checkVisible === undefined) { checkVisible = false; }
if (force === undefined) { force = false; }
operation = operation || 0;
for (var i = 0; i < this.children.length; i++)
{
if ((!checkAlive || (checkAlive && this.children[i].alive)) && (!checkVisible || (checkVisible && this.children[i].visible)))
{
if (this.children[i] instanceof Phaser.Group)
{
this.children[i].setAllChildren(key, value, checkAlive, checkVisible, operation, force);
}
else
{
this.setProperty(this.children[i], key.split('.'), value, operation, force);
}
}
}
};
/**
* Quickly check that the same property across all children of this group is equal to the given value.
*
* This call doesn't descend down children, so if you have a Group inside of this group, the property will be checked on the group but not its children.
*
* @method Phaser.Group#checkAll
* @param {string} key - The property, as a string, to be set. For example: 'body.velocity.x'
* @param {any} value - The value that will be checked.
* @param {boolean} [checkAlive=false] - If set then only children with alive=true will be checked. This includes any Groups that are children.
* @param {boolean} [checkVisible=false] - If set then only children with visible=true will be checked. This includes any Groups that are children.
* @param {boolean} [force=false] - If `force` is true then the property will be checked on the child regardless if it already exists or not. If true and the property doesn't exist, false will be returned.
*/
Phaser.Group.prototype.checkAll = function (key, value, checkAlive, checkVisible, force) {
if (checkAlive === undefined) { checkAlive = false; }
if (checkVisible === undefined) { checkVisible = false; }
if (force === undefined) { force = false; }
for (var i = 0; i < this.children.length; i++)
{
if ((!checkAlive || (checkAlive && this.children[i].alive)) && (!checkVisible || (checkVisible && this.children[i].visible)))
{
if (!this.checkProperty(this.children[i], key, value, force))
{
return false;
}
}
}
return true;
};
/**
* Adds the amount to the given property on all children in this group.
*
* `Group.addAll('x', 10)` will add 10 to the child.x value for each child.
*
* @method Phaser.Group#addAll
* @param {string} property - The property to increment, for example 'body.velocity.x' or 'angle'.
* @param {number} amount - The amount to increment the property by. If child.x = 10 then addAll('x', 40) would make child.x = 50.
* @param {boolean} checkAlive - If true the property will only be changed if the child is alive.
* @param {boolean} checkVisible - If true the property will only be changed if the child is visible.
*/
Phaser.Group.prototype.addAll = function (property, amount, checkAlive, checkVisible) {
this.setAll(property, amount, checkAlive, checkVisible, 1);
};
/**
* Subtracts the amount from the given property on all children in this group.
*
* `Group.subAll('x', 10)` will minus 10 from the child.x value for each child.
*
* @method Phaser.Group#subAll
* @param {string} property - The property to decrement, for example 'body.velocity.x' or 'angle'.
* @param {number} amount - The amount to subtract from the property. If child.x = 50 then subAll('x', 40) would make child.x = 10.
* @param {boolean} checkAlive - If true the property will only be changed if the child is alive.
* @param {boolean} checkVisible - If true the property will only be changed if the child is visible.
*/
Phaser.Group.prototype.subAll = function (property, amount, checkAlive, checkVisible) {
this.setAll(property, amount, checkAlive, checkVisible, 2);
};
/**
* Multiplies the given property by the amount on all children in this group.
*
* `Group.multiplyAll('x', 2)` will x2 the child.x value for each child.
*
* @method Phaser.Group#multiplyAll
* @param {string} property - The property to multiply, for example 'body.velocity.x' or 'angle'.
* @param {number} amount - The amount to multiply the property by. If child.x = 10 then multiplyAll('x', 2) would make child.x = 20.
* @param {boolean} checkAlive - If true the property will only be changed if the child is alive.
* @param {boolean} checkVisible - If true the property will only be changed if the child is visible.
*/
Phaser.Group.prototype.multiplyAll = function (property, amount, checkAlive, checkVisible) {
this.setAll(property, amount, checkAlive, checkVisible, 3);
};
/**
* Divides the given property by the amount on all children in this group.
*
* `Group.divideAll('x', 2)` will half the child.x value for each child.
*
* @method Phaser.Group#divideAll
* @param {string} property - The property to divide, for example 'body.velocity.x' or 'angle'.
* @param {number} amount - The amount to divide the property by. If child.x = 100 then divideAll('x', 2) would make child.x = 50.
* @param {boolean} checkAlive - If true the property will only be changed if the child is alive.
* @param {boolean} checkVisible - If true the property will only be changed if the child is visible.
*/
Phaser.Group.prototype.divideAll = function (property, amount, checkAlive, checkVisible) {
this.setAll(property, amount, checkAlive, checkVisible, 4);
};
/**
* Calls a function, specified by name, on all children in the group who exist (or do not exist).
*
* After the existsValue parameter you can add as many parameters as you like, which will all be passed to the child callback.
*
* @method Phaser.Group#callAllExists
* @param {string} callback - Name of the function on the children to call.
* @param {boolean} existsValue - Only children with exists=existsValue will be called.
* @param {...any} parameter - Additional parameters that will be passed to the callback.
*/
Phaser.Group.prototype.callAllExists = function (callback, existsValue) {
var args;
if (arguments.length > 2)
{
args = [];
for (var i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
}
for (var i = 0; i < this.children.length; i++)
{
if (this.children[i].exists === existsValue && this.children[i][callback])
{
this.children[i][callback].apply(this.children[i], args);
}
}
};
/**
* Returns a reference to a function that exists on a child of the group based on the given callback array.
*
* @method Phaser.Group#callbackFromArray
* @param {object} child - The object to inspect.
* @param {array} callback - The array of function names.
* @param {integer} length - The size of the array (pre-calculated in callAll).
* @protected
*/
Phaser.Group.prototype.callbackFromArray = function (child, callback, length) {
// Kinda looks like a Christmas tree
if (length === 1)
{
if (child[callback[0]])
{
return child[callback[0]];
}
}
else if (length === 2)
{
if (child[callback[0]][callback[1]])
{
return child[callback[0]][callback[1]];
}
}
else if (length === 3)
{
if (child[callback[0]][callback[1]][callback[2]])
{
return child[callback[0]][callback[1]][callback[2]];
}
}
else if (length === 4)
{
if (child[callback[0]][callback[1]][callback[2]][callback[3]])
{
return child[callback[0]][callback[1]][callback[2]][callback[3]];
}
}
else if (child[callback])
{
return child[callback];
}
return false;
};
/**
* Calls a function, specified by name, on all on children.
*
* The function is called for all children regardless if they are dead or alive (see callAllExists for different options).
* After the method parameter and context you can add as many extra parameters as you like, which will all be passed to the child.
*
* @method Phaser.Group#callAll
* @param {string} method - Name of the function on the child to call. Deep property lookup is supported.
* @param {string} [context=null] - A string containing the context under which the method will be executed. Set to null to default to the child.
* @param {...any} args - Additional parameters that will be passed to the method.
*/
Phaser.Group.prototype.callAll = function (method, context) {
if (method === undefined)
{
return;
}
// Extract the method into an array
method = method.split('.');
var methodLength = method.length;
if (context === undefined || context === null || context === '')
{
context = null;
}
else
{
// Extract the context into an array
if (typeof context === 'string')
{
context = context.split('.');
var contextLength = context.length;
}
}
var args;
if (arguments.length > 2)
{
args = [];
for (var i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
}
var callback = null;
var callbackContext = null;
for (var i = 0; i < this.children.length; i++)
{
callback = this.callbackFromArray(this.children[i], method, methodLength);
if (context && callback)
{
callbackContext = this.callbackFromArray(this.children[i], context, contextLength);
if (callback)
{
callback.apply(callbackContext, args);
}
}
else if (callback)
{
callback.apply(this.children[i], args);
}
}
};
/**
* The core preUpdate - as called by World.
* @method Phaser.Group#preUpdate
* @protected
*/
Phaser.Group.prototype.preUpdate = function () {
if (this.pendingDestroy)
{
this.destroy();
return false;
}
if (!this.exists || !this.parent.exists)
{
this.renderOrderID = -1;
return false;
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i].preUpdate();
}
return true;
};
/**
* The core update - as called by World.
* @method Phaser.Group#update
* @protected
*/
Phaser.Group.prototype.update = function () {
// Goes in reverse, because it's highly likely the child will destroy itself in `update`
var i = this.children.length;
while (i--)
{
this.children[i].update();
}
};
/**
* The core postUpdate - as called by World.
* @method Phaser.Group#postUpdate
* @protected
*/
Phaser.Group.prototype.postUpdate = function () {
// Fixed to Camera?
if (this.fixedToCamera)
{
this.x = (this.game.camera.view.x + this.cameraOffset.x) / this.game.camera.scale.x;
this.y = (this.game.camera.view.y + this.cameraOffset.y) / this.game.camera.scale.y;
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i].postUpdate();
}
};
/**
* Find children matching a certain predicate.
*
* For example:
*
* var healthyList = Group.filter(function(child, index, children) {
* return child.health > 10 ? true : false;
* }, true);
* healthyList.callAll('attack');
*
* Note: Currently this will skip any children which are Groups themselves.
*
* @method Phaser.Group#filter
* @param {function} predicate - The function that each child will be evaluated against. Each child of the group will be passed to it as its first parameter, the index as the second, and the entire child array as the third
* @param {boolean} [checkExists=false] - If true, only existing can be selected; otherwise all children can be selected and will be passed to the predicate.
* @return {Phaser.ArraySet} Returns an array list containing all the children that the predicate returned true for
*/
Phaser.Group.prototype.filter = function (predicate, checkExists) {
var index = -1;
var length = this.children.length;
var results = [];
while (++index < length)
{
var child = this.children[index];
if (!checkExists || (checkExists && child.exists))
{
if (predicate(child, index, this.children))
{
results.push(child);
}
}
}
return new Phaser.ArraySet(results);
};
/**
* Call a function on each child in this group.
*
* Additional arguments for the callback can be specified after the `checkExists` parameter. For example,
*
* Group.forEach(awardBonusGold, this, true, 100, 500)
*
* would invoke `awardBonusGold` function with the parameters `(child, 100, 500)`.
*
* Note: This check will skip any children which are Groups themselves.
*
* @method Phaser.Group#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 {boolean} [checkExists=false] - If set only children matching for which `exists` is true will be passed to the callback, otherwise all children will be passed.
* @param {...any} [args=(none)] - Additional arguments to pass to the callback function, after the child item.
*/
Phaser.Group.prototype.forEach = function (callback, callbackContext, checkExists) {
if (checkExists === undefined) { checkExists = false; }
if (arguments.length <= 3)
{
for (var i = 0; i < this.children.length; i++)
{
if (!checkExists || (checkExists && this.children[i].exists))
{
callback.call(callbackContext, this.children[i]);
}
}
}
else
{
// Assigning to arguments properties causes Extreme Deoptimization in Chrome, FF, and IE.
// Using an array and pushing each element (not a slice!) is _significantly_ faster.
var args = [null];
for (var i = 3; i < arguments.length; i++)
{
args.push(arguments[i]);
}
for (var i = 0; i < this.children.length; i++)
{
if (!checkExists || (checkExists && this.children[i].exists))
{
args[0] = this.children[i];
callback.apply(callbackContext, args);
}
}
}
};
/**
* Call a function on each existing child in this group.
*
* See {@link Phaser.Group#forEach forEach} for details.
*
* @method Phaser.Group#forEachExists
* @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.
*/
Phaser.Group.prototype.forEachExists = function (callback, callbackContext) {
var args;
if (arguments.length > 2)
{
args = [null];
for (var i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
}
this.iterate('exists', true, Phaser.Group.RETURN_TOTAL, callback, callbackContext, args);
};
/**
* Call a function on each alive child in this group.
*
* See {@link Phaser.Group#forEach forEach} for details.
*
* @method Phaser.Group#forEachAlive
* @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.
*/
Phaser.Group.prototype.forEachAlive = function (callback, callbackContext) {
var args;
if (arguments.length > 2)
{
args = [null];
for (var i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
}
this.iterate('alive', true, Phaser.Group.RETURN_TOTAL, callback, callbackContext, args);
};
/**
* Call a function on each dead child in this group.
*
* See {@link Phaser.Group#forEach forEach} for details.
*
* @method Phaser.Group#forEachDead
* @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.
*/
Phaser.Group.prototype.forEachDead = function (callback, callbackContext) {
var args;
if (arguments.length > 2)
{
args = [null];
for (var i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
}
this.iterate('alive', false, Phaser.Group.RETURN_TOTAL, callback, callbackContext, args);
};
/**
* Sort the children in the group according to a particular key and ordering.
*
* Call this function to sort the group according to a particular key value and order.
*
* For example to depth sort Sprites for Zelda-style game you might call `group.sort('y', Phaser.Group.SORT_ASCENDING)` at the bottom of your `State.update()`.
*
* Internally this uses a standard JavaScript Array sort, so everything that applies there also applies here, including
* alphabetical sorting, mixing strings and numbers, and Unicode sorting. See MDN for more details.
*
* @method Phaser.Group#sort
* @param {string} [key='z'] - The name of the property to sort on. Defaults to the objects z-depth value.
* @param {integer} [order=Phaser.Group.SORT_ASCENDING] - Order ascending ({@link Phaser.Group.SORT_ASCENDING SORT_ASCENDING}) or descending ({@link Phaser.Group.SORT_DESCENDING SORT_DESCENDING}).
*/
Phaser.Group.prototype.sort = function (key, order) {
if (this.children.length < 2)
{
// Nothing to swap
return;
}
if (key === undefined) { key = 'z'; }
if (order === undefined) { order = Phaser.Group.SORT_ASCENDING; }
this._sortProperty = key;
if (order === Phaser.Group.SORT_ASCENDING)
{
this.children.sort(this.ascendingSortHandler.bind(this));
}
else
{
this.children.sort(this.descendingSortHandler.bind(this));
}
this.updateZ();
};
/**
* Sort the children in the group according to custom sort function.
*
* The `sortHandler` is provided the two parameters: the two children involved in the comparison (a and b).
* It should return -1 if `a > b`, 1 if `a < b` or 0 if `a === b`.
*
* @method Phaser.Group#customSort
* @param {function} sortHandler - The custom sort function.
* @param {object} [context=undefined] - The context in which the sortHandler is called.
*/
Phaser.Group.prototype.customSort = function (sortHandler, context) {
if (this.children.length < 2)
{
// Nothing to swap
return;
}
this.children.sort(sortHandler.bind(context));
this.updateZ();
};
/**
* An internal helper function for the sort process.
*
* @method Phaser.Group#ascendingSortHandler
* @protected
* @param {object} a - The first object being sorted.
* @param {object} b - The second object being sorted.
*/
Phaser.Group.prototype.ascendingSortHandler = function (a, b) {
if (a[this._sortProperty] < b[this._sortProperty])
{
return -1;
}
else if (a[this._sortProperty] > b[this._sortProperty])
{
return 1;
}
else
{
if (a.z < b.z)
{
return -1;
}
else
{
return 1;
}
}
};
/**
* An internal helper function for the sort process.
*
* @method Phaser.Group#descendingSortHandler
* @protected
* @param {object} a - The first object being sorted.
* @param {object} b - The second object being sorted.
*/
Phaser.Group.prototype.descendingSortHandler = function (a, b) {
if (a[this._sortProperty] < b[this._sortProperty])
{
return 1;
}
else if (a[this._sortProperty] > b[this._sortProperty])
{
return -1;
}
else
{
return 0;
}
};
/**
* Iterates over the children of the group performing one of several actions for matched children.
*
* A child is considered a match when it has a property, named `key`, whose value is equal to `value`
* according to a strict equality comparison.
*
* The result depends on the `returnType`:
*
* - {@link Phaser.Group.RETURN_TOTAL RETURN_TOTAL}:
* The callback, if any, is applied to all matching children. The number of matched children is returned.
* - {@link Phaser.Group.RETURN_NONE RETURN_NONE}:
* The callback, if any, is applied to all matching children. No value is returned.
* - {@link Phaser.Group.RETURN_CHILD RETURN_CHILD}:
* The callback, if any, is applied to the *first* matching child and the *first* matched child is returned.
* If there is no matching child then null is returned.
*
* If `args` is specified it must be an array. The matched child will be assigned to the first
* element and the entire array will be applied to the callback function.
*
* @method Phaser.Group#iterate
* @param {string} key - The child property to check, i.e. 'exists', 'alive', 'health'
* @param {any} value - A child matches if `child[key] === value` is true.
* @param {integer} returnType - How to iterate the children and what to return.
* @param {function} [callback=null] - Optional function that will be called on each matching child. The matched child is supplied as the first argument.
* @param {object} [callbackContext] - The context in which the function should be called (usually 'this').
* @param {any[]} [args=(none)] - The arguments supplied to to the callback; the first array index (argument) will be replaced with the matched child.
* @return {any} Returns either an integer (for RETURN_TOTAL), the first matched child (for RETURN_CHILD), or null.
*/
Phaser.Group.prototype.iterate = function (key, value, returnType, callback, callbackContext, args) {
if (this.children.length === 0)
{
if (returnType === Phaser.Group.RETURN_TOTAL)
{
return 0;
}
else if (returnType === Phaser.Group.RETURN_ALL)
{
return [];
}
}
var total = 0;
if (returnType === Phaser.Group.RETURN_ALL)
{
var output = [];
}
for (var i = 0; i < this.children.length; i++)
{
if (this.children[i][key] === value)
{
total++;
if (callback)
{
if (args)
{
args[0] = this.children[i];
callback.apply(callbackContext, args);
}
else
{
callback.call(callbackContext, this.children[i]);
}
}
if (returnType === Phaser.Group.RETURN_CHILD)
{
return this.children[i];
}
else if (returnType === Phaser.Group.RETURN_ALL)
{
output.push(this.children[i]);
}
}
}
if (returnType === Phaser.Group.RETURN_TOTAL)
{
return total;
}
else if (returnType === Phaser.Group.RETURN_ALL)
{
return output;
}
else
{
// RETURN_CHILD or RETURN_NONE
return null;
}
};
/**
* Get the first display object that exists, or doesn't exist.
*
* You can use the optional argument `createIfNull` to create a new Game Object if none matching your exists argument were found in this Group.
*
* It works by calling `Group.create` passing it the parameters given to this method, and returning the new child.
*
* If a child *was* found , `createIfNull` is `false` and you provided the additional arguments then the child
* will be reset and/or have a new texture loaded on it. This is handled by `Group.resetChild`.
*
* @method Phaser.Group#getFirstExists
* @param {boolean} [exists=true] - If true, find the first existing child; otherwise find the first non-existing child.
* @param {boolean} [createIfNull=false] - If `true` and no alive children are found a new one is created.
* @param {number} [x] - The x coordinate to reset the child to. The value is in relation to the group.x point.
* @param {number} [y] - The y coordinate to reset the child to. The value is in relation to the group.y point.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - This is the image or texture used by the Sprite 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 Sprite 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 {DisplayObject} The first child, or `null` if none found and `createIfNull` was false.
*/
Phaser.Group.prototype.getFirstExists = function (exists, createIfNull, x, y, key, frame) {
if (createIfNull === undefined) { createIfNull = false; }
if (typeof exists !== 'boolean')
{
exists = true;
}
var child = this.iterate('exists', exists, Phaser.Group.RETURN_CHILD);
return (child === null && createIfNull) ? this.create(x, y, key, frame) : this.resetChild(child, x, y, key, frame);
};
/**
* Get the first child that is alive (`child.alive === true`).
*
* This is handy for choosing a squad leader, etc.
*
* You can use the optional argument `createIfNull` to create a new Game Object if no alive ones were found in this Group.
*
* It works by calling `Group.create` passing it the parameters given to this method, and returning the new child.
*
* If a child *was* found , `createIfNull` is `false` and you provided the additional arguments then the child
* will be reset and/or have a new texture loaded on it. This is handled by `Group.resetChild`.
*
* @method Phaser.Group#getFirstAlive
* @param {boolean} [createIfNull=false] - If `true` and no alive children are found a new one is created.
* @param {number} [x] - The x coordinate to reset the child to. The value is in relation to the group.x point.
* @param {number} [y] - The y coordinate to reset the child to. The value is in relation to the group.y point.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - This is the image or texture used by the Sprite 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 Sprite 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 {DisplayObject} The alive dead child, or `null` if none found and `createIfNull` was false.
*/
Phaser.Group.prototype.getFirstAlive = function (createIfNull, x, y, key, frame) {
if (createIfNull === undefined) { createIfNull = false; }
var child = this.iterate('alive', true, Phaser.Group.RETURN_CHILD);
return (child === null && createIfNull) ? this.create(x, y, key, frame) : this.resetChild(child, x, y, key, frame);
};
/**
* Get the first child that is dead (`child.alive === false`).
*
* This is handy for checking if everything has been wiped out and adding to the pool as needed.
*
* You can use the optional argument `createIfNull` to create a new Game Object if no dead ones were found in this Group.
*
* It works by calling `Group.create` passing it the parameters given to this method, and returning the new child.
*
* If a child *was* found , `createIfNull` is `false` and you provided the additional arguments then the child
* will be reset and/or have a new texture loaded on it. This is handled by `Group.resetChild`.
*
* @method Phaser.Group#getFirstDead
* @param {boolean} [createIfNull=false] - If `true` and no dead children are found a new one is created.
* @param {number} [x] - The x coordinate to reset the child to. The value is in relation to the group.x point.
* @param {number} [y] - The y coordinate to reset the child to. The value is in relation to the group.y point.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - This is the image or texture used by the Sprite 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 Sprite 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 {DisplayObject} The first dead child, or `null` if none found and `createIfNull` was false.
*/
Phaser.Group.prototype.getFirstDead = function (createIfNull, x, y, key, frame) {
if (createIfNull === undefined) { createIfNull = false; }
var child = this.iterate('alive', false, Phaser.Group.RETURN_CHILD);
return (child === null && createIfNull) ? this.create(x, y, key, frame) : this.resetChild(child, x, y, key, frame);
};
/**
* Takes a child and if the `x` and `y` arguments are given it calls `child.reset(x, y)` on it.
*
* If the `key` and optionally the `frame` arguments are given, it calls `child.loadTexture(key, frame)` on it.
*
* The two operations are separate. For example if you just wish to load a new texture then pass `null` as the x and y values.
*
* @method Phaser.Group#resetChild
* @param {DisplayObject} child - The child to reset and/or load the texture on.
* @param {number} [x] - The x coordinate to reset the child to. The value is in relation to the group.x point.
* @param {number} [y] - The y coordinate to reset the child to. The value is in relation to the group.y point.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - This is the image or texture used by the Sprite 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 Sprite 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 {DisplayObject} The child that was reset: usually a {@link Phaser.Sprite}.
*/
Phaser.Group.prototype.resetChild = function (child, x, y, key, frame) {
if (child === null)
{
return null;
}
if (x === undefined) { x = null; }
if (y === undefined) { y = null; }
if (x !== null && y !== null)
{
child.reset(x, y);
}
if (key !== undefined)
{
child.loadTexture(key, frame);
}
return child;
};
/**
* Return the child at the top of this group.
*
* The top child is the child displayed (rendered) above every other child.
*
* @method Phaser.Group#getTop
* @return {any} The child at the top of the Group.
*/
Phaser.Group.prototype.getTop = function () {
if (this.children.length > 0)
{
return this.children[this.children.length - 1];
}
};
/**
* Returns the child at the bottom of this group.
*
* The bottom child the child being displayed (rendered) below every other child.
*
* @method Phaser.Group#getBottom
* @return {any} The child at the bottom of the Group.
*/
Phaser.Group.prototype.getBottom = function () {
if (this.children.length > 0)
{
return this.children[0];
}
};
/**
* Get the closest child to given Object, with optional callback to filter children.
*
* This can be a Sprite, Group, Image or any object with public x and y properties.
*
* 'close' is determined by the distance from the objects `x` and `y` properties compared to the childs `x` and `y` properties.
*
* You can use the optional `callback` argument to apply your own filter to the distance checks.
* If the child is closer then the previous child, it will be sent to `callback` as the first argument,
* with the distance as the second. The callback should return `true` if it passes your
* filtering criteria, otherwise it should return `false`.
*
* @method Phaser.Group#getClosestTo
* @param {any} object - The object used to determine the distance. This can be a Sprite, Group, Image or any object with public x and y properties.
* @param {function} [callback] - The function that each child will be evaluated against. Each child of the group will be passed to it as its first parameter, with the distance as the second. It should return `true` if the child passes the matching criteria.
* @param {object} [callbackContext] - The context in which the function should be called (usually 'this').
* @return {any} The child closest to given object, or `null` if no child was found.
*/
Phaser.Group.prototype.getClosestTo = function (object, callback, callbackContext) {
var distance = Number.MAX_VALUE;
var tempDistance = 0;
var result = null;
for (var i = 0; i < this.children.length; i++)
{
var child = this.children[i];
if (child.exists)
{
tempDistance = Math.abs(Phaser.Point.distance(object, child));
if (tempDistance < distance && (!callback || callback.call(callbackContext, child, tempDistance)))
{
distance = tempDistance;
result = child;
}
}
}
return result;
};
/**
* Get the child furthest away from the given Object, with optional callback to filter children.
*
* This can be a Sprite, Group, Image or any object with public x and y properties.
*
* 'furthest away' is determined by the distance from the objects `x` and `y` properties compared to the childs `x` and `y` properties.
*
* You can use the optional `callback` argument to apply your own filter to the distance checks.
* If the child is closer then the previous child, it will be sent to `callback` as the first argument,
* with the distance as the second. The callback should return `true` if it passes your
* filtering criteria, otherwise it should return `false`.
*
* @method Phaser.Group#getFurthestFrom
* @param {any} object - The object used to determine the distance. This can be a Sprite, Group, Image or any object with public x and y properties.
* @param {function} [callback] - The function that each child will be evaluated against. Each child of the group will be passed to it as its first parameter, with the distance as the second. It should return `true` if the child passes the matching criteria.
* @param {object} [callbackContext] - The context in which the function should be called (usually 'this').
* @return {any} The child furthest from the given object, or `null` if no child was found.
*/
Phaser.Group.prototype.getFurthestFrom = function (object, callback, callbackContext) {
var distance = 0;
var tempDistance = 0;
var result = null;
for (var i = 0; i < this.children.length; i++)
{
var child = this.children[i];
if (child.exists)
{
tempDistance = Math.abs(Phaser.Point.distance(object, child));
if (tempDistance > distance && (!callback || callback.call(callbackContext, child, tempDistance)))
{
distance = tempDistance;
result = child;
}
}
}
return result;
};
/**
* Get the number of living children in this group.
*
* @method Phaser.Group#countLiving
* @return {integer} The number of children flagged as alive.
*/
Phaser.Group.prototype.countLiving = function () {
return this.iterate('alive', true, Phaser.Group.RETURN_TOTAL);
};
/**
* Get the number of dead children in this group.
*
* @method Phaser.Group#countDead
* @return {integer} The number of children flagged as dead.
*/
Phaser.Group.prototype.countDead = function () {
return this.iterate('alive', false, Phaser.Group.RETURN_TOTAL);
};
/**
* Returns a random child from the group.
*
* @method Phaser.Group#getRandom
* @param {integer} [startIndex=0] - Offset from the front of the group (lowest child).
* @param {integer} [length=(to top)] - Restriction on the number of values you want to randomly select from.
* @return {any} A random child of this Group.
*/
Phaser.Group.prototype.getRandom = function (startIndex, length) {
if (startIndex === undefined) { startIndex = 0; }
if (length === undefined) { length = this.children.length; }
if (length === 0)
{
return null;
}
return Phaser.ArrayUtils.getRandomItem(this.children, startIndex, length);
};
/**
* Returns a random child from the Group that has `exists` set to `true`.
*
* Optionally you can specify a start and end index. For example if this Group had 100 children,
* and you set `startIndex` to 0 and `endIndex` to 50, it would return a random child from only
* the first 50 children in the Group.
*
* @method Phaser.Group#getRandomExists
* @param {integer} [startIndex=0] - The first child index to start the search from.
* @param {integer} [endIndex] - The last child index to search up to.
* @return {any} A random child of this Group that exists.
*/
Phaser.Group.prototype.getRandomExists = function (startIndex, endIndex) {
var list = this.getAll('exists', true, startIndex, endIndex);
return this.game.rnd.pick(list);
};
/**
* Returns all children in this Group.
*
* You can optionally specify a matching criteria using the `property` and `value` arguments.
*
* For example: `getAll('exists', true)` would return only children that have their exists property set.
*
* Optionally you can specify a start and end index. For example if this Group had 100 children,
* and you set `startIndex` to 0 and `endIndex` to 50, it would return a random child from only
* the first 50 children in the Group.
*
* @method Phaser.Group#getAll
* @param {string} [property] - An optional property to test against the value argument.
* @param {any} [value] - If property is set then Child.property must strictly equal this value to be included in the results.
* @param {integer} [startIndex=0] - The first child index to start the search from.
* @param {integer} [endIndex] - The last child index to search up until.
* @return {any} A random existing child of this Group.
*/
Phaser.Group.prototype.getAll = function (property, value, startIndex, endIndex) {
if (startIndex === undefined) { startIndex = 0; }
if (endIndex === undefined) { endIndex = this.children.length; }
var output = [];
for (var i = startIndex; i < endIndex; i++)
{
var child = this.children[i];
if (property && child[property] === value)
{
output.push(child);
}
}
return output;
};
/**
* Removes the given child from this group.
*
* This will dispatch an `onRemovedFromGroup` event from the child (if it has one), and optionally destroy the child.
*
* If the group cursor was referring to the removed child it is updated to refer to the next child.
*
* @method Phaser.Group#remove
* @param {any} child - The child to remove.
* @param {boolean} [destroy=false] - If true `destroy` will be invoked on the removed child.
* @param {boolean} [silent=false] - If true the the child will not dispatch the `onRemovedFromGroup` event.
* @return {boolean} true if the child was removed from this group, otherwise false.
*/
Phaser.Group.prototype.remove = function (child, destroy, silent) {
if (destroy === undefined) { destroy = false; }
if (silent === undefined) { silent = false; }
if (this.children.length === 0 || this.children.indexOf(child) === -1)
{
return false;
}
if (!silent && child.events && !child.destroyPhase)
{
child.events.onRemovedFromGroup$dispatch(child, this);
}
var removed = this.removeChild(child);
this.removeFromHash(child);
this.updateZ();
if (this.cursor === child)
{
this.next();
}
if (destroy && removed)
{
removed.destroy(true);
}
return true;
};
/**
* Moves all children from this Group to the Group given.
*
* @method Phaser.Group#moveAll
* @param {Phaser.Group} group - The new Group to which the children will be moved to.
* @param {boolean} [silent=false] - If true the children will not dispatch the `onAddedToGroup` event for the new Group.
* @return {Phaser.Group} The Group to which all the children were moved.
*/
Phaser.Group.prototype.moveAll = function (group, silent) {
if (silent === undefined) { silent = false; }
if (this.children.length > 0 && group instanceof Phaser.Group)
{
do
{
group.add(this.children[0], silent);
}
while (this.children.length > 0);
this.hash = [];
this.cursor = null;
}
return group;
};
/**
* Removes all children from this Group, but does not remove the group from its parent.
*
* The children can be optionally destroyed as they are removed.
*
* You can also optionally also destroy the BaseTexture the Child is using. Be careful if you've
* more than one Game Object sharing the same BaseTexture.
*
* @method Phaser.Group#removeAll
* @param {boolean} [destroy=false] - If true `destroy` will be invoked on each removed child.
* @param {boolean} [silent=false] - If true the children will not dispatch their `onRemovedFromGroup` events.
* @param {boolean} [destroyTexture=false] - If true, and if the `destroy` argument is also true, the BaseTexture belonging to the Child is also destroyed. Note that if another Game Object is sharing the same BaseTexture it will invalidate it.
*/
Phaser.Group.prototype.removeAll = function (destroy, silent, destroyTexture) {
if (destroy === undefined) { destroy = false; }
if (silent === undefined) { silent = false; }
if (destroyTexture === undefined) { destroyTexture = false; }
if (this.children.length === 0)
{
return;
}
do
{
if (!silent && this.children[0].events)
{
this.children[0].events.onRemovedFromGroup$dispatch(this.children[0], this);
}
var removed = this.removeChild(this.children[0]);
this.removeFromHash(removed);
if (destroy && removed)
{
removed.destroy(true, destroyTexture);
}
}
while (this.children.length > 0);
this.hash = [];
this.cursor = null;
};
/**
* Removes all children from this group whose index falls beteen the given startIndex and endIndex values.
*
* @method Phaser.Group#removeBetween
* @param {integer} startIndex - The index to start removing children from.
* @param {integer} [endIndex] - The index to stop removing children at. Must be higher than startIndex. If undefined this method will remove all children between startIndex and the end of the group.
* @param {boolean} [destroy=false] - If true `destroy` will be invoked on each removed child.
* @param {boolean} [silent=false] - If true the children will not dispatch their `onRemovedFromGroup` events.
*/
Phaser.Group.prototype.removeBetween = function (startIndex, endIndex, destroy, silent) {
if (endIndex === undefined) { endIndex = this.children.length - 1; }
if (destroy === undefined) { destroy = false; }
if (silent === undefined) { silent = false; }
if (this.children.length === 0)
{
return;
}
if (startIndex > endIndex || startIndex < 0 || endIndex > this.children.length)
{
return false;
}
var i = endIndex;
while (i >= startIndex)
{
if (!silent && this.children[i].events)
{
this.children[i].events.onRemovedFromGroup$dispatch(this.children[i], this);
}
var removed = this.removeChild(this.children[i]);
this.removeFromHash(removed);
if (destroy && removed)
{
removed.destroy(true);
}
if (this.cursor === this.children[i])
{
this.cursor = null;
}
i--;
}
this.updateZ();
};
/**
* Destroys this group.
*
* Removes all children, then removes this group from its parent and nulls references.
*
* @method Phaser.Group#destroy
* @param {boolean} [destroyChildren=true] - If true `destroy` will be invoked on each removed child.
* @param {boolean} [soft=false] - A 'soft destroy' (set to true) doesn't remove this group from its parent or null the game reference. Set to false and it does.
*/
Phaser.Group.prototype.destroy = function (destroyChildren, soft) {
if (this.game === null || this.ignoreDestroy) { return; }
if (destroyChildren === undefined) { destroyChildren = true; }
if (soft === undefined) { soft = false; }
this.onDestroy.dispatch(this, destroyChildren, soft);
this.removeAll(destroyChildren);
this.cursor = null;
this.filters = null;
this.pendingDestroy = false;
if (!soft)
{
if (this.parent)
{
this.parent.removeChild(this);
}
this.game = null;
this.exists = false;
}
};
/**
* Total number of existing children in the group.
*
* @name Phaser.Group#total
* @property {integer} total
* @readonly
*/
Object.defineProperty(Phaser.Group.prototype, "total", {
get: function () {
return this.iterate('exists', true, Phaser.Group.RETURN_TOTAL);
}
});
/**
* Total number of children in this group, regardless of exists/alive status.
*
* @name Phaser.Group#length
* @property {integer} length
* @readonly
*/
Object.defineProperty(Phaser.Group.prototype, "length", {
get: function () {
return this.children.length;
}
});
/**
* The angle of rotation of the group container, in degrees.
*
* This adjusts the group itself by modifying its local rotation transform.
*
* This has no impact on the rotation/angle properties of the children, but it will update their worldTransform
* and on-screen orientation and position.
*
* @name Phaser.Group#angle
* @property {number} angle
*/
Object.defineProperty(Phaser.Group.prototype, "angle", {
get: function() {
return Phaser.Math.radToDeg(this.rotation);
},
set: function(value) {
this.rotation = Phaser.Math.degToRad(value);
}
});
/**
* The center x coordinate of this Group.
*
* It is derived by calling `getBounds`, calculating the Groups dimensions based on its
* visible children.
*
* @name Phaser.Group#centerX
* @property {number} centerX
*/
Object.defineProperty(Phaser.Group.prototype, "centerX", {
get: function () {
return this.getBounds(this.parent).centerX;
},
set: function (value) {
var r = this.getBounds(this.parent);
var offset = this.x - r.x;
this.x = (value + offset) - r.halfWidth;
}
});
/**
* The center y coordinate of this Group.
*
* It is derived by calling `getBounds`, calculating the Groups dimensions based on its
* visible children.
*
* @name Phaser.Group#centerY
* @property {number} centerY
*/
Object.defineProperty(Phaser.Group.prototype, "centerY", {
get: function () {
return this.getBounds(this.parent).centerY;
},
set: function (value) {
var r = this.getBounds(this.parent);
var offset = this.y - r.y;
this.y = (value + offset) - r.halfHeight;
}
});
/**
* The left coordinate of this Group.
*
* It is derived by calling `getBounds`, calculating the Groups dimensions based on its
* visible children.
*
* @name Phaser.Group#left
* @property {number} left
*/
Object.defineProperty(Phaser.Group.prototype, "left", {
get: function () {
return this.getBounds(this.parent).left;
},
set: function (value) {
var r = this.getBounds(this.parent);
var offset = this.x - r.x;
this.x = value + offset;
}
});
/**
* The right coordinate of this Group.
*
* It is derived by calling `getBounds`, calculating the Groups dimensions based on its
* visible children.
*
* @name Phaser.Group#right
* @property {number} right
*/
Object.defineProperty(Phaser.Group.prototype, "right", {
get: function () {
return this.getBounds(this.parent).right;
},
set: function (value) {
var r = this.getBounds(this.parent);
var offset = this.x - r.x;
this.x = (value + offset) - r.width;
}
});
/**
* The top coordinate of this Group.
*
* It is derived by calling `getBounds`, calculating the Groups dimensions based on its
* visible children.
*
* @name Phaser.Group#top
* @property {number} top
*/
Object.defineProperty(Phaser.Group.prototype, "top", {
get: function () {
return this.getBounds(this.parent).top;
},
set: function (value) {
var r = this.getBounds(this.parent);
var offset = this.y - r.y;
this.y = (value + offset);
}
});
/**
* The bottom coordinate of this Group.
*
* It is derived by calling `getBounds`, calculating the Groups dimensions based on its
* visible children.
*
* @name Phaser.Group#bottom
* @property {number} bottom
*/
Object.defineProperty(Phaser.Group.prototype, "bottom", {
get: function () {
return this.getBounds(this.parent).bottom;
},
set: function (value) {
var r = this.getBounds(this.parent);
var offset = this.y - r.y;
this.y = (value + offset) - r.height;
}
});
/**
* Aligns this Group within another Game Object, or Rectangle, known as the
* 'container', to one of 9 possible positions.
*
* The container must be a Game Object, or Phaser.Rectangle object. This can include properties
* such as `World.bounds` or `Camera.view`, for aligning Groups within the world
* and camera bounds. Or it can include other Sprites, Images, Text objects, BitmapText,
* TileSprites or Buttons.
*
* Please note that aligning a Group to another Game Object does **not** make it a child of
* the container. It simply modifies its position coordinates so it aligns with it.
*
* The position constants you can use are:
*
* `Phaser.TOP_LEFT`, `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`,
* `Phaser.CENTER`, `Phaser.RIGHT_CENTER`, `Phaser.BOTTOM_LEFT`,
* `Phaser.BOTTOM_CENTER` and `Phaser.BOTTOM_RIGHT`.
*
* Groups are placed in such a way that their _bounds_ align with the
* container, taking into consideration rotation and scale of its children.
* This allows you to neatly align Groups, irrespective of their position value.
*
* The optional `offsetX` and `offsetY` arguments allow you to apply extra spacing to the final
* aligned position of the Group. For example:
*
* `group.alignIn(background, Phaser.BOTTOM_RIGHT, -20, -20)`
*
* Would align the `group` to the bottom-right, but moved 20 pixels in from the corner.
* Think of the offsets as applying an adjustment to the containers bounds before the alignment takes place.
* So providing a negative offset will 'shrink' the container bounds by that amount, and providing a positive
* one expands it.
*
* @method Phaser.Group#alignIn
* @param {Phaser.Rectangle|Phaser.Sprite|Phaser.Image|Phaser.Text|Phaser.BitmapText|Phaser.Button|Phaser.Graphics|Phaser.TileSprite} container - The Game Object or Rectangle with which to align this Group to. Can also include properties such as `World.bounds` or `Camera.view`.
* @param {integer} [position] - The position constant. One of `Phaser.TOP_LEFT` (default), `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`, `Phaser.CENTER`, `Phaser.RIGHT_CENTER`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER` or `Phaser.BOTTOM_RIGHT`.
* @param {integer} [offsetX=0] - A horizontal adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @param {integer} [offsetY=0] - A vertical adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @return {Phaser.Group} This Group.
*/
// This function is set at the bottom of src/gameobjects/components/Bounds.js
/**
* Aligns this Group to the side of another Game Object, or Rectangle, known as the
* 'parent', in one of 11 possible positions.
*
* The parent must be a Game Object, or Phaser.Rectangle object. This can include properties
* such as `World.bounds` or `Camera.view`, for aligning Groups within the world
* and camera bounds. Or it can include other Sprites, Images, Text objects, BitmapText,
* TileSprites or Buttons.
*
* Please note that aligning a Group to another Game Object does **not** make it a child of
* the parent. It simply modifies its position coordinates so it aligns with it.
*
* The position constants you can use are:
*
* `Phaser.TOP_LEFT` (default), `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_TOP`,
* `Phaser.LEFT_CENTER`, `Phaser.LEFT_BOTTOM`, `Phaser.RIGHT_TOP`, `Phaser.RIGHT_CENTER`,
* `Phaser.RIGHT_BOTTOM`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER`
* and `Phaser.BOTTOM_RIGHT`.
*
* Groups are placed in such a way that their _bounds_ align with the
* parent, taking into consideration rotation and scale of the children.
* This allows you to neatly align Groups, irrespective of their position value.
*
* The optional `offsetX` and `offsetY` arguments allow you to apply extra spacing to the final
* aligned position of the Group. For example:
*
* `group.alignTo(background, Phaser.BOTTOM_RIGHT, -20, -20)`
*
* Would align the `group` to the bottom-right, but moved 20 pixels in from the corner.
* Think of the offsets as applying an adjustment to the parents bounds before the alignment takes place.
* So providing a negative offset will 'shrink' the parent bounds by that amount, and providing a positive
* one expands it.
*
* @method Phaser.Group#alignTo
* @param {Phaser.Rectangle|Phaser.Sprite|Phaser.Image|Phaser.Text|Phaser.BitmapText|Phaser.Button|Phaser.Graphics|Phaser.TileSprite} parent - The Game Object or Rectangle with which to align this Group to. Can also include properties such as `World.bounds` or `Camera.view`.
* @param {integer} [position] - The position constant. One of `Phaser.TOP_LEFT`, `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_TOP`, `Phaser.LEFT_CENTER`, `Phaser.LEFT_BOTTOM`, `Phaser.RIGHT_TOP`, `Phaser.RIGHT_CENTER`, `Phaser.RIGHT_BOTTOM`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER` or `Phaser.BOTTOM_RIGHT`.
* @param {integer} [offsetX=0] - A horizontal adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @param {integer} [offsetY=0] - A vertical adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @return {Phaser.Group} This Group.
*/
// This function is set at the bottom of src/gameobjects/components/Bounds.js
/**
* A display object is any object that can be rendered in the Phaser/pixi.js scene graph.
*
* This includes {@link Phaser.Group} (groups are display objects!),
* {@link Phaser.Sprite}, {@link Phaser.Button}, {@link Phaser.Text}
* as well as {@link PIXI.DisplayObject} and all derived types.
*
* @typedef {object} DisplayObject
*/
// Documentation stub for linking.
/**
* The x coordinate of the group container.
*
* You can adjust the group container itself by modifying its coordinates.
* This will have no impact on the x/y coordinates of its children, but it will update their worldTransform and on-screen position.
* @name Phaser.Group#x
* @property {number} x
*/
/**
* The y coordinate of the group container.
*
* You can adjust the group container itself by modifying its coordinates.
* This will have no impact on the x/y coordinates of its children, but it will update their worldTransform and on-screen position.
* @name Phaser.Group#y
* @property {number} y
*/
/**
* The angle of rotation of the group container, in radians.
*
* This will adjust the group container itself by modifying its rotation.
* This will have no impact on the rotation value of its children, but it will update their worldTransform and on-screen position.
* @name Phaser.Group#rotation
* @property {number} rotation
*/
/**
* The visible state of the group. Non-visible Groups and all of their children are not rendered.
*
* @name Phaser.Group#visible
* @property {boolean} visible
*/
/**
* The alpha value of the group container.
*
* @name Phaser.Group#alpha
* @property {number} alpha
*/
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* "This world is but a canvas to our imagination." - Henry David Thoreau
*
* A game has only one world. The world is an abstract place in which all game objects live. It is not bound
* by stage limits and can be any size. You look into the world via cameras. All game objects live within
* the world at world-based coordinates. By default a world is created the same size as your Stage.
*
* @class Phaser.World
* @extends Phaser.Group
* @constructor
* @param {Phaser.Game} game - Reference to the current game instance.
*/
Phaser.World = function (game) {
Phaser.Group.call(this, game, null, '__world', false);
/**
* The World has no fixed size, but it does have a bounds outside of which objects are no longer considered as being "in world" and you should use this to clean-up the display list and purge dead objects.
* By default we set the Bounds to be from 0,0 to Game.width,Game.height. I.e. it will match the size given to the game constructor with 0,0 representing the top-left of the display.
* However 0,0 is actually the center of the world, and if you rotate or scale the world all of that will happen from 0,0.
* So if you want to make a game in which the world itself will rotate you should adjust the bounds so that 0,0 is the center point, i.e. set them to -1000,-1000,2000,2000 for a 2000x2000 sized world centered around 0,0.
* @property {Phaser.Rectangle} bounds - Bound of this world that objects can not escape from.
*/
this.bounds = new Phaser.Rectangle(0, 0, game.width, game.height);
/**
* @property {Phaser.Camera} camera - Camera instance.
*/
this.camera = null;
/**
* @property {boolean} _definedSize - True if the World has been given a specifically defined size (i.e. from a Tilemap or direct in code) or false if it's just matched to the Game dimensions.
* @readonly
*/
this._definedSize = false;
/**
* @property {number} width - The defined width of the World. Sometimes the bounds needs to grow larger than this (if you resize the game) but this retains the original requested dimension.
*/
this._width = game.width;
/**
* @property {number} height - The defined height of the World. Sometimes the bounds needs to grow larger than this (if you resize the game) but this retains the original requested dimension.
*/
this._height = game.height;
this.game.state.onStateChange.add(this.stateChange, this);
};
Phaser.World.prototype = Object.create(Phaser.Group.prototype);
Phaser.World.prototype.constructor = Phaser.World;
/**
* Initialises the game world.
*
* @method Phaser.World#boot
* @protected
*/
Phaser.World.prototype.boot = function () {
this.camera = new Phaser.Camera(this.game, 0, 0, 0, this.game.width, this.game.height);
this.game.stage.addChild(this);
this.camera.boot();
};
/**
* Called whenever the State changes or resets.
*
* It resets the world.x and world.y coordinates back to zero,
* then resets the Camera.
*
* @method Phaser.World#stateChange
* @protected
*/
Phaser.World.prototype.stateChange = function () {
this.x = 0;
this.y = 0;
this.camera.reset();
};
/**
* Updates the size of this world and sets World.x/y to the given values
* The Camera bounds and Physics bounds (if set) are also updated to match the new World bounds.
*
* @method Phaser.World#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 game world in pixels.
* @param {number} height - New height of the game world in pixels.
*/
Phaser.World.prototype.setBounds = function (x, y, width, height) {
this._definedSize = true;
this._width = width;
this._height = height;
this.bounds.setTo(x, y, width, height);
this.x = x;
this.y = y;
if (this.camera.bounds)
{
// The Camera can never be smaller than the game size
this.camera.bounds.setTo(x, y, Math.max(width, this.game.width), Math.max(height, this.game.height));
}
this.game.physics.setBoundsToWorld();
};
/**
* Updates the size of this world. Note that this doesn't modify the world x/y coordinates, just the width and height.
*
* @method Phaser.World#resize
* @param {number} width - New width of the game world in pixels.
* @param {number} height - New height of the game world in pixels.
*/
Phaser.World.prototype.resize = function (width, height) {
// Don't ever scale the World bounds lower than the original requested dimensions if it's a defined world size
if (this._definedSize)
{
if (width < this._width)
{
width = this._width;
}
if (height < this._height)
{
height = this._height;
}
}
this.bounds.width = width;
this.bounds.height = height;
this.game.camera.setBoundsToWorld();
this.game.physics.setBoundsToWorld();
};
/**
* Destroyer of worlds.
*
* @method Phaser.World#shutdown
*/
Phaser.World.prototype.shutdown = function () {
// World is a Group, so run a soft destruction on this and all children.
this.destroy(true, true);
};
/**
* This will take the given game object and check if its x/y coordinates fall outside of the world bounds.
* If they do it will reposition the object to the opposite side of the world, creating a wrap-around effect.
* If sprite has a P2 body then the body (sprite.body) should be passed as first parameter to the function.
*
* Please understand there are limitations to this method. For example if you have scaled the World
* then objects won't always be re-positioned correctly, and you'll need to employ your own wrapping function.
*
* @method Phaser.World#wrap
* @param {Phaser.Sprite|Phaser.Image|Phaser.TileSprite|Phaser.Text} sprite - The object you wish to wrap around the world bounds.
* @param {number} [padding=0] - Extra padding added equally to the sprite.x and y coordinates before checking if within the world bounds. Ignored if useBounds is true.
* @param {boolean} [useBounds=false] - If useBounds is false wrap checks the object.x/y coordinates. If true it does a more accurate bounds check, which is more expensive.
* @param {boolean} [horizontal=true] - If horizontal is false, wrap will not wrap the object.x coordinates horizontally.
* @param {boolean} [vertical=true] - If vertical is false, wrap will not wrap the object.y coordinates vertically.
*/
Phaser.World.prototype.wrap = function (sprite, padding, useBounds, horizontal, vertical) {
if (padding === undefined) { padding = 0; }
if (useBounds === undefined) { useBounds = false; }
if (horizontal === undefined) { horizontal = true; }
if (vertical === undefined) { vertical = true; }
if (!useBounds)
{
if (horizontal && sprite.x + padding < this.bounds.x)
{
sprite.x = this.bounds.right + padding;
}
else if (horizontal && sprite.x - padding > this.bounds.right)
{
sprite.x = this.bounds.left - padding;
}
if (vertical && sprite.y + padding < this.bounds.top)
{
sprite.y = this.bounds.bottom + padding;
}
else if (vertical && sprite.y - padding > this.bounds.bottom)
{
sprite.y = this.bounds.top - padding;
}
}
else
{
sprite.getBounds();
if (horizontal)
{
if ((sprite.x + sprite._currentBounds.width) < this.bounds.x)
{
sprite.x = this.bounds.right;
}
else if (sprite.x > this.bounds.right)
{
sprite.x = this.bounds.left;
}
}
if (vertical)
{
if ((sprite.y + sprite._currentBounds.height) < this.bounds.top)
{
sprite.y = this.bounds.bottom;
}
else if (sprite.y > this.bounds.bottom)
{
sprite.y = this.bounds.top;
}
}
}
};
/**
* @name Phaser.World#width
* @property {number} width - Gets or sets the current width of the game world. The world can never be smaller than the game (canvas) dimensions.
*/
Object.defineProperty(Phaser.World.prototype, "width", {
get: function () {
return this.bounds.width;
},
set: function (value) {
if (value < this.game.width)
{
value = this.game.width;
}
this.bounds.width = value;
this._width = value;
this._definedSize = true;
}
});
/**
* @name Phaser.World#height
* @property {number} height - Gets or sets the current height of the game world. The world can never be smaller than the game (canvas) dimensions.
*/
Object.defineProperty(Phaser.World.prototype, "height", {
get: function () {
return this.bounds.height;
},
set: function (value) {
if (value < this.game.height)
{
value = this.game.height;
}
this.bounds.height = value;
this._height = value;
this._definedSize = true;
}
});
/**
* @name Phaser.World#centerX
* @property {number} centerX - Gets the X position corresponding to the center point of the world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "centerX", {
get: function () {
return this.bounds.halfWidth + this.bounds.x;
}
});
/**
* @name Phaser.World#centerY
* @property {number} centerY - Gets the Y position corresponding to the center point of the world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "centerY", {
get: function () {
return this.bounds.halfHeight + this.bounds.y;
}
});
/**
* @name Phaser.World#randomX
* @property {number} randomX - Gets a random integer which is lesser than or equal to the current width of the game world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "randomX", {
get: function () {
if (this.bounds.x < 0)
{
return this.game.rnd.between(this.bounds.x, (this.bounds.width - Math.abs(this.bounds.x)));
}
else
{
return this.game.rnd.between(this.bounds.x, this.bounds.width);
}
}
});
/**
* @name Phaser.World#randomY
* @property {number} randomY - Gets a random integer which is lesser than or equal to the current height of the game world.
* @readonly
*/
Object.defineProperty(Phaser.World.prototype, "randomY", {
get: function () {
if (this.bounds.y < 0)
{
return this.game.rnd.between(this.bounds.y, (this.bounds.height - Math.abs(this.bounds.y)));
}
else
{
return this.game.rnd.between(this.bounds.y, this.bounds.height);
}
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Phaser.Game object is the main controller for the entire Phaser game. It is responsible
* for handling the boot process, parsing the configuration values, creating the renderer,
* and setting-up all of the Phaser systems, such as physics, sound and input.
* Once that is complete it will start the default State, and then begin the main game loop.
*
* You can access lots of the Phaser systems via the properties on the `game` object. For
* example `game.renderer` is the Renderer, `game.sound` is the Sound Manager, and so on.
*
* Anywhere you can access the `game` property, you can access all of these core systems.
* For example a Sprite has a `game` property, allowing you to talk to the various parts
* of Phaser directly, without having to look after your own references.
*
* In it's most simplest form, a Phaser game can be created by providing the arguments
* to the constructor:
*
* ```
* var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create });
* ```
*
* In the example above it is passing in a State object directly. You can also use the State
* Manager to do this:
*
* ```
* var game = new Phaser.Game(800, 600, Phaser.AUTO);
* game.state.add('Boot', BasicGame.Boot);
* game.state.add('Preloader', BasicGame.Preloader);
* game.state.add('MainMenu', BasicGame.MainMenu);
* game.state.add('Game', BasicGame.Game);
* game.state.start('Boot');
*
* ```
* In the example above, 4 States are added to the State Manager, and Phaser is told to
* start running the `Boot` state when it has finished initializing. There are example
* project templates you can use in the Phaser GitHub repo, inside the `resources` folder.
*
* Instead of specifying arguments you can also pass a single object instead:
*
* ```
* var config = {
* width: 800,
* height: 600,
* renderer: Phaser.AUTO,
* antialias: true,
* multiTexture: true,
* state: {
* preload: preload,
* create: create,
* update: update
* }
* }
*
* var game = new Phaser.Game(config);
* ```
*
* @class Phaser.Game
* @constructor
* @param {number|string} [width=800] - The width of your game in game pixels. If given as a string the value must be between 0 and 100 and will be used as the percentage width of the parent container, or the browser window if no parent is given.
* @param {number|string} [height=600] - The height of your game in game pixels. If given as a string the value must be between 0 and 100 and will be used as the percentage height of the parent container, or the browser window if no parent is given.
* @param {number} [renderer=Phaser.AUTO] - Which renderer to use: Phaser.AUTO will auto-detect, Phaser.WEBGL, Phaser.WEBGL_MULTI, Phaser.CANVAS or Phaser.HEADLESS (no rendering at all).
* @param {string|HTMLElement} [parent=''] - The DOM element into which this games canvas will be injected. Either a DOM ID (string) or the element itself.
* @param {object} [state=null] - The default state object. A object consisting of Phaser.State functions (preload, create, update, render) or null.
* @param {boolean} [transparent=false] - Use a transparent canvas background or not.
* @param {boolean} [antialias=true] - Draw all image textures anti-aliased or not. The default is for smooth textures, but disable if your game features pixel art.
* @param {object} [physicsConfig=null] - A physics configuration object to pass to the Physics world on creation.
*/
Phaser.Game = function (width, height, renderer, parent, state, transparent, antialias, physicsConfig) {
/**
* @property {number} id - Phaser Game ID
* @readonly
*/
this.id = Phaser.GAMES.push(this) - 1;
/**
* @property {object} config - The Phaser.Game configuration object.
*/
this.config = null;
/**
* @property {object} physicsConfig - The Phaser.Physics.World configuration object.
*/
this.physicsConfig = physicsConfig;
/**
* @property {string|HTMLElement} parent - The Games DOM parent.
* @default
*/
this.parent = '';
/**
* The current Game Width in pixels.
*
* _Do not modify this property directly:_ use {@link Phaser.ScaleManager#setGameSize} - eg. `game.scale.setGameSize(width, height)` - instead.
*
* @property {integer} width
* @readonly
* @default
*/
this.width = 800;
/**
* The current Game Height in pixels.
*
* _Do not modify this property directly:_ use {@link Phaser.ScaleManager#setGameSize} - eg. `game.scale.setGameSize(width, height)` - instead.
*
* @property {integer} height
* @readonly
* @default
*/
this.height = 600;
/**
* The resolution of your game. This value is read only, but can be changed at start time it via a game configuration object.
*
* @property {integer} resolution
* @readonly
* @default
*/
this.resolution = 1;
/**
* @property {integer} _width - Private internal var.
* @private
*/
this._width = 800;
/**
* @property {integer} _height - Private internal var.
* @private
*/
this._height = 600;
/**
* @property {boolean} transparent - Use a transparent canvas background or not.
* @default
*/
this.transparent = false;
/**
* @property {boolean} antialias - Anti-alias graphics. By default scaled images are smoothed in Canvas and WebGL, set anti-alias to false to disable this globally.
* @default
*/
this.antialias = true;
/**
* Has support for Multiple bound Textures in WebGL been enabled? This is a read-only property.
* To set it you need to either specify `Phaser.WEBGL_MULTI` as the renderer type, or use the Game
* Configuration object with the property `multiTexture` set to true. It has to be enabled before
* Pixi boots, and cannot be changed after the game is running. Once enabled, take advantage of it
* via the `game.renderer.setTexturePriority` method.
*
* @property {boolean} multiTexture
* @default
* @readOnly
*/
this.multiTexture = false;
/**
* @property {boolean} preserveDrawingBuffer - The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering.
* @default
*/
this.preserveDrawingBuffer = false;
/**
* Clear the Canvas each frame before rendering the display list.
* You can set this to `false` to gain some performance if your game always contains a background that completely fills the display.
* @property {boolean} clearBeforeRender
* @default
*/
this.clearBeforeRender = true;
/**
* @property {PIXI.CanvasRenderer|PIXI.WebGLRenderer} renderer - The Pixi Renderer.
* @protected
*/
this.renderer = null;
/**
* @property {number} renderType - The Renderer this game will use. Either Phaser.AUTO, Phaser.CANVAS, Phaser.WEBGL, Phaser.WEBGL_MULTI or Phaser.HEADLESS.
* @readonly
*/
this.renderType = Phaser.AUTO;
/**
* @property {Phaser.StateManager} state - The StateManager.
*/
this.state = null;
/**
* @property {boolean} isBooted - Whether the game engine is booted, aka available.
* @readonly
*/
this.isBooted = false;
/**
* @property {boolean} isRunning - Is game running or paused?
* @readonly
*/
this.isRunning = false;
/**
* @property {Phaser.RequestAnimationFrame} raf - Automatically handles the core game loop via requestAnimationFrame or setTimeout
* @protected
*/
this.raf = null;
/**
* @property {Phaser.GameObjectFactory} add - Reference to the Phaser.GameObjectFactory.
*/
this.add = null;
/**
* @property {Phaser.GameObjectCreator} make - Reference to the GameObject Creator.
*/
this.make = null;
/**
* @property {Phaser.Cache} cache - Reference to the assets cache.
*/
this.cache = null;
/**
* @property {Phaser.Input} input - Reference to the input manager
*/
this.input = null;
/**
* @property {Phaser.Loader} load - Reference to the assets loader.
*/
this.load = null;
/**
* @property {Phaser.Math} math - Reference to the math helper.
*/
this.math = null;
/**
* @property {Phaser.Net} net - Reference to the network class.
*/
this.net = null;
/**
* @property {Phaser.ScaleManager} scale - The game scale manager.
*/
this.scale = null;
/**
* @property {Phaser.SoundManager} sound - Reference to the sound manager.
*/
this.sound = null;
/**
* @property {Phaser.Stage} stage - Reference to the stage.
*/
this.stage = null;
/**
* @property {Phaser.Time} time - Reference to the core game clock.
*/
this.time = null;
/**
* @property {Phaser.TweenManager} tweens - Reference to the tween manager.
*/
this.tweens = null;
/**
* @property {Phaser.World} world - Reference to the world.
*/
this.world = null;
/**
* @property {Phaser.Physics} physics - Reference to the physics manager.
*/
this.physics = null;
/**
* @property {Phaser.PluginManager} plugins - Reference to the plugin manager.
*/
this.plugins = null;
/**
* @property {Phaser.RandomDataGenerator} rnd - Instance of repeatable random data generator helper.
*/
this.rnd = null;
/**
* @property {Phaser.Device} device - Contains device information and capabilities.
*/
this.device = Phaser.Device;
/**
* @property {Phaser.Camera} camera - A handy reference to world.camera.
*/
this.camera = null;
/**
* @property {HTMLCanvasElement} canvas - A handy reference to renderer.view, the canvas that the game is being rendered in to.
*/
this.canvas = null;
/**
* @property {CanvasRenderingContext2D} context - A handy reference to renderer.context (only set for CANVAS games, not WebGL)
*/
this.context = null;
/**
* @property {Phaser.Utils.Debug} debug - A set of useful debug utilities.
*/
this.debug = null;
/**
* @property {Phaser.Particles} particles - The Particle Manager.
*/
this.particles = null;
/**
* @property {Phaser.Create} create - The Asset Generator.
*/
this.create = null;
/**
* If `false` Phaser will automatically render the display list every update. If `true` the render loop will be skipped.
* You can toggle this value at run-time to gain exact control over when Phaser renders. This can be useful in certain types of game or application.
* Please note that if you don't render the display list then none of the game object transforms will be updated, so use this value carefully.
* @property {boolean} lockRender
* @default
*/
this.lockRender = false;
/**
* @property {boolean} stepping - Enable core loop stepping with Game.enableStep().
* @default
* @readonly
*/
this.stepping = false;
/**
* @property {boolean} pendingStep - An internal property used by enableStep, but also useful to query from your own game objects.
* @default
* @readonly
*/
this.pendingStep = false;
/**
* @property {number} stepCount - When stepping is enabled this contains the current step cycle.
* @default
* @readonly
*/
this.stepCount = 0;
/**
* @property {Phaser.Signal} onPause - This event is fired when the game pauses.
*/
this.onPause = null;
/**
* @property {Phaser.Signal} onResume - This event is fired when the game resumes from a paused state.
*/
this.onResume = null;
/**
* @property {Phaser.Signal} onBlur - This event is fired when the game no longer has focus (typically on page hide).
*/
this.onBlur = null;
/**
* @property {Phaser.Signal} onFocus - This event is fired when the game has focus (typically on page show).
*/
this.onFocus = null;
/**
* @property {boolean} _paused - Is game paused?
* @private
*/
this._paused = false;
/**
* @property {boolean} _codePaused - Was the game paused via code or a visibility change?
* @private
*/
this._codePaused = false;
/**
* The ID of the current/last logic update applied this render frame, starting from 0.
* The first update is `currentUpdateID === 0` and the last update is `currentUpdateID === updatesThisFrame.`
* @property {integer} currentUpdateID
* @protected
*/
this.currentUpdateID = 0;
/**
* Number of logic updates expected to occur this render frame; will be 1 unless there are catch-ups required (and allowed).
* @property {integer} updatesThisFrame
* @protected
*/
this.updatesThisFrame = 1;
/**
* @property {number} _deltaTime - Accumulate elapsed time until a logic update is due.
* @private
*/
this._deltaTime = 0;
/**
* @property {number} _lastCount - Remember how many 'catch-up' iterations were used on the logicUpdate last frame.
* @private
*/
this._lastCount = 0;
/**
* @property {number} _spiraling - If the 'catch-up' iterations are spiraling out of control, this counter is incremented.
* @private
*/
this._spiraling = 0;
/**
* @property {boolean} _kickstart - Force a logic update + render by default (always set on Boot and State swap)
* @private
*/
this._kickstart = true;
/**
* If the game is struggling to maintain the desired FPS, this signal will be dispatched.
* The desired/chosen FPS should probably be closer to the {@link Phaser.Time#suggestedFps} value.
* @property {Phaser.Signal} fpsProblemNotifier
* @public
*/
this.fpsProblemNotifier = new Phaser.Signal();
/**
* @property {boolean} forceSingleUpdate - Should the game loop force a logic update, regardless of the delta timer? Set to true if you know you need this. You can toggle it on the fly.
*/
this.forceSingleUpdate = true;
/**
* @property {number} _nextNotification - The soonest game.time.time value that the next fpsProblemNotifier can be dispatched.
* @private
*/
this._nextFpsNotification = 0;
// Parse the configuration object (if any)
if (arguments.length === 1 && typeof arguments[0] === 'object')
{
this.parseConfig(arguments[0]);
}
else
{
this.config = { enableDebug: true };
if (typeof width !== 'undefined')
{
this._width = width;
}
if (typeof height !== 'undefined')
{
this._height = height;
}
if (typeof renderer !== 'undefined')
{
this.renderType = renderer;
}
if (typeof parent !== 'undefined')
{
this.parent = parent;
}
if (typeof transparent !== 'undefined')
{
this.transparent = transparent;
}
if (typeof antialias !== 'undefined')
{
this.antialias = antialias;
}
this.rnd = new Phaser.RandomDataGenerator([(Date.now() * Math.random()).toString()]);
this.state = new Phaser.StateManager(this, state);
}
this.device.whenReady(this.boot, this);
return this;
};
Phaser.Game.prototype = {
/**
* Parses a Game configuration object.
*
* @method Phaser.Game#parseConfig
* @protected
*/
parseConfig: function (config) {
this.config = config;
if (config['enableDebug'] === undefined)
{
this.config.enableDebug = true;
}
if (config['width'])
{
this._width = config['width'];
}
if (config['height'])
{
this._height = config['height'];
}
if (config['renderer'])
{
this.renderType = config['renderer'];
}
if (config['parent'])
{
this.parent = config['parent'];
}
if (config['transparent'] !== undefined)
{
this.transparent = config['transparent'];
}
if (config['antialias'] !== undefined)
{
this.antialias = config['antialias'];
}
if (config['multiTexture'] !== undefined)
{
this.multiTexture = config['multiTexture'];
}
if (config['resolution'])
{
this.resolution = config['resolution'];
}
if (config['preserveDrawingBuffer'] !== undefined)
{
this.preserveDrawingBuffer = config['preserveDrawingBuffer'];
}
if (config['physicsConfig'])
{
this.physicsConfig = config['physicsConfig'];
}
var seed = [(Date.now() * Math.random()).toString()];
if (config['seed'])
{
seed = config['seed'];
}
this.rnd = new Phaser.RandomDataGenerator(seed);
var state = null;
if (config['state'])
{
state = config['state'];
}
this.state = new Phaser.StateManager(this, state);
},
/**
* Initialize engine sub modules and start the game.
*
* @method Phaser.Game#boot
* @protected
*/
boot: function () {
if (this.isBooted)
{
return;
}
this.onPause = new Phaser.Signal();
this.onResume = new Phaser.Signal();
this.onBlur = new Phaser.Signal();
this.onFocus = new Phaser.Signal();
this.isBooted = true;
PIXI.game = this;
this.math = Phaser.Math;
this.scale = new Phaser.ScaleManager(this, this._width, this._height);
this.stage = new Phaser.Stage(this);
this.setUpRenderer();
this.world = new Phaser.World(this);
this.add = new Phaser.GameObjectFactory(this);
this.make = new Phaser.GameObjectCreator(this);
this.cache = new Phaser.Cache(this);
this.load = new Phaser.Loader(this);
this.time = new Phaser.Time(this);
this.tweens = new Phaser.TweenManager(this);
this.input = new Phaser.Input(this);
this.sound = new Phaser.SoundManager(this);
this.physics = new Phaser.Physics(this, this.physicsConfig);
this.particles = new Phaser.Particles(this);
this.create = new Phaser.Create(this);
this.plugins = new Phaser.PluginManager(this);
this.net = new Phaser.Net(this);
this.time.boot();
this.stage.boot();
this.world.boot();
this.scale.boot();
this.input.boot();
this.sound.boot();
this.state.boot();
if (this.config['enableDebug'])
{
this.debug = new Phaser.Utils.Debug(this);
this.debug.boot();
}
else
{
this.debug = { preUpdate: function () {}, update: function () {}, reset: function () {} };
}
this.showDebugHeader();
this.isRunning = true;
if (this.config && this.config['forceSetTimeOut'])
{
this.raf = new Phaser.RequestAnimationFrame(this, this.config['forceSetTimeOut']);
}
else
{
this.raf = new Phaser.RequestAnimationFrame(this, false);
}
this._kickstart = true;
if (window['focus'])
{
if (!window['PhaserGlobal'] || (window['PhaserGlobal'] && !window['PhaserGlobal'].stopFocus))
{
window.focus();
}
}
this.raf.start();
},
/**
* Displays a Phaser version debug header in the console.
*
* @method Phaser.Game#showDebugHeader
* @protected
*/
showDebugHeader: function () {
if (window['PhaserGlobal'] && window['PhaserGlobal'].hideBanner)
{
return;
}
var v = Phaser.VERSION;
var r = 'Canvas';
var a = 'HTML Audio';
var c = 1;
if (this.renderType === Phaser.WEBGL)
{
r = 'WebGL';
c++;
}
else if (this.renderType === Phaser.HEADLESS)
{
r = 'Headless';
}
if (this.device.webAudio)
{
a = 'WebAudio';
c++;
}
if (this.device.chrome)
{
var args = [
'%c %c %c Phaser v' + v + ' | Pixi.js | ' + r + ' | ' + a + ' %c %c ' + '%c http://phaser.io %c\u2665%c\u2665%c\u2665',
'background: #fb8cb3',
'background: #d44a52',
'color: #ffffff; background: #871905;',
'background: #d44a52',
'background: #fb8cb3',
'background: #ffffff'
];
for (var i = 0; i < 3; i++)
{
if (i < c)
{
args.push('color: #ff2424; background: #fff');
}
else
{
args.push('color: #959595; background: #fff');
}
}
console.log.apply(console, args);
}
else if (window['console'])
{
console.log('Phaser v' + v + ' | Pixi.js | ' + r + ' | ' + a + ' | http://phaser.io');
}
},
/**
* Checks if the device is capable of using the requested renderer and sets it up or an alternative if not.
*
* @method Phaser.Game#setUpRenderer
* @protected
*/
setUpRenderer: function () {
if (this.config['canvas'])
{
this.canvas = this.config['canvas'];
}
else
{
this.canvas = Phaser.Canvas.create(this, this.width, this.height, this.config['canvasID'], true);
}
if (this.config['canvasStyle'])
{
this.canvas.style = this.config['canvasStyle'];
}
else
{
this.canvas.style['-webkit-full-screen'] = 'width: 100%; height: 100%';
}
if (this.renderType === Phaser.HEADLESS || this.renderType === Phaser.CANVAS || (this.renderType === Phaser.AUTO && !this.device.webGL))
{
if (this.device.canvas)
{
// They requested Canvas and their browser supports it
this.renderType = Phaser.CANVAS;
this.renderer = new PIXI.CanvasRenderer(this);
this.context = this.renderer.context;
}
else
{
throw new Error('Phaser.Game - Cannot create Canvas or WebGL context, aborting.');
}
}
else
{
// They requested WebGL and their browser supports it
if (this.multiTexture || this.renderType === Phaser.WEBGL_MULTI)
{
PIXI.enableMultiTexture();
}
this.renderType = Phaser.WEBGL;
this.renderer = new PIXI.WebGLRenderer(this);
this.context = null;
this.canvas.addEventListener('webglcontextlost', this.contextLost.bind(this), false);
this.canvas.addEventListener('webglcontextrestored', this.contextRestored.bind(this), false);
}
if (this.device.cocoonJS)
{
this.canvas.screencanvas = (this.renderType === Phaser.CANVAS) ? true : false;
}
if (this.renderType !== Phaser.HEADLESS)
{
this.stage.smoothed = this.antialias;
Phaser.Canvas.addToDOM(this.canvas, this.parent, false);
Phaser.Canvas.setTouchAction(this.canvas);
}
},
/**
* Handles WebGL context loss.
*
* @method Phaser.Game#contextLost
* @private
* @param {Event} event - The webglcontextlost event.
*/
contextLost: function (event) {
event.preventDefault();
this.renderer.contextLost = true;
},
/**
* Handles WebGL context restoration.
*
* @method Phaser.Game#contextRestored
* @private
*/
contextRestored: function () {
this.renderer.initContext();
this.cache.clearGLTextures();
this.renderer.contextLost = false;
},
/**
* The core game loop.
*
* @method Phaser.Game#update
* @protected
* @param {number} time - The current time as provided by RequestAnimationFrame.
*/
update: function (time) {
this.time.update(time);
if (this._kickstart)
{
this.updateLogic(this.time.desiredFpsMult);
// call the game render update exactly once every frame
this.updateRender(this.time.slowMotion * this.time.desiredFps);
this._kickstart = false;
return;
}
// if the logic time is spiraling upwards, skip a frame entirely
if (this._spiraling > 1 && !this.forceSingleUpdate)
{
// cause an event to warn the program that this CPU can't keep up with the current desiredFps rate
if (this.time.time > this._nextFpsNotification)
{
// only permit one fps notification per 10 seconds
this._nextFpsNotification = this.time.time + 10000;
// dispatch the notification signal
this.fpsProblemNotifier.dispatch();
}
// reset the _deltaTime accumulator which will cause all pending dropped frames to be permanently skipped
this._deltaTime = 0;
this._spiraling = 0;
// call the game render update exactly once every frame
this.updateRender(this.time.slowMotion * this.time.desiredFps);
}
else
{
// step size taking into account the slow motion speed
var slowStep = this.time.slowMotion * 1000.0 / this.time.desiredFps;
// accumulate time until the slowStep threshold is met or exceeded... up to a limit of 3 catch-up frames at slowStep intervals
this._deltaTime += Math.max(Math.min(slowStep * 3, this.time.elapsed), 0);
// call the game update logic multiple times if necessary to "catch up" with dropped frames
// unless forceSingleUpdate is true
var count = 0;
this.updatesThisFrame = Math.floor(this._deltaTime / slowStep);
if (this.forceSingleUpdate)
{
this.updatesThisFrame = Math.min(1, this.updatesThisFrame);
}
while (this._deltaTime >= slowStep)
{
this._deltaTime -= slowStep;
this.currentUpdateID = count;
this.updateLogic(this.time.desiredFpsMult);
count++;
if (this.forceSingleUpdate && count === 1)
{
break;
}
else
{
this.time.refresh();
}
}
// detect spiraling (if the catch-up loop isn't fast enough, the number of iterations will increase constantly)
if (count > this._lastCount)
{
this._spiraling++;
}
else if (count < this._lastCount)
{
// looks like it caught up successfully, reset the spiral alert counter
this._spiraling = 0;
}
this._lastCount = count;
// call the game render update exactly once every frame unless we're playing catch-up from a spiral condition
this.updateRender(this._deltaTime / slowStep);
}
},
/**
* Updates all logic subsystems in Phaser. Called automatically by Game.update.
*
* @method Phaser.Game#updateLogic
* @protected
* @param {number} timeStep - The current timeStep value as determined by Game.update.
*/
updateLogic: function (timeStep) {
if (!this._paused && !this.pendingStep)
{
if (this.stepping)
{
this.pendingStep = true;
}
this.scale.preUpdate();
this.debug.preUpdate();
this.camera.preUpdate();
this.physics.preUpdate();
this.state.preUpdate(timeStep);
this.plugins.preUpdate(timeStep);
this.stage.preUpdate();
this.state.update();
this.stage.update();
this.tweens.update();
this.sound.update();
this.input.update();
this.physics.update();
this.particles.update();
this.plugins.update();
this.stage.postUpdate();
this.plugins.postUpdate();
}
else
{
// Scaling and device orientation changes are still reflected when paused.
this.scale.pauseUpdate();
this.state.pauseUpdate();
this.debug.preUpdate();
}
this.stage.updateTransform();
},
/**
* Runs the Render cycle.
* It starts by calling State.preRender. In here you can do any last minute adjustments of display objects as required.
* It then calls the renderer, which renders the entire display list, starting from the Stage object and working down.
* It then calls plugin.render on any loaded plugins, in the order in which they were enabled.
* After this State.render is called. Any rendering that happens here will take place on-top of the display list.
* Finally plugin.postRender is called on any loaded plugins, in the order in which they were enabled.
* This method is called automatically by Game.update, you don't need to call it directly.
* Should you wish to have fine-grained control over when Phaser renders then use the `Game.lockRender` boolean.
* Phaser will only render when this boolean is `false`.
*
* @method Phaser.Game#updateRender
* @protected
* @param {number} elapsedTime - The time elapsed since the last update.
*/
updateRender: function (elapsedTime) {
if (this.lockRender)
{
return;
}
this.state.preRender(elapsedTime);
if (this.renderType !== Phaser.HEADLESS)
{
this.renderer.render(this.stage);
this.plugins.render(elapsedTime);
this.state.render(elapsedTime);
}
this.plugins.postRender(elapsedTime);
},
/**
* Enable core game loop stepping. When enabled you must call game.step() directly (perhaps via a DOM button?)
* Calling step will advance the game loop by one frame. This is extremely useful for hard to track down errors!
*
* @method Phaser.Game#enableStep
*/
enableStep: function () {
this.stepping = true;
this.pendingStep = false;
this.stepCount = 0;
},
/**
* Disables core game loop stepping.
*
* @method Phaser.Game#disableStep
*/
disableStep: function () {
this.stepping = false;
this.pendingStep = false;
},
/**
* When stepping is enabled you must call this function directly (perhaps via a DOM button?) to advance the game loop by one frame.
* This is extremely useful to hard to track down errors! Use the internal stepCount property to monitor progress.
*
* @method Phaser.Game#step
*/
step: function () {
this.pendingStep = false;
this.stepCount++;
},
/**
* Nukes the entire game from orbit.
*
* Calls destroy on Game.state, Game.sound, Game.scale, Game.stage, Game.input, Game.physics and Game.plugins.
*
* Then sets all of those local handlers to null, destroys the renderer, removes the canvas from the DOM
* and resets the PIXI default renderer.
*
* @method Phaser.Game#destroy
*/
destroy: function () {
this.raf.stop();
this.state.destroy();
this.sound.destroy();
this.scale.destroy();
this.stage.destroy();
this.input.destroy();
this.physics.destroy();
this.plugins.destroy();
this.state = null;
this.sound = null;
this.scale = null;
this.stage = null;
this.input = null;
this.physics = null;
this.plugins = null;
this.cache = null;
this.load = null;
this.time = null;
this.world = null;
this.isBooted = false;
this.renderer.destroy(false);
Phaser.Canvas.removeFromDOM(this.canvas);
PIXI.defaultRenderer = null;
Phaser.GAMES[this.id] = null;
},
/**
* Called by the Stage visibility handler.
*
* @method Phaser.Game#gamePaused
* @param {object} event - The DOM event that caused the game to pause, if any.
* @protected
*/
gamePaused: function (event) {
// If the game is already paused it was done via game code, so don't re-pause it
if (!this._paused)
{
this._paused = true;
this.time.gamePaused();
if (this.sound.muteOnPause)
{
this.sound.setMute();
}
this.onPause.dispatch(event);
// Avoids Cordova iOS crash event: https://github.com/photonstorm/phaser/issues/1800
if (this.device.cordova && this.device.iOS)
{
this.lockRender = true;
}
}
},
/**
* Called by the Stage visibility handler.
*
* @method Phaser.Game#gameResumed
* @param {object} event - The DOM event that caused the game to pause, if any.
* @protected
*/
gameResumed: function (event) {
// Game is paused, but wasn't paused via code, so resume it
if (this._paused && !this._codePaused)
{
this._paused = false;
this.time.gameResumed();
this.input.reset();
if (this.sound.muteOnPause)
{
this.sound.unsetMute();
}
this.onResume.dispatch(event);
// Avoids Cordova iOS crash event: https://github.com/photonstorm/phaser/issues/1800
if (this.device.cordova && this.device.iOS)
{
this.lockRender = false;
}
}
},
/**
* Called by the Stage visibility handler.
*
* @method Phaser.Game#focusLoss
* @param {object} event - The DOM event that caused the game to pause, if any.
* @protected
*/
focusLoss: function (event) {
this.onBlur.dispatch(event);
if (!this.stage.disableVisibilityChange)
{
this.gamePaused(event);
}
},
/**
* Called by the Stage visibility handler.
*
* @method Phaser.Game#focusGain
* @param {object} event - The DOM event that caused the game to pause, if any.
* @protected
*/
focusGain: function (event) {
this.onFocus.dispatch(event);
if (!this.stage.disableVisibilityChange)
{
this.gameResumed(event);
}
}
};
Phaser.Game.prototype.constructor = Phaser.Game;
/**
* The paused state of the Game. A paused game doesn't update any of its subsystems.
* When a game is paused the onPause event is dispatched. When it is resumed the onResume event is dispatched.
* @name Phaser.Game#paused
* @property {boolean} paused - Gets and sets the paused state of the Game.
*/
Object.defineProperty(Phaser.Game.prototype, "paused", {
get: function () {
return this._paused;
},
set: function (value) {
if (value === true)
{
if (this._paused === false)
{
this._paused = true;
this.sound.setMute();
this.time.gamePaused();
this.onPause.dispatch(this);
}
this._codePaused = true;
}
else
{
if (this._paused)
{
this._paused = false;
this.input.reset();
this.sound.unsetMute();
this.time.gameResumed();
this.onResume.dispatch(this);
}
this._codePaused = false;
}
}
});
/**
*
* "Deleted code is debugged code." - Jeff Sickel
*
* ヽ(〃^▽^〃)ノ
*
*/
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Phaser.Input is the Input Manager for all types of Input across Phaser, including mouse, keyboard, touch and MSPointer.
* The Input manager is updated automatically by the core game loop.
*
* @class Phaser.Input
* @constructor
* @param {Phaser.Game} game - Current game instance.
*/
Phaser.Input = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {HTMLCanvasElement} hitCanvas - The canvas to which single pixels are drawn in order to perform pixel-perfect hit detection.
* @default
*/
this.hitCanvas = null;
/**
* @property {CanvasRenderingContext2D} hitContext - The context of the pixel perfect hit canvas.
* @default
*/
this.hitContext = null;
/**
* An array of callbacks that will be fired every time the activePointer receives a move event from the DOM.
* To add a callback to this array please use `Input.addMoveCallback`.
* @property {array} moveCallbacks
* @protected
*/
this.moveCallbacks = [];
/**
* @property {function} customCandidateHandler - See Input.setInteractiveCandidateHandler.
* @private
*/
this.customCandidateHandler = null;
/**
* @property {object} customCandidateHandlerContext - See Input.setInteractiveCandidateHandler.
* @private
*/
this.customCandidateHandlerContext = null;
/**
* @property {number} pollRate - How often should the input pointers be checked for updates? A value of 0 means every single frame (60fps); a value of 1 means every other frame (30fps) and so on.
* @default
*/
this.pollRate = 0;
/**
* When enabled, input (eg. Keyboard, Mouse, Touch) will be processed - as long as the individual sources are enabled themselves.
*
* When not enabled, _all_ input sources are ignored. To disable just one type of input; for example, the Mouse, use `input.mouse.enabled = false`.
* @property {boolean} enabled
* @default
*/
this.enabled = true;
/**
* @property {number} multiInputOverride - Controls the expected behavior when using a mouse and touch together on a multi-input device.
* @default
*/
this.multiInputOverride = Phaser.Input.MOUSE_TOUCH_COMBINE;
/**
* @property {Phaser.Point} position - A point object representing the current position of the Pointer.
* @default
*/
this.position = null;
/**
* @property {Phaser.Point} speed - A point object representing the speed of the Pointer. Only really useful in single Pointer games; otherwise see the Pointer objects directly.
*/
this.speed = null;
/**
* A Circle object centered on the x/y screen coordinates of the Input.
* Default size of 44px (Apples recommended "finger tip" size) but can be changed to anything.
* @property {Phaser.Circle} circle
*/
this.circle = null;
/**
* @property {Phaser.Point} scale - The scale by which all input coordinates are multiplied; calculated by the ScaleManager. In an un-scaled game the values will be x = 1 and y = 1.
*/
this.scale = null;
/**
* @property {integer} maxPointers - The maximum number of Pointers allowed to be active at any one time. A value of -1 is only limited by the total number of pointers. For lots of games it's useful to set this to 1.
* @default -1 (Limited by total pointers.)
*/
this.maxPointers = -1;
/**
* @property {number} tapRate - The number of milliseconds that the Pointer has to be pressed down and then released to be considered a tap or click.
* @default
*/
this.tapRate = 200;
/**
* @property {number} doubleTapRate - The number of milliseconds between taps of the same Pointer for it to be considered a double tap / click.
* @default
*/
this.doubleTapRate = 300;
/**
* @property {number} holdRate - The number of milliseconds that the Pointer has to be pressed down for it to fire a onHold event.
* @default
*/
this.holdRate = 2000;
/**
* @property {number} justPressedRate - The number of milliseconds below which the Pointer is considered justPressed.
* @default
*/
this.justPressedRate = 200;
/**
* @property {number} justReleasedRate - The number of milliseconds below which the Pointer is considered justReleased .
* @default
*/
this.justReleasedRate = 200;
/**
* Sets if the Pointer objects should record a history of x/y coordinates they have passed through.
* The history is cleared each time the Pointer is pressed down.
* The history is updated at the rate specified in Input.pollRate
* @property {boolean} recordPointerHistory
* @default
*/
this.recordPointerHistory = false;
/**
* @property {number} recordRate - The rate in milliseconds at which the Pointer objects should update their tracking history.
* @default
*/
this.recordRate = 100;
/**
* The total number of entries that can be recorded into the Pointer objects tracking history.
* If the Pointer is tracking one event every 100ms; then a trackLimit of 100 would store the last 10 seconds worth of history.
* @property {number} recordLimit
* @default
*/
this.recordLimit = 100;
/**
* @property {Phaser.Pointer} pointer1 - A Pointer object.
*/
this.pointer1 = null;
/**
* @property {Phaser.Pointer} pointer2 - A Pointer object.
*/
this.pointer2 = null;
/**
* @property {Phaser.Pointer} pointer3 - A Pointer object.
*/
this.pointer3 = null;
/**
* @property {Phaser.Pointer} pointer4 - A Pointer object.
*/
this.pointer4 = null;
/**
* @property {Phaser.Pointer} pointer5 - A Pointer object.
*/
this.pointer5 = null;
/**
* @property {Phaser.Pointer} pointer6 - A Pointer object.
*/
this.pointer6 = null;
/**
* @property {Phaser.Pointer} pointer7 - A Pointer object.
*/
this.pointer7 = null;
/**
* @property {Phaser.Pointer} pointer8 - A Pointer object.
*/
this.pointer8 = null;
/**
* @property {Phaser.Pointer} pointer9 - A Pointer object.
*/
this.pointer9 = null;
/**
* @property {Phaser.Pointer} pointer10 - A Pointer object.
*/
this.pointer10 = null;
/**
* An array of non-mouse pointers that have been added to the game.
* The properties `pointer1..N` are aliases for `pointers[0..N-1]`.
* @property {Phaser.Pointer[]} pointers
* @public
* @readonly
*/
this.pointers = [];
/**
* The most recently active Pointer object.
*
* When you've limited max pointers to 1 this will accurately be either the first finger touched or mouse.
*
* @property {Phaser.Pointer} activePointer
*/
this.activePointer = null;
/**
* The mouse has its own unique Phaser.Pointer object which you can use if making a desktop specific game.
*
* @property {Pointer} mousePointer
*/
this.mousePointer = null;
/**
* The Mouse Input manager.
*
* You should not usually access this manager directly, but instead use Input.mousePointer or Input.activePointer
* which normalizes all the input values for you, regardless of browser.
*
* @property {Phaser.Mouse} mouse
*/
this.mouse = null;
/**
* The Keyboard Input manager.
*
* @property {Phaser.Keyboard} keyboard
*/
this.keyboard = null;
/**
* The Touch Input manager.
*
* You should not usually access this manager directly, but instead use Input.activePointer
* which normalizes all the input values for you, regardless of browser.
*
* @property {Phaser.Touch} touch
*/
this.touch = null;
/**
* The MSPointer Input manager.
*
* You should not usually access this manager directly, but instead use Input.activePointer
* which normalizes all the input values for you, regardless of browser.
*
* @property {Phaser.MSPointer} mspointer
*/
this.mspointer = null;
/**
* The Gamepad Input manager.
*
* @property {Phaser.Gamepad} gamepad
*/
this.gamepad = null;
/**
* If the Input Manager has been reset locked then all calls made to InputManager.reset,
* such as from a State change, are ignored.
* @property {boolean} resetLocked
* @default
*/
this.resetLocked = false;
/**
* A Signal that is dispatched each time a pointer is pressed down.
* @property {Phaser.Signal} onDown
*/
this.onDown = null;
/**
* A Signal that is dispatched each time a pointer is released.
* @property {Phaser.Signal} onUp
*/
this.onUp = null;
/**
* A Signal that is dispatched each time a pointer is tapped.
* @property {Phaser.Signal} onTap
*/
this.onTap = null;
/**
* A Signal that is dispatched each time a pointer is held down.
* @property {Phaser.Signal} onHold
*/
this.onHold = null;
/**
* You can tell all Pointers to ignore any Game Object with a `priorityID` lower than this value.
* This is useful when stacking UI layers. Set to zero to disable.
* @property {number} minPriorityID
* @default
*/
this.minPriorityID = 0;
/**
* A list of interactive objects. The InputHandler components add and remove themselves from this list.
* @property {Phaser.ArraySet} interactiveItems
*/
this.interactiveItems = new Phaser.ArraySet();
/**
* @property {Phaser.Point} _localPoint - Internal cache var.
* @private
*/
this._localPoint = new Phaser.Point();
/**
* @property {number} _pollCounter - Internal var holding the current poll counter.
* @private
*/
this._pollCounter = 0;
/**
* @property {Phaser.Point} _oldPosition - A point object representing the previous position of the Pointer.
* @private
*/
this._oldPosition = null;
/**
* @property {number} _x - x coordinate of the most recent Pointer event
* @private
*/
this._x = 0;
/**
* @property {number} _y - Y coordinate of the most recent Pointer event
* @private
*/
this._y = 0;
};
/**
* @constant
* @type {number}
*/
Phaser.Input.MOUSE_OVERRIDES_TOUCH = 0;
/**
* @constant
* @type {number}
*/
Phaser.Input.TOUCH_OVERRIDES_MOUSE = 1;
/**
* @constant
* @type {number}
*/
Phaser.Input.MOUSE_TOUCH_COMBINE = 2;
/**
* The maximum number of pointers that can be added. This excludes the mouse pointer.
* @constant
* @type {integer}
*/
Phaser.Input.MAX_POINTERS = 10;
Phaser.Input.prototype = {
/**
* Starts the Input Manager running.
*
* @method Phaser.Input#boot
* @protected
*/
boot: function () {
this.mousePointer = new Phaser.Pointer(this.game, 0, Phaser.PointerMode.CURSOR);
this.addPointer();
this.addPointer();
this.mouse = new Phaser.Mouse(this.game);
this.touch = new Phaser.Touch(this.game);
this.mspointer = new Phaser.MSPointer(this.game);
if (Phaser.Keyboard)
{
this.keyboard = new Phaser.Keyboard(this.game);
}
if (Phaser.Gamepad)
{
this.gamepad = new Phaser.Gamepad(this.game);
}
this.onDown = new Phaser.Signal();
this.onUp = new Phaser.Signal();
this.onTap = new Phaser.Signal();
this.onHold = new Phaser.Signal();
this.scale = new Phaser.Point(1, 1);
this.speed = new Phaser.Point();
this.position = new Phaser.Point();
this._oldPosition = new Phaser.Point();
this.circle = new Phaser.Circle(0, 0, 44);
this.activePointer = this.mousePointer;
this.hitCanvas = Phaser.CanvasPool.create(this, 1, 1);
this.hitContext = this.hitCanvas.getContext('2d');
this.mouse.start();
this.touch.start();
this.mspointer.start();
this.mousePointer.active = true;
if (this.keyboard)
{
this.keyboard.start();
}
var _this = this;
this._onClickTrampoline = function (event) {
_this.onClickTrampoline(event);
};
this.game.canvas.addEventListener('click', this._onClickTrampoline, false);
},
/**
* Stops all of the Input Managers from running.
*
* @method Phaser.Input#destroy
*/
destroy: function () {
this.mouse.stop();
this.touch.stop();
this.mspointer.stop();
if (this.keyboard)
{
this.keyboard.stop();
}
if (this.gamepad)
{
this.gamepad.stop();
}
this.moveCallbacks = [];
Phaser.CanvasPool.remove(this);
this.game.canvas.removeEventListener('click', this._onClickTrampoline);
},
/**
* Adds a callback that is fired every time `Pointer.processInteractiveObjects` is called.
* The purpose of `processInteractiveObjects` is to work out which Game Object the Pointer is going to
* interact with. It works by polling all of the valid game objects, and then slowly discounting those
* that don't meet the criteria (i.e. they aren't under the Pointer, are disabled, invisible, etc).
*
* Eventually a short-list of 'candidates' is created. These are all of the Game Objects which are valid
* for input and overlap with the Pointer. If you need fine-grained control over which of the items is
* selected then you can use this callback to do so.
*
* The callback will be sent 3 parameters:
*
* 1) A reference to the Phaser.Pointer object that is processing the Items.
* 2) An array containing all potential interactive candidates. This is an array of `InputHandler` objects, not Sprites.
* 3) The current 'favorite' candidate, based on its priorityID and position in the display list.
*
* Your callback MUST return one of the candidates sent to it.
*
* @method Phaser.Input#setInteractiveCandidateHandler
* @param {function} callback - The callback that will be called each time `Pointer.processInteractiveObjects` is called. Set to `null` to disable.
* @param {object} context - The context in which the callback will be called.
*/
setInteractiveCandidateHandler: function (callback, context) {
this.customCandidateHandler = callback;
this.customCandidateHandlerContext = context;
},
/**
* Adds a callback that is fired every time the activePointer receives a DOM move event such as a mousemove or touchmove.
*
* The callback will be sent 4 parameters:
*
* A reference to the Phaser.Pointer object that moved,
* The x position of the pointer,
* The y position,
* A boolean indicating if the movement was the result of a 'click' event (such as a mouse click or touch down).
*
* It will be called every time the activePointer moves, which in a multi-touch game can be a lot of times, so this is best
* to only use if you've limited input to a single pointer (i.e. mouse or touch).
*
* The callback is added to the Phaser.Input.moveCallbacks array and should be removed with Phaser.Input.deleteMoveCallback.
*
* @method Phaser.Input#addMoveCallback
* @param {function} callback - The callback that will be called each time the activePointer receives a DOM move event.
* @param {object} context - The context in which the callback will be called.
*/
addMoveCallback: function (callback, context) {
this.moveCallbacks.push({ callback: callback, context: context });
},
/**
* Removes the callback from the Phaser.Input.moveCallbacks array.
*
* @method Phaser.Input#deleteMoveCallback
* @param {function} callback - The callback to be removed.
* @param {object} context - The context in which the callback exists.
*/
deleteMoveCallback: function (callback, context) {
var i = this.moveCallbacks.length;
while (i--)
{
if (this.moveCallbacks[i].callback === callback && this.moveCallbacks[i].context === context)
{
this.moveCallbacks.splice(i, 1);
return;
}
}
},
/**
* Add a new Pointer object to the Input Manager.
* By default Input creates 3 pointer objects: `mousePointer` (not include in part of general pointer pool), `pointer1` and `pointer2`.
* This method adds an additional pointer, up to a maximum of Phaser.Input.MAX_POINTERS (default of 10).
*
* @method Phaser.Input#addPointer
* @return {Phaser.Pointer|null} The new Pointer object that was created; null if a new pointer could not be added.
*/
addPointer: function () {
if (this.pointers.length >= Phaser.Input.MAX_POINTERS)
{
console.warn("Phaser.Input.addPointer: Maximum limit of " + Phaser.Input.MAX_POINTERS + " pointers reached.");
return null;
}
var id = this.pointers.length + 1;
var pointer = new Phaser.Pointer(this.game, id, Phaser.PointerMode.TOUCH);
this.pointers.push(pointer);
this['pointer' + id] = pointer;
return pointer;
},
/**
* Updates the Input Manager. Called by the core Game loop.
*
* @method Phaser.Input#update
* @protected
*/
update: function () {
if (this.keyboard)
{
this.keyboard.update();
}
if (this.pollRate > 0 && this._pollCounter < this.pollRate)
{
this._pollCounter++;
return;
}
this.speed.x = this.position.x - this._oldPosition.x;
this.speed.y = this.position.y - this._oldPosition.y;
this._oldPosition.copyFrom(this.position);
this.mousePointer.update();
if (this.gamepad && this.gamepad.active)
{
this.gamepad.update();
}
for (var i = 0; i < this.pointers.length; i++)
{
this.pointers[i].update();
}
this._pollCounter = 0;
},
/**
* Reset all of the Pointers and Input states.
*
* The optional `hard` parameter will reset any events or callbacks that may be bound.
* Input.reset is called automatically during a State change or if a game loses focus / visibility.
* To control control the reset manually set {@link Phaser.InputManager.resetLocked} to `true`.
*
* @method Phaser.Input#reset
* @public
* @param {boolean} [hard=false] - A soft reset won't reset any events or callbacks that are bound. A hard reset will.
*/
reset: function (hard) {
if (!this.game.isBooted || this.resetLocked)
{
return;
}
if (hard === undefined) { hard = false; }
this.mousePointer.reset();
if (this.keyboard)
{
this.keyboard.reset(hard);
}
if (this.gamepad)
{
this.gamepad.reset();
}
for (var i = 0; i < this.pointers.length; i++)
{
this.pointers[i].reset();
}
if (this.game.canvas.style.cursor !== 'none')
{
this.game.canvas.style.cursor = 'inherit';
}
if (hard)
{
this.onDown.dispose();
this.onUp.dispose();
this.onTap.dispose();
this.onHold.dispose();
this.onDown = new Phaser.Signal();
this.onUp = new Phaser.Signal();
this.onTap = new Phaser.Signal();
this.onHold = new Phaser.Signal();
this.moveCallbacks = [];
}
this._pollCounter = 0;
},
/**
* Resets the speed and old position properties.
*
* @method Phaser.Input#resetSpeed
* @param {number} x - Sets the oldPosition.x value.
* @param {number} y - Sets the oldPosition.y value.
*/
resetSpeed: function (x, y) {
this._oldPosition.setTo(x, y);
this.speed.setTo(0, 0);
},
/**
* Find the first free Pointer object and start it, passing in the event data.
* This is called automatically by Phaser.Touch and Phaser.MSPointer.
*
* @method Phaser.Input#startPointer
* @protected
* @param {any} event - The event data from the Touch event.
* @return {Phaser.Pointer} The Pointer object that was started or null if no Pointer object is available.
*/
startPointer: function (event) {
if (this.maxPointers >= 0 && this.countActivePointers(this.maxPointers) >= this.maxPointers)
{
return null;
}
if (!this.pointer1.active)
{
return this.pointer1.start(event);
}
if (!this.pointer2.active)
{
return this.pointer2.start(event);
}
for (var i = 2; i < this.pointers.length; i++)
{
var pointer = this.pointers[i];
if (!pointer.active)
{
return pointer.start(event);
}
}
return null;
},
/**
* Updates the matching Pointer object, passing in the event data.
* This is called automatically and should not normally need to be invoked.
*
* @method Phaser.Input#updatePointer
* @protected
* @param {any} event - The event data from the Touch event.
* @return {Phaser.Pointer} The Pointer object that was updated; null if no pointer was updated.
*/
updatePointer: function (event) {
if (this.pointer1.active && this.pointer1.identifier === event.identifier)
{
return this.pointer1.move(event);
}
if (this.pointer2.active && this.pointer2.identifier === event.identifier)
{
return this.pointer2.move(event);
}
for (var i = 2; i < this.pointers.length; i++)
{
var pointer = this.pointers[i];
if (pointer.active && pointer.identifier === event.identifier)
{
return pointer.move(event);
}
}
return null;
},
/**
* Stops the matching Pointer object, passing in the event data.
*
* @method Phaser.Input#stopPointer
* @protected
* @param {any} event - The event data from the Touch event.
* @return {Phaser.Pointer} The Pointer object that was stopped or null if no Pointer object is available.
*/
stopPointer: function (event) {
if (this.pointer1.active && this.pointer1.identifier === event.identifier)
{
return this.pointer1.stop(event);
}
if (this.pointer2.active && this.pointer2.identifier === event.identifier)
{
return this.pointer2.stop(event);
}
for (var i = 2; i < this.pointers.length; i++)
{
var pointer = this.pointers[i];
if (pointer.active && pointer.identifier === event.identifier)
{
return pointer.stop(event);
}
}
return null;
},
/**
* Returns the total number of active pointers, not exceeding the specified limit
*
* @name Phaser.Input#countActivePointers
* @private
* @property {integer} [limit=(max pointers)] - Stop counting after this.
* @return {integer} The number of active pointers, or limit - whichever is less.
*/
countActivePointers: function (limit) {
if (limit === undefined) { limit = this.pointers.length; }
var count = limit;
for (var i = 0; i < this.pointers.length && count > 0; i++)
{
var pointer = this.pointers[i];
if (pointer.active)
{
count--;
}
}
return (limit - count);
},
/**
* Get the first Pointer with the given active state.
*
* @method Phaser.Input#getPointer
* @param {boolean} [isActive=false] - The state the Pointer should be in - active or inactive?
* @return {Phaser.Pointer} A Pointer object or null if no Pointer object matches the requested state.
*/
getPointer: function (isActive) {
if (isActive === undefined) { isActive = false; }
for (var i = 0; i < this.pointers.length; i++)
{
var pointer = this.pointers[i];
if (pointer.active === isActive)
{
return pointer;
}
}
return null;
},
/**
* Get the Pointer object whos `identifier` property matches the given identifier value.
*
* The identifier property is not set until the Pointer has been used at least once, as its populated by the DOM event.
* Also it can change every time you press the pointer down, and is not fixed once set.
* Note: Not all browsers set the identifier property and it's not part of the W3C spec, so you may need getPointerFromId instead.
*
* @method Phaser.Input#getPointerFromIdentifier
* @param {number} identifier - The Pointer.identifier value to search for.
* @return {Phaser.Pointer} A Pointer object or null if no Pointer object matches the requested identifier.
*/
getPointerFromIdentifier: function (identifier) {
for (var i = 0; i < this.pointers.length; i++)
{
var pointer = this.pointers[i];
if (pointer.identifier === identifier)
{
return pointer;
}
}
return null;
},
/**
* Get the Pointer object whos `pointerId` property matches the given value.
*
* The pointerId property is not set until the Pointer has been used at least once, as its populated by the DOM event.
* Also it can change every time you press the pointer down if the browser recycles it.
*
* @method Phaser.Input#getPointerFromId
* @param {number} pointerId - The `pointerId` (not 'id') value to search for.
* @return {Phaser.Pointer} A Pointer object or null if no Pointer object matches the requested identifier.
*/
getPointerFromId: function (pointerId) {
for (var i = 0; i < this.pointers.length; i++)
{
var pointer = this.pointers[i];
if (pointer.pointerId === pointerId)
{
return pointer;
}
}
return null;
},
/**
* This will return the local coordinates of the specified displayObject based on the given Pointer.
*
* @method Phaser.Input#getLocalPosition
* @param {Phaser.Sprite|Phaser.Image} displayObject - The DisplayObject to get the local coordinates for.
* @param {Phaser.Pointer} pointer - The Pointer to use in the check against the displayObject.
* @return {Phaser.Point} A point containing the coordinates of the Pointer position relative to the DisplayObject.
*/
getLocalPosition: function (displayObject, pointer, output) {
if (output === undefined) { output = new Phaser.Point(); }
var wt = displayObject.worldTransform;
var id = 1 / (wt.a * wt.d + wt.c * -wt.b);
return output.setTo(
wt.d * id * pointer.x + -wt.c * id * pointer.y + (wt.ty * wt.c - wt.tx * wt.d) * id,
wt.a * id * pointer.y + -wt.b * id * pointer.x + (-wt.ty * wt.a + wt.tx * wt.b) * id
);
},
/**
* Tests if the pointer hits the given object.
*
* @method Phaser.Input#hitTest
* @param {DisplayObject} displayObject - The displayObject to test for a hit.
* @param {Phaser.Pointer} pointer - The pointer to use for the test.
* @param {Phaser.Point} localPoint - The local translated point.
*/
hitTest: function (displayObject, pointer, localPoint) {
if (!displayObject.worldVisible)
{
return false;
}
this.getLocalPosition(displayObject, pointer, this._localPoint);
localPoint.copyFrom(this._localPoint);
if (displayObject.hitArea && displayObject.hitArea.contains)
{
return (displayObject.hitArea.contains(this._localPoint.x, this._localPoint.y));
}
else if (displayObject instanceof Phaser.TileSprite)
{
var width = displayObject.width;
var height = displayObject.height;
var x1 = -width * displayObject.anchor.x;
if (this._localPoint.x >= x1 && this._localPoint.x < x1 + width)
{
var y1 = -height * displayObject.anchor.y;
if (this._localPoint.y >= y1 && this._localPoint.y < y1 + height)
{
return true;
}
}
}
else if (displayObject instanceof PIXI.Sprite)
{
var width = displayObject.texture.frame.width;
var height = displayObject.texture.frame.height;
var x1 = -width * displayObject.anchor.x;
if (this._localPoint.x >= x1 && this._localPoint.x < x1 + width)
{
var y1 = -height * displayObject.anchor.y;
if (this._localPoint.y >= y1 && this._localPoint.y < y1 + height)
{
return true;
}
}
}
else if (displayObject instanceof Phaser.Graphics)
{
for (var i = 0; i < displayObject.graphicsData.length; i++)
{
var data = displayObject.graphicsData[i];
if (!data.fill)
{
continue;
}
// Only deal with fills..
if (data.shape && data.shape.contains(this._localPoint.x, this._localPoint.y))
{
return true;
}
}
}
// Didn't hit the parent, does it have any children?
for (var i = 0; i < displayObject.children.length; i++)
{
if (this.hitTest(displayObject.children[i], pointer, localPoint))
{
return true;
}
}
return false;
},
/**
* Used for click trampolines. See {@link Phaser.Pointer.addClickTrampoline}.
*
* @method Phaser.Input#onClickTrampoline
* @private
*/
onClickTrampoline: function () {
// It might not always be the active pointer, but this does work on
// Desktop browsers (read: IE) with Mouse or MSPointer input.
this.activePointer.processClickTrampolines();
}
};
Phaser.Input.prototype.constructor = Phaser.Input;
/**
* The X coordinate of the most recently active pointer.
* This value takes game scaling into account automatically. See Pointer.screenX/clientX for source values.
* @name Phaser.Input#x
* @property {number} x
*/
Object.defineProperty(Phaser.Input.prototype, "x", {
get: function () {
return this._x;
},
set: function (value) {
this._x = Math.floor(value);
}
});
/**
* The Y coordinate of the most recently active pointer.
* This value takes game scaling into account automatically. See Pointer.screenY/clientY for source values.
* @name Phaser.Input#y
* @property {number} y
*/
Object.defineProperty(Phaser.Input.prototype, "y", {
get: function () {
return this._y;
},
set: function (value) {
this._y = Math.floor(value);
}
});
/**
* True if the Input is currently poll rate locked.
* @name Phaser.Input#pollLocked
* @property {boolean} pollLocked
* @readonly
*/
Object.defineProperty(Phaser.Input.prototype, "pollLocked", {
get: function () {
return (this.pollRate > 0 && this._pollCounter < this.pollRate);
}
});
/**
* The total number of inactive Pointers.
* @name Phaser.Input#totalInactivePointers
* @property {number} totalInactivePointers
* @readonly
*/
Object.defineProperty(Phaser.Input.prototype, "totalInactivePointers", {
get: function () {
return this.pointers.length - this.countActivePointers();
}
});
/**
* The total number of active Pointers, not counting the mouse pointer.
* @name Phaser.Input#totalActivePointers
* @property {integers} totalActivePointers
* @readonly
*/
Object.defineProperty(Phaser.Input.prototype, "totalActivePointers", {
get: function () {
return this.countActivePointers();
}
});
/**
* The world X coordinate of the most recently active pointer.
* @name Phaser.Input#worldX
* @property {number} worldX - The world X coordinate of the most recently active pointer.
* @readonly
*/
Object.defineProperty(Phaser.Input.prototype, "worldX", {
get: function () {
return this.game.camera.view.x + this.x;
}
});
/**
* The world Y coordinate of the most recently active pointer.
* @name Phaser.Input#worldY
* @property {number} worldY - The world Y coordinate of the most recently active pointer.
* @readonly
*/
Object.defineProperty(Phaser.Input.prototype, "worldY", {
get: function () {
return this.game.camera.view.y + this.y;
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Mouse class is responsible for handling all aspects of mouse interaction with the browser.
*
* It captures and processes mouse events that happen on the game canvas object.
* It also adds a single `mouseup` listener to `window` which is used to capture the mouse being released
* when not over the game.
*
* You should not normally access this class directly, but instead use a Phaser.Pointer object
* which normalises all game input for you, including accurate button handling.
*
* @class Phaser.Mouse
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.Mouse = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {Phaser.Input} input - A reference to the Phaser Input Manager.
* @protected
*/
this.input = game.input;
/**
* @property {object} callbackContext - The context under which callbacks are called.
*/
this.callbackContext = this.game;
/**
* @property {function} mouseDownCallback - A callback that can be fired when the mouse is pressed down.
*/
this.mouseDownCallback = null;
/**
* @property {function} mouseUpCallback - A callback that can be fired when the mouse is released from a pressed down state.
*/
this.mouseUpCallback = null;
/**
* @property {function} mouseOutCallback - A callback that can be fired when the mouse is no longer over the game canvas.
*/
this.mouseOutCallback = null;
/**
* @property {function} mouseOverCallback - A callback that can be fired when the mouse enters the game canvas (usually after a mouseout).
*/
this.mouseOverCallback = null;
/**
* @property {function} mouseWheelCallback - A callback that can be fired when the mousewheel is used.
*/
this.mouseWheelCallback = null;
/**
* @property {boolean} capture - If true the DOM mouse events will have event.preventDefault applied to them, if false they will propagate fully.
*/
this.capture = false;
/**
* This property was removed in Phaser 2.4 and should no longer be used.
* Instead please see the Pointer button properties such as `Pointer.leftButton`, `Pointer.rightButton` and so on.
* Or Pointer.button holds the DOM event button value if you require that.
* @property {number} button
* @default
*/
this.button = -1;
/**
* The direction of the _last_ mousewheel usage 1 for up -1 for down.
* @property {number} wheelDelta
*/
this.wheelDelta = 0;
/**
* Mouse input will only be processed if enabled.
* @property {boolean} enabled
* @default
*/
this.enabled = true;
/**
* @property {boolean} locked - If the mouse has been Pointer Locked successfully this will be set to true.
* @default
*/
this.locked = false;
/**
* @property {boolean} stopOnGameOut - If true Pointer.stop will be called if the mouse leaves the game canvas.
* @default
*/
this.stopOnGameOut = false;
/**
* @property {Phaser.Signal} pointerLock - This event is dispatched when the browser enters or leaves pointer lock state.
* @default
*/
this.pointerLock = new Phaser.Signal();
/**
* The browser mouse DOM event. Will be null if no mouse event has ever been received.
* Access this property only inside a Mouse event handler and do not keep references to it.
* @property {MouseEvent|null} event
* @default
*/
this.event = null;
/**
* @property {function} _onMouseDown - Internal event handler reference.
* @private
*/
this._onMouseDown = null;
/**
* @property {function} _onMouseMove - Internal event handler reference.
* @private
*/
this._onMouseMove = null;
/**
* @property {function} _onMouseUp - Internal event handler reference.
* @private
*/
this._onMouseUp = null;
/**
* @property {function} _onMouseOut - Internal event handler reference.
* @private
*/
this._onMouseOut = null;
/**
* @property {function} _onMouseOver - Internal event handler reference.
* @private
*/
this._onMouseOver = null;
/**
* @property {function} _onMouseWheel - Internal event handler reference.
* @private
*/
this._onMouseWheel = null;
/**
* Wheel proxy event object, if required. Shared for all wheel events for this mouse.
* @property {Phaser.Mouse~WheelEventProxy} _wheelEvent
* @private
*/
this._wheelEvent = null;
};
/**
* @constant
* @type {number}
*/
Phaser.Mouse.NO_BUTTON = -1;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.LEFT_BUTTON = 0;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.MIDDLE_BUTTON = 1;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.RIGHT_BUTTON = 2;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.BACK_BUTTON = 3;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.FORWARD_BUTTON = 4;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.WHEEL_UP = 1;
/**
* @constant
* @type {number}
*/
Phaser.Mouse.WHEEL_DOWN = -1;
Phaser.Mouse.prototype = {
/**
* Starts the event listeners running.
* @method Phaser.Mouse#start
*/
start: function () {
if (this.game.device.android && this.game.device.chrome === false)
{
// Android stock browser fires mouse events even if you preventDefault on the touchStart, so ...
return;
}
if (this._onMouseDown !== null)
{
// Avoid setting multiple listeners
return;
}
var _this = this;
this._onMouseDown = function (event) {
return _this.onMouseDown(event);
};
this._onMouseMove = function (event) {
return _this.onMouseMove(event);
};
this._onMouseUp = function (event) {
return _this.onMouseUp(event);
};
this._onMouseUpGlobal = function (event) {
return _this.onMouseUpGlobal(event);
};
this._onMouseOutGlobal = function (event) {
return _this.onMouseOutGlobal(event);
};
this._onMouseOut = function (event) {
return _this.onMouseOut(event);
};
this._onMouseOver = function (event) {
return _this.onMouseOver(event);
};
this._onMouseWheel = function (event) {
return _this.onMouseWheel(event);
};
var canvas = this.game.canvas;
canvas.addEventListener('mousedown', this._onMouseDown, true);
canvas.addEventListener('mousemove', this._onMouseMove, true);
canvas.addEventListener('mouseup', this._onMouseUp, true);
if (!this.game.device.cocoonJS)
{
window.addEventListener('mouseup', this._onMouseUpGlobal, true);
window.addEventListener('mouseout', this._onMouseOutGlobal, true);
canvas.addEventListener('mouseover', this._onMouseOver, true);
canvas.addEventListener('mouseout', this._onMouseOut, true);
}
var wheelEvent = this.game.device.wheelEvent;
if (wheelEvent)
{
canvas.addEventListener(wheelEvent, this._onMouseWheel, true);
if (wheelEvent === 'mousewheel')
{
this._wheelEvent = new WheelEventProxy(-1/40, 1);
}
else if (wheelEvent === 'DOMMouseScroll')
{
this._wheelEvent = new WheelEventProxy(1, 1);
}
}
},
/**
* The internal method that handles the mouse down event from the browser.
* @method Phaser.Mouse#onMouseDown
* @param {MouseEvent} event - The native event from the browser. This gets stored in Mouse.event.
*/
onMouseDown: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (this.mouseDownCallback)
{
this.mouseDownCallback.call(this.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
event['identifier'] = 0;
this.input.mousePointer.start(event);
},
/**
* The internal method that handles the mouse move event from the browser.
* @method Phaser.Mouse#onMouseMove
* @param {MouseEvent} event - The native event from the browser. This gets stored in Mouse.event.
*/
onMouseMove: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (this.mouseMoveCallback)
{
this.mouseMoveCallback.call(this.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
event['identifier'] = 0;
this.input.mousePointer.move(event);
},
/**
* The internal method that handles the mouse up event from the browser.
* @method Phaser.Mouse#onMouseUp
* @param {MouseEvent} event - The native event from the browser. This gets stored in Mouse.event.
*/
onMouseUp: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (this.mouseUpCallback)
{
this.mouseUpCallback.call(this.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
event['identifier'] = 0;
this.input.mousePointer.stop(event);
},
/**
* The internal method that handles the mouse up event from the window.
*
* @method Phaser.Mouse#onMouseUpGlobal
* @param {MouseEvent} event - The native event from the browser. This gets stored in Mouse.event.
*/
onMouseUpGlobal: function (event) {
if (!this.input.mousePointer.withinGame)
{
if (this.mouseUpCallback)
{
this.mouseUpCallback.call(this.callbackContext, event);
}
event['identifier'] = 0;
this.input.mousePointer.stop(event);
}
},
/**
* The internal method that handles the mouse out event from the window.
*
* @method Phaser.Mouse#onMouseOutGlobal
* @param {MouseEvent} event - The native event from the browser. This gets stored in Mouse.event.
*/
onMouseOutGlobal: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
this.input.mousePointer.withinGame = false;
if (!this.input.enabled || !this.enabled)
{
return;
}
// If we get a mouseout event from the window then basically
// something serious has gone down, usually along the lines of
// the browser opening a context-menu or similar.
// On OS X Chrome especially this is bad news, as it blocks
// us then getting a mouseup event, so we need to force that through.
//
// No matter what, we must cancel the left and right buttons
this.input.mousePointer.stop(event);
this.input.mousePointer.leftButton.stop(event);
this.input.mousePointer.rightButton.stop(event);
},
/**
* The internal method that handles the mouse out event from the browser.
*
* @method Phaser.Mouse#onMouseOut
* @param {MouseEvent} event - The native event from the browser. This gets stored in Mouse.event.
*/
onMouseOut: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
this.input.mousePointer.withinGame = false;
if (this.mouseOutCallback)
{
this.mouseOutCallback.call(this.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
if (this.stopOnGameOut)
{
event['identifier'] = 0;
this.input.mousePointer.stop(event);
}
},
/**
* The internal method that handles the mouse over event from the browser.
*
* @method Phaser.Mouse#onMouseOver
* @param {MouseEvent} event - The native event from the browser. This gets stored in Mouse.event.
*/
onMouseOver: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
this.input.mousePointer.withinGame = true;
if (this.mouseOverCallback)
{
this.mouseOverCallback.call(this.callbackContext, event);
}
},
/**
* The internal method that handles the mouse wheel event from the browser.
*
* @method Phaser.Mouse#onMouseWheel
* @param {MouseEvent} event - The native event from the browser.
*/
onMouseWheel: function (event) {
if (this._wheelEvent) {
event = this._wheelEvent.bindEvent(event);
}
this.event = event;
if (this.capture)
{
event.preventDefault();
}
// reverse detail for firefox
this.wheelDelta = Phaser.Math.clamp(-event.deltaY, -1, 1);
if (this.mouseWheelCallback)
{
this.mouseWheelCallback.call(this.callbackContext, event);
}
},
/**
* If the browser supports it you can request that the pointer be locked to the browser window.
* This is classically known as 'FPS controls', where the pointer can't leave the browser until the user presses an exit key.
* If the browser successfully enters a locked state the event Phaser.Mouse.pointerLock will be dispatched and the first parameter will be 'true'.
* @method Phaser.Mouse#requestPointerLock
*/
requestPointerLock: function () {
if (this.game.device.pointerLock)
{
var element = this.game.canvas;
element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
element.requestPointerLock();
var _this = this;
this._pointerLockChange = function (event) {
return _this.pointerLockChange(event);
};
document.addEventListener('pointerlockchange', this._pointerLockChange, true);
document.addEventListener('mozpointerlockchange', this._pointerLockChange, true);
document.addEventListener('webkitpointerlockchange', this._pointerLockChange, true);
}
},
/**
* Internal pointerLockChange handler.
*
* @method Phaser.Mouse#pointerLockChange
* @param {Event} event - The native event from the browser. This gets stored in Mouse.event.
*/
pointerLockChange: function (event) {
var element = this.game.canvas;
if (document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element)
{
// Pointer was successfully locked
this.locked = true;
this.pointerLock.dispatch(true, event);
}
else
{
// Pointer was unlocked
this.locked = false;
this.pointerLock.dispatch(false, event);
}
},
/**
* Internal release pointer lock handler.
* @method Phaser.Mouse#releasePointerLock
*/
releasePointerLock: function () {
document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock;
document.exitPointerLock();
document.removeEventListener('pointerlockchange', this._pointerLockChange, true);
document.removeEventListener('mozpointerlockchange', this._pointerLockChange, true);
document.removeEventListener('webkitpointerlockchange', this._pointerLockChange, true);
},
/**
* Stop the event listeners.
* @method Phaser.Mouse#stop
*/
stop: function () {
var canvas = this.game.canvas;
canvas.removeEventListener('mousedown', this._onMouseDown, true);
canvas.removeEventListener('mousemove', this._onMouseMove, true);
canvas.removeEventListener('mouseup', this._onMouseUp, true);
canvas.removeEventListener('mouseover', this._onMouseOver, true);
canvas.removeEventListener('mouseout', this._onMouseOut, true);
var wheelEvent = this.game.device.wheelEvent;
if (wheelEvent)
{
canvas.removeEventListener(wheelEvent, this._onMouseWheel, true);
}
window.removeEventListener('mouseup', this._onMouseUpGlobal, true);
window.removeEventListener('mouseout', this._onMouseOutGlobal, true);
document.removeEventListener('pointerlockchange', this._pointerLockChange, true);
document.removeEventListener('mozpointerlockchange', this._pointerLockChange, true);
document.removeEventListener('webkitpointerlockchange', this._pointerLockChange, true);
}
};
Phaser.Mouse.prototype.constructor = Phaser.Mouse;
/* jshint latedef:nofunc */
/**
* A purely internal event support class to proxy 'wheelscroll' and 'DOMMouseWheel'
* events to 'wheel'-like events.
*
* See https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel for choosing a scale and delta mode.
*
* @method Phaser.Mouse#WheelEventProxy
* @private
* @param {number} scaleFactor - Scale factor as applied to wheelDelta/wheelDeltaX or details.
* @param {integer} deltaMode - The reported delta mode.
*/
function WheelEventProxy (scaleFactor, deltaMode) {
/**
* @property {number} _scaleFactor - Scale factor as applied to wheelDelta/wheelDeltaX or details.
* @private
*/
this._scaleFactor = scaleFactor;
/**
* @property {number} _deltaMode - The reported delta mode.
* @private
*/
this._deltaMode = deltaMode;
/**
* @property {any} originalEvent - The original event _currently_ being proxied; the getters will follow suit.
* @private
*/
this.originalEvent = null;
}
WheelEventProxy.prototype = {};
WheelEventProxy.prototype.constructor = WheelEventProxy;
WheelEventProxy.prototype.bindEvent = function (event) {
// Generate stubs automatically
if (!WheelEventProxy._stubsGenerated && event)
{
var makeBinder = function (name) {
return function () {
var v = this.originalEvent[name];
return typeof v !== 'function' ? v : v.bind(this.originalEvent);
};
};
for (var prop in event)
{
if (!(prop in WheelEventProxy.prototype))
{
Object.defineProperty(WheelEventProxy.prototype, prop, {
get: makeBinder(prop)
});
}
}
WheelEventProxy._stubsGenerated = true;
}
this.originalEvent = event;
return this;
};
Object.defineProperties(WheelEventProxy.prototype, {
"type": { value: "wheel" },
"deltaMode": { get: function () { return this._deltaMode; } },
"deltaY": {
get: function () {
return (this._scaleFactor * (this.originalEvent.wheelDelta || this.originalEvent.detail)) || 0;
}
},
"deltaX": {
get: function () {
return (this._scaleFactor * this.originalEvent.wheelDeltaX) || 0;
}
},
"deltaZ": { value: 0 }
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The MSPointer class handles Microsoft touch interactions with the game and the resulting Pointer objects.
*
* It will work only in Internet Explorer 10+ and Windows Store or Windows Phone 8 apps using JavaScript.
* http://msdn.microsoft.com/en-us/library/ie/hh673557(v=vs.85).aspx
*
* You should not normally access this class directly, but instead use a Phaser.Pointer object which
* normalises all game input for you including accurate button handling.
*
* Please note that at the current time of writing Phaser does not yet support chorded button interactions:
* http://www.w3.org/TR/pointerevents/#chorded-button-interactions
*
* @class Phaser.MSPointer
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.MSPointer = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {Phaser.Input} input - A reference to the Phaser Input Manager.
* @protected
*/
this.input = game.input;
/**
* @property {object} callbackContext - The context under which callbacks are called (defaults to game).
*/
this.callbackContext = this.game;
/**
* @property {function} pointerDownCallback - A callback that can be fired on a MSPointerDown event.
*/
this.pointerDownCallback = null;
/**
* @property {function} pointerMoveCallback - A callback that can be fired on a MSPointerMove event.
*/
this.pointerMoveCallback = null;
/**
* @property {function} pointerUpCallback - A callback that can be fired on a MSPointerUp event.
*/
this.pointerUpCallback = null;
/**
* @property {boolean} capture - If true the Pointer events will have event.preventDefault applied to them, if false they will propagate fully.
*/
this.capture = true;
/**
* This property was removed in Phaser 2.4 and should no longer be used.
* Instead please see the Pointer button properties such as `Pointer.leftButton`, `Pointer.rightButton` and so on.
* Or Pointer.button holds the DOM event button value if you require that.
* @property {number} button
*/
this.button = -1;
/**
* The browser MSPointer DOM event. Will be null if no event has ever been received.
* Access this property only inside a Pointer event handler and do not keep references to it.
* @property {MSPointerEvent|null} event
* @default
*/
this.event = null;
/**
* MSPointer input will only be processed if enabled.
* @property {boolean} enabled
* @default
*/
this.enabled = true;
/**
* @property {function} _onMSPointerDown - Internal function to handle MSPointer events.
* @private
*/
this._onMSPointerDown = null;
/**
* @property {function} _onMSPointerMove - Internal function to handle MSPointer events.
* @private
*/
this._onMSPointerMove = null;
/**
* @property {function} _onMSPointerUp - Internal function to handle MSPointer events.
* @private
*/
this._onMSPointerUp = null;
/**
* @property {function} _onMSPointerUpGlobal - Internal function to handle MSPointer events.
* @private
*/
this._onMSPointerUpGlobal = null;
/**
* @property {function} _onMSPointerOut - Internal function to handle MSPointer events.
* @private
*/
this._onMSPointerOut = null;
/**
* @property {function} _onMSPointerOver - Internal function to handle MSPointer events.
* @private
*/
this._onMSPointerOver = null;
};
Phaser.MSPointer.prototype = {
/**
* Starts the event listeners running.
* @method Phaser.MSPointer#start
*/
start: function () {
if (this._onMSPointerDown !== null)
{
// Avoid setting multiple listeners
return;
}
var _this = this;
if (this.game.device.mspointer)
{
this._onMSPointerDown = function (event) {
return _this.onPointerDown(event);
};
this._onMSPointerMove = function (event) {
return _this.onPointerMove(event);
};
this._onMSPointerUp = function (event) {
return _this.onPointerUp(event);
};
this._onMSPointerUpGlobal = function (event) {
return _this.onPointerUpGlobal(event);
};
this._onMSPointerOut = function (event) {
return _this.onPointerOut(event);
};
this._onMSPointerOver = function (event) {
return _this.onPointerOver(event);
};
var canvas = this.game.canvas;
canvas.addEventListener('MSPointerDown', this._onMSPointerDown, false);
canvas.addEventListener('MSPointerMove', this._onMSPointerMove, false);
canvas.addEventListener('MSPointerUp', this._onMSPointerUp, false);
// IE11+ uses non-prefix events
canvas.addEventListener('pointerdown', this._onMSPointerDown, false);
canvas.addEventListener('pointermove', this._onMSPointerMove, false);
canvas.addEventListener('pointerup', this._onMSPointerUp, false);
canvas.style['-ms-content-zooming'] = 'none';
canvas.style['-ms-touch-action'] = 'none';
if (!this.game.device.cocoonJS)
{
window.addEventListener('MSPointerUp', this._onMSPointerUpGlobal, true);
canvas.addEventListener('MSPointerOver', this._onMSPointerOver, true);
canvas.addEventListener('MSPointerOut', this._onMSPointerOut, true);
// IE11+ uses non-prefix events
window.addEventListener('pointerup', this._onMSPointerUpGlobal, true);
canvas.addEventListener('pointerover', this._onMSPointerOver, true);
canvas.addEventListener('pointerout', this._onMSPointerOut, true);
}
}
},
/**
* The function that handles the PointerDown event.
*
* @method Phaser.MSPointer#onPointerDown
* @param {PointerEvent} event - The native DOM event.
*/
onPointerDown: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (this.pointerDownCallback)
{
this.pointerDownCallback.call(this.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
event.identifier = event.pointerId;
if (event.pointerType === 'mouse' || event.pointerType === 0x00000004)
{
this.input.mousePointer.start(event);
}
else
{
this.input.startPointer(event);
}
},
/**
* The function that handles the PointerMove event.
* @method Phaser.MSPointer#onPointerMove
* @param {PointerEvent} event - The native DOM event.
*/
onPointerMove: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (this.pointerMoveCallback)
{
this.pointerMoveCallback.call(this.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
event.identifier = event.pointerId;
if (event.pointerType === 'mouse' || event.pointerType === 0x00000004)
{
this.input.mousePointer.move(event);
}
else
{
this.input.updatePointer(event);
}
},
/**
* The function that handles the PointerUp event.
* @method Phaser.MSPointer#onPointerUp
* @param {PointerEvent} event - The native DOM event.
*/
onPointerUp: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (this.pointerUpCallback)
{
this.pointerUpCallback.call(this.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
event.identifier = event.pointerId;
if (event.pointerType === 'mouse' || event.pointerType === 0x00000004)
{
this.input.mousePointer.stop(event);
}
else
{
this.input.stopPointer(event);
}
},
/**
* The internal method that handles the mouse up event from the window.
*
* @method Phaser.MSPointer#onPointerUpGlobal
* @param {PointerEvent} event - The native event from the browser. This gets stored in MSPointer.event.
*/
onPointerUpGlobal: function (event) {
if ((event.pointerType === 'mouse' || event.pointerType === 0x00000004) && !this.input.mousePointer.withinGame)
{
this.onPointerUp(event);
}
else
{
var pointer = this.input.getPointerFromIdentifier(event.identifier);
if (pointer && pointer.withinGame)
{
this.onPointerUp(event);
}
}
},
/**
* The internal method that handles the pointer out event from the browser.
*
* @method Phaser.MSPointer#onPointerOut
* @param {PointerEvent} event - The native event from the browser. This gets stored in MSPointer.event.
*/
onPointerOut: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (event.pointerType === 'mouse' || event.pointerType === 0x00000004)
{
this.input.mousePointer.withinGame = false;
}
else
{
var pointer = this.input.getPointerFromIdentifier(event.identifier);
if (pointer)
{
pointer.withinGame = false;
}
}
if (this.input.mouse.mouseOutCallback)
{
this.input.mouse.mouseOutCallback.call(this.input.mouse.callbackContext, event);
}
if (!this.input.enabled || !this.enabled)
{
return;
}
if (this.input.mouse.stopOnGameOut)
{
event['identifier'] = 0;
if (pointer)
{
pointer.stop(event);
}
else
{
this.input.mousePointer.stop(event);
}
}
},
/**
* The internal method that handles the pointer out event from the browser.
*
* @method Phaser.MSPointer#onPointerOut
* @param {PointerEvent} event - The native event from the browser. This gets stored in MSPointer.event.
*/
onPointerOver: function (event) {
this.event = event;
if (this.capture)
{
event.preventDefault();
}
if (event.pointerType === 'mouse' || event.pointerType === 0x00000004)
{
this.input.mousePointer.withinGame = true;
}
else
{
var pointer = this.input.getPointerFromIdentifier(event.identifier);
if (pointer)
{
pointer.withinGame = true;
}
}
if (this.input.mouse.mouseOverCallback)
{
this.input.mouse.mouseOverCallback.call(this.input.mouse.callbackContext, event);
}
},
/**
* Stop the event listeners.
* @method Phaser.MSPointer#stop
*/
stop: function () {
var canvas = this.game.canvas;
canvas.removeEventListener('MSPointerDown', this._onMSPointerDown, false);
canvas.removeEventListener('MSPointerMove', this._onMSPointerMove, false);
canvas.removeEventListener('MSPointerUp', this._onMSPointerUp, false);
// IE11+ uses non-prefix events
canvas.removeEventListener('pointerdown', this._onMSPointerDown, false);
canvas.removeEventListener('pointermove', this._onMSPointerMove, false);
canvas.removeEventListener('pointerup', this._onMSPointerUp, false);
window.removeEventListener('MSPointerUp', this._onMSPointerUpGlobal, true);
canvas.removeEventListener('MSPointerOver', this._onMSPointerOver, true);
canvas.removeEventListener('MSPointerOut', this._onMSPointerOut, true);
// IE11+ uses non-prefix events
window.removeEventListener('pointerup', this._onMSPointerUpGlobal, true);
canvas.removeEventListener('pointerover', this._onMSPointerOver, true);
canvas.removeEventListener('pointerout', this._onMSPointerOut, true);
}
};
Phaser.MSPointer.prototype.constructor = Phaser.MSPointer;
/**
* @author Richard Davey <rich@photonstorm.com>
* @author @karlmacklin <tacklemcclean@gmail.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* DeviceButtons belong to both `Phaser.Pointer` and `Phaser.SinglePad` (Gamepad) instances.
*
* For Pointers they represent the various buttons that can exist on mice and pens, such as the left button, right button,
* middle button and advanced buttons like back and forward.
*
* Access them via `Pointer.leftbutton`, `Pointer.rightButton` and so on.
*
* On Gamepads they represent all buttons on the pad: from shoulder buttons to action buttons.
*
* At the time of writing this there are device limitations you should be aware of:
*
* - On Windows, if you install a mouse driver, and its utility software allows you to customize button actions
* (e.g., IntelliPoint and SetPoint), the middle (wheel) button, the 4th button, and the 5th button might not be set,
* even when they are pressed.
* - On Linux (GTK), the 4th button and the 5th button are not supported.
* - On Mac OS X 10.5 there is no platform API for implementing any advanced buttons.
*
* @class Phaser.DeviceButton
* @constructor
* @param {Phaser.Pointer|Phaser.SinglePad} parent - A reference to the parent of this button. Either a Pointer or a Gamepad.
* @param {number} buttonCode - The button code this DeviceButton is responsible for.
*/
Phaser.DeviceButton = function (parent, buttonCode) {
/**
* @property {Phaser.Pointer|Phaser.SinglePad} parent - A reference to the Pointer or Gamepad that owns this button.
*/
this.parent = parent;
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = parent.game;
/**
* @property {object} event - The DOM event that caused the change in button state.
* @default
*/
this.event = null;
/**
* @property {boolean} isDown - The "down" state of the button.
* @default
*/
this.isDown = false;
/**
* @property {boolean} isUp - The "up" state of the button.
* @default
*/
this.isUp = true;
/**
* @property {number} timeDown - The timestamp when the button was last pressed down.
* @default
*/
this.timeDown = 0;
/**
* @property {number} timeUp - The timestamp when the button was last released.
* @default
*/
this.timeUp = 0;
/**
* Gamepad only.
* If a button is held down this holds down the number of times the button has 'repeated'.
* @property {number} repeats
* @default
*/
this.repeats = 0;
/**
* True if the alt key was held down when this button was last pressed or released.
* Not supported on Gamepads.
* @property {boolean} altKey
* @default
*/
this.altKey = false;
/**
* True if the shift key was held down when this button was last pressed or released.
* Not supported on Gamepads.
* @property {boolean} shiftKey
* @default
*/
this.shiftKey = false;
/**
* True if the control key was held down when this button was last pressed or released.
* Not supported on Gamepads.
* @property {boolean} ctrlKey
* @default
*/
this.ctrlKey = false;
/**
* @property {number} value - Button value. Mainly useful for checking analog buttons (like shoulder triggers) on Gamepads.
* @default
*/
this.value = 0;
/**
* @property {number} buttonCode - The buttoncode of this button if a Gamepad, or the DOM button event value if a Pointer.
*/
this.buttonCode = buttonCode;
/**
* This Signal is dispatched every time this DeviceButton is pressed down.
* It is only dispatched once (until the button is released again).
* When dispatched it sends 2 arguments: A reference to this DeviceButton and the value of the button.
* @property {Phaser.Signal} onDown
*/
this.onDown = new Phaser.Signal();
/**
* This Signal is dispatched every time this DeviceButton is released from a down state.
* It is only dispatched once (until the button is pressed again).
* When dispatched it sends 2 arguments: A reference to this DeviceButton and the value of the button.
* @property {Phaser.Signal} onUp
*/
this.onUp = new Phaser.Signal();
/**
* Gamepad only.
* This Signal is dispatched every time this DeviceButton changes floating value (between, but not exactly, 0 and 1).
* When dispatched it sends 2 arguments: A reference to this DeviceButton and the value of the button.
* @property {Phaser.Signal} onFloat
*/
this.onFloat = new Phaser.Signal();
};
Phaser.DeviceButton.prototype = {
/**
* Called automatically by Phaser.Pointer and Phaser.SinglePad.
* Handles the button down state.
*
* @method Phaser.DeviceButton#start
* @protected
* @param {object} [event] - The DOM event that triggered the button change.
* @param {number} [value] - The button value. Only get for Gamepads.
*/
start: function (event, value) {
if (this.isDown)
{
return;
}
this.isDown = true;
this.isUp = false;
this.timeDown = this.game.time.time;
this.repeats = 0;
this.event = event;
this.value = value;
if (event)
{
this.altKey = event.altKey;
this.shiftKey = event.shiftKey;
this.ctrlKey = event.ctrlKey;
}
this.onDown.dispatch(this, value);
},
/**
* Called automatically by Phaser.Pointer and Phaser.SinglePad.
* Handles the button up state.
*
* @method Phaser.DeviceButton#stop
* @protected
* @param {object} [event] - The DOM event that triggered the button change.
* @param {number} [value] - The button value. Only get for Gamepads.
*/
stop: function (event, value) {
if (this.isUp)
{
return;
}
this.isDown = false;
this.isUp = true;
this.timeUp = this.game.time.time;
this.event = event;
this.value = value;
if (event)
{
this.altKey = event.altKey;
this.shiftKey = event.shiftKey;
this.ctrlKey = event.ctrlKey;
}
this.onUp.dispatch(this, value);
},
/**
* Called automatically by Phaser.SinglePad.
*
* @method Phaser.DeviceButton#padFloat
* @protected
* @param {number} value - Button value
*/
padFloat: function (value) {
this.value = value;
this.onFloat.dispatch(this, value);
},
/**
* Returns the "just pressed" state of this button.
* Just pressed is considered true if the button was pressed down within the duration given (default 250ms).
*
* @method Phaser.DeviceButton#justPressed
* @param {number} [duration=250] - The duration in ms below which the button is considered as being just pressed.
* @return {boolean} True if the button is just pressed otherwise false.
*/
justPressed: function (duration) {
duration = duration || 250;
return (this.isDown && (this.timeDown + duration) > this.game.time.time);
},
/**
* Returns the "just released" state of this button.
* Just released is considered as being true if the button was released within the duration given (default 250ms).
*
* @method Phaser.DeviceButton#justReleased
* @param {number} [duration=250] - The duration in ms below which the button is considered as being just released.
* @return {boolean} True if the button is just released otherwise false.
*/
justReleased: function (duration) {
duration = duration || 250;
return (this.isUp && (this.timeUp + duration) > this.game.time.time);
},
/**
* Resets this DeviceButton, changing it to an isUp state and resetting the duration and repeats counters.
*
* @method Phaser.DeviceButton#reset
*/
reset: function () {
this.isDown = false;
this.isUp = true;
this.timeDown = this.game.time.time;
this.repeats = 0;
this.altKey = false;
this.shiftKey = false;
this.ctrlKey = false;
},
/**
* Destroys this DeviceButton, this disposes of the onDown, onUp and onFloat signals
* and clears the parent and game references.
*
* @method Phaser.DeviceButton#destroy
*/
destroy: function () {
this.onDown.dispose();
this.onUp.dispose();
this.onFloat.dispose();
this.parent = null;
this.game = null;
}
};
Phaser.DeviceButton.prototype.constructor = Phaser.DeviceButton;
/**
* How long the button has been held down for in milliseconds.
* If not currently down it returns -1.
*
* @name Phaser.DeviceButton#duration
* @property {number} duration
* @readonly
*/
Object.defineProperty(Phaser.DeviceButton.prototype, "duration", {
get: function () {
if (this.isUp)
{
return -1;
}
return this.game.time.time - this.timeDown;
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Pointer object is used by the Mouse, Touch and MSPoint managers and represents a single finger on the touch screen.
*
* @class Phaser.Pointer
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {number} id - The ID of the Pointer object within the game. Each game can have up to 10 active pointers.
* @param {Phaser.PointerMode} pointerMode=(CURSOR|CONTACT) - The operational mode of this pointer, eg. CURSOR or TOUCH.
*/
Phaser.Pointer = function (game, id, pointerMode) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* @property {number} id - The ID of the Pointer object within the game. Each game can have up to 10 active pointers.
*/
this.id = id;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.POINTER;
/**
* @property {boolean} exists - A Pointer object that exists is allowed to be checked for physics collisions and overlaps.
* @default
*/
this.exists = true;
/**
* @property {number} identifier - The identifier property of the Pointer as set by the DOM event when this Pointer is started.
* @default
*/
this.identifier = 0;
/**
* @property {number} pointerId - The pointerId property of the Pointer as set by the DOM event when this Pointer is started. The browser can and will recycle this value.
* @default
*/
this.pointerId = null;
/**
* @property {Phaser.PointerMode} pointerMode - The operational mode of this pointer.
*/
this.pointerMode = pointerMode || (Phaser.PointerMode.CURSOR | Phaser.PointerMode.CONTACT);
/**
* @property {any} target - The target property of the Pointer as set by the DOM event when this Pointer is started.
* @default
*/
this.target = null;
/**
* The button property of the most recent DOM event when this Pointer is started.
* You should not rely on this value for accurate button detection, instead use the Pointer properties
* `leftButton`, `rightButton`, `middleButton` and so on.
* @property {any} button
* @default
*/
this.button = null;
/**
* If this Pointer is a Mouse or Pen / Stylus then you can access its left button directly through this property.
*
* The DeviceButton has its own properties such as `isDown`, `duration` and methods like `justReleased` for more fine-grained
* button control.
*
* @property {Phaser.DeviceButton} leftButton
* @default
*/
this.leftButton = new Phaser.DeviceButton(this, Phaser.Pointer.LEFT_BUTTON);
/**
* If this Pointer is a Mouse or Pen / Stylus then you can access its middle button directly through this property.
*
* The DeviceButton has its own properties such as `isDown`, `duration` and methods like `justReleased` for more fine-grained
* button control.
*
* Please see the DeviceButton docs for details on browser button limitations.
*
* @property {Phaser.DeviceButton} middleButton
* @default
*/
this.middleButton = new Phaser.DeviceButton(this, Phaser.Pointer.MIDDLE_BUTTON);
/**
* If this Pointer is a Mouse or Pen / Stylus then you can access its right button directly through this property.
*
* The DeviceButton has its own properties such as `isDown`, `duration` and methods like `justReleased` for more fine-grained
* button control.
*
* Please see the DeviceButton docs for details on browser button limitations.
*
* @property {Phaser.DeviceButton} rightButton
* @default
*/
this.rightButton = new Phaser.DeviceButton(this, Phaser.Pointer.RIGHT_BUTTON);
/**
* If this Pointer is a Mouse or Pen / Stylus then you can access its X1 (back) button directly through this property.
*
* The DeviceButton has its own properties such as `isDown`, `duration` and methods like `justReleased` for more fine-grained
* button control.
*
* Please see the DeviceButton docs for details on browser button limitations.
*
* @property {Phaser.DeviceButton} backButton
* @default
*/
this.backButton = new Phaser.DeviceButton(this, Phaser.Pointer.BACK_BUTTON);
/**
* If this Pointer is a Mouse or Pen / Stylus then you can access its X2 (forward) button directly through this property.
*
* The DeviceButton has its own properties such as `isDown`, `duration` and methods like `justReleased` for more fine-grained
* button control.
*
* Please see the DeviceButton docs for details on browser button limitations.
*
* @property {Phaser.DeviceButton} forwardButton
* @default
*/
this.forwardButton = new Phaser.DeviceButton(this, Phaser.Pointer.FORWARD_BUTTON);
/**
* If this Pointer is a Pen / Stylus then you can access its eraser button directly through this property.
*
* The DeviceButton has its own properties such as `isDown`, `duration` and methods like `justReleased` for more fine-grained
* button control.
*
* Please see the DeviceButton docs for details on browser button limitations.
*
* @property {Phaser.DeviceButton} eraserButton
* @default
*/
this.eraserButton = new Phaser.DeviceButton(this, Phaser.Pointer.ERASER_BUTTON);
/**
* @property {boolean} _holdSent - Local private variable to store the status of dispatching a hold event.
* @private
* @default
*/
this._holdSent = false;
/**
* @property {array} _history - Local private variable storing the short-term history of pointer movements.
* @private
*/
this._history = [];
/**
* @property {number} _nextDrop - Local private variable storing the time at which the next history drop should occur.
* @private
*/
this._nextDrop = 0;
/**
* @property {boolean} _stateReset - Monitor events outside of a state reset loop.
* @private
*/
this._stateReset = false;
/**
* @property {boolean} withinGame - true if the Pointer is over the game canvas, otherwise false.
*/
this.withinGame = false;
/**
* @property {number} clientX - The horizontal coordinate of the Pointer within the application's client area at which the event occurred (as opposed to the coordinates within the page).
*/
this.clientX = -1;
/**
* @property {number} clientY - The vertical coordinate of the Pointer within the application's client area at which the event occurred (as opposed to the coordinates within the page).
*/
this.clientY = -1;
/**
* @property {number} pageX - The horizontal coordinate of the Pointer relative to whole document.
*/
this.pageX = -1;
/**
* @property {number} pageY - The vertical coordinate of the Pointer relative to whole document.
*/
this.pageY = -1;
/**
* @property {number} screenX - The horizontal coordinate of the Pointer relative to the screen.
*/
this.screenX = -1;
/**
* @property {number} screenY - The vertical coordinate of the Pointer relative to the screen.
*/
this.screenY = -1;
/**
* @property {number} rawMovementX - The horizontal raw relative movement of the Pointer in pixels since last event.
* @default
*/
this.rawMovementX = 0;
/**
* @property {number} rawMovementY - The vertical raw relative movement of the Pointer in pixels since last event.
* @default
*/
this.rawMovementY = 0;
/**
* @property {number} movementX - The horizontal processed relative movement of the Pointer in pixels since last event.
* @default
*/
this.movementX = 0;
/**
* @property {number} movementY - The vertical processed relative movement of the Pointer in pixels since last event.
* @default
*/
this.movementY = 0;
/**
* @property {number} x - The horizontal coordinate of the Pointer. This value is automatically scaled based on the game scale.
* @default
*/
this.x = -1;
/**
* @property {number} y - The vertical coordinate of the Pointer. This value is automatically scaled based on the game scale.
* @default
*/
this.y = -1;
/**
* @property {boolean} isMouse - If the Pointer is a mouse or pen / stylus this is true, otherwise false.
*/
this.isMouse = (id === 0);
/**
* If the Pointer is touching the touchscreen, or *any* mouse or pen button is held down, isDown is set to true.
* If you need to check a specific mouse or pen button then use the button properties, i.e. Pointer.rightButton.isDown.
* @property {boolean} isDown
* @default
*/
this.isDown = false;
/**
* If the Pointer is not touching the touchscreen, or *all* mouse or pen buttons are up, isUp is set to true.
* If you need to check a specific mouse or pen button then use the button properties, i.e. Pointer.rightButton.isUp.
* @property {boolean} isUp
* @default
*/
this.isUp = true;
/**
* @property {number} timeDown - A timestamp representing when the Pointer first touched the touchscreen.
* @default
*/
this.timeDown = 0;
/**
* @property {number} timeUp - A timestamp representing when the Pointer left the touchscreen.
* @default
*/
this.timeUp = 0;
/**
* @property {number} previousTapTime - A timestamp representing when the Pointer was last tapped or clicked.
* @default
*/
this.previousTapTime = 0;
/**
* @property {number} totalTouches - The total number of times this Pointer has been touched to the touchscreen.
* @default
*/
this.totalTouches = 0;
/**
* @property {number} msSinceLastClick - The number of milliseconds since the last click or touch event.
* @default
*/
this.msSinceLastClick = Number.MAX_VALUE;
/**
* @property {any} targetObject - The Game Object this Pointer is currently over / touching / dragging.
* @default
*/
this.targetObject = null;
/**
* This array is erased and re-populated every time this Pointer is updated. It contains references to all
* of the Game Objects that were considered as being valid for processing by this Pointer, this frame. To be
* valid they must have suitable a `priorityID`, be Input enabled, visible and actually have the Pointer over
* them. You can check the contents of this array in events such as `onInputDown`, but beware it is reset
* every frame.
* @property {array} interactiveCandidates
* @default
*/
this.interactiveCandidates = [];
/**
* @property {boolean} active - An active pointer is one that is currently pressed down on the display. A Mouse is always active.
* @default
*/
this.active = false;
/**
* @property {boolean} dirty - A dirty pointer needs to re-poll any interactive objects it may have been over, regardless if it has moved or not.
* @default
*/
this.dirty = false;
/**
* @property {Phaser.Point} position - A Phaser.Point object containing the current x/y values of the pointer on the display.
*/
this.position = new Phaser.Point();
/**
* @property {Phaser.Point} positionDown - A Phaser.Point object containing the x/y values of the pointer when it was last in a down state on the display.
*/
this.positionDown = new Phaser.Point();
/**
* @property {Phaser.Point} positionUp - A Phaser.Point object containing the x/y values of the pointer when it was last released.
*/
this.positionUp = new Phaser.Point();
/**
* A Phaser.Circle that is centered on the x/y coordinates of this pointer, useful for hit detection.
* The Circle size is 44px (Apples recommended "finger tip" size).
* @property {Phaser.Circle} circle
*/
this.circle = new Phaser.Circle(0, 0, 44);
/**
* Click trampolines associated with this pointer. See `addClickTrampoline`.
* @property {object[]|null} _clickTrampolines
* @private
*/
this._clickTrampolines = null;
/**
* When the Pointer has click trampolines the last target object is stored here
* so it can be used to check for validity of the trampoline in a post-Up/'stop'.
* @property {object} _trampolineTargetObject
* @private
*/
this._trampolineTargetObject = null;
};
/**
* No buttons at all.
* @constant
* @type {number}
*/
Phaser.Pointer.NO_BUTTON = 0;
/**
* The Left Mouse button, or in PointerEvent devices a Touch contact or Pen contact.
* @constant
* @type {number}
*/
Phaser.Pointer.LEFT_BUTTON = 1;
/**
* The Right Mouse button, or in PointerEvent devices a Pen contact with a barrel button.
* @constant
* @type {number}
*/
Phaser.Pointer.RIGHT_BUTTON = 2;
/**
* The Middle Mouse button.
* @constant
* @type {number}
*/
Phaser.Pointer.MIDDLE_BUTTON = 4;
/**
* The X1 button. This is typically the mouse Back button, but is often reconfigured.
* On Linux (GTK) this is unsupported. On Windows if advanced pointer software (such as IntelliPoint) is installed this doesn't register.
* @constant
* @type {number}
*/
Phaser.Pointer.BACK_BUTTON = 8;
/**
* The X2 button. This is typically the mouse Forward button, but is often reconfigured.
* On Linux (GTK) this is unsupported. On Windows if advanced pointer software (such as IntelliPoint) is installed this doesn't register.
* @constant
* @type {number}
*/
Phaser.Pointer.FORWARD_BUTTON = 16;
/**
* The Eraser pen button on PointerEvent supported devices only.
* @constant
* @type {number}
*/
Phaser.Pointer.ERASER_BUTTON = 32;
Phaser.Pointer.prototype = {
/**
* Resets the states of all the button booleans.
*
* @method Phaser.Pointer#resetButtons
* @protected
*/
resetButtons: function () {
this.isDown = false;
this.isUp = true;
if (this.isMouse)
{
this.leftButton.reset();
this.middleButton.reset();
this.rightButton.reset();
this.backButton.reset();
this.forwardButton.reset();
this.eraserButton.reset();
}
},
/**
* Called by updateButtons.
*
* @method Phaser.Pointer#processButtonsDown
* @private
* @param {integer} buttons - The DOM event.buttons property.
* @param {MouseEvent} event - The DOM event.
*/
processButtonsDown: function (buttons, event) {
// Note: These are bitwise checks, not booleans
if (Phaser.Pointer.LEFT_BUTTON & buttons)
{
this.leftButton.start(event);
}
if (Phaser.Pointer.RIGHT_BUTTON & buttons)
{
this.rightButton.start(event);
}
if (Phaser.Pointer.MIDDLE_BUTTON & buttons)
{
this.middleButton.start(event);
}
if (Phaser.Pointer.BACK_BUTTON & buttons)
{
this.backButton.start(event);
}
if (Phaser.Pointer.FORWARD_BUTTON & buttons)
{
this.forwardButton.start(event);
}
if (Phaser.Pointer.ERASER_BUTTON & buttons)
{
this.eraserButton.start(event);
}
},
/**
* Called by updateButtons.
*
* @method Phaser.Pointer#processButtonsUp
* @private
* @param {integer} buttons - The DOM event.buttons property.
* @param {MouseEvent} event - The DOM event.
*/
processButtonsUp: function (button, event) {
// Note: These are bitwise checks, not booleans
if (button === Phaser.Mouse.LEFT_BUTTON)
{
this.leftButton.stop(event);
}
if (button === Phaser.Mouse.RIGHT_BUTTON)
{
this.rightButton.stop(event);
}
if (button === Phaser.Mouse.MIDDLE_BUTTON)
{
this.middleButton.stop(event);
}
if (button === Phaser.Mouse.BACK_BUTTON)
{
this.backButton.stop(event);
}
if (button === Phaser.Mouse.FORWARD_BUTTON)
{
this.forwardButton.stop(event);
}
if (button === 5)
{
this.eraserButton.stop(event);
}
},
/**
* Called when the event.buttons property changes from zero.
* Contains a button bitmask.
*
* @method Phaser.Pointer#updateButtons
* @protected
* @param {MouseEvent} event - The DOM event.
*/
updateButtons: function (event) {
this.button = event.button;
var down = (event.type.toLowerCase().substr(-4) === 'down');
if (event.buttons !== undefined)
{
if (down)
{
this.processButtonsDown(event.buttons, event);
}
else
{
this.processButtonsUp(event.button, event);
}
}
else
{
// No buttons property (like Safari on OSX when using a trackpad)
if (down)
{
this.leftButton.start(event);
}
else
{
this.leftButton.stop(event);
this.rightButton.stop(event);
}
}
// On OS X (and other devices with trackpads) you have to press CTRL + the pad
// to initiate a right-click event, so we'll check for that here ONLY if
// event.buttons = 1 (i.e. they only have a 1 button mouse or trackpad)
if (event.buttons === 1 && event.ctrlKey && this.leftButton.isDown)
{
this.leftButton.stop(event);
this.rightButton.start(event);
}
this.isUp = true;
this.isDown = false;
if (this.leftButton.isDown || this.rightButton.isDown || this.middleButton.isDown || this.backButton.isDown || this.forwardButton.isDown || this.eraserButton.isDown)
{
this.isUp = false;
this.isDown = true;
}
},
/**
* Called when the Pointer is pressed onto the touchscreen.
* @method Phaser.Pointer#start
* @param {any} event - The DOM event from the browser.
*/
start: function (event) {
var input = this.game.input;
if (event['pointerId'])
{
this.pointerId = event.pointerId;
}
this.identifier = event.identifier;
this.target = event.target;
if (this.isMouse)
{
this.updateButtons(event);
}
else
{
this.isDown = true;
this.isUp = false;
}
this.active = true;
this.withinGame = true;
this.dirty = false;
this._history = [];
this._clickTrampolines = null;
this._trampolineTargetObject = null;
// Work out how long it has been since the last click
this.msSinceLastClick = this.game.time.time - this.timeDown;
this.timeDown = this.game.time.time;
this._holdSent = false;
// This sets the x/y and other local values
this.move(event, true);
// x and y are the old values here?
this.positionDown.setTo(this.x, this.y);
if (input.multiInputOverride === Phaser.Input.MOUSE_OVERRIDES_TOUCH ||
input.multiInputOverride === Phaser.Input.MOUSE_TOUCH_COMBINE ||
(input.multiInputOverride === Phaser.Input.TOUCH_OVERRIDES_MOUSE && input.totalActivePointers === 0))
{
input.x = this.x;
input.y = this.y;
input.position.setTo(this.x, this.y);
input.onDown.dispatch(this, event);
input.resetSpeed(this.x, this.y);
}
this._stateReset = false;
this.totalTouches++;
if (this.targetObject !== null)
{
this.targetObject._touchedHandler(this);
}
return this;
},
/**
* Called by the Input Manager.
* @method Phaser.Pointer#update
*/
update: function () {
var input = this.game.input;
if (this.active)
{
// Force a check?
if (this.dirty)
{
if (input.interactiveItems.total > 0)
{
this.processInteractiveObjects(false);
}
this.dirty = false;
}
if (this._holdSent === false && this.duration >= input.holdRate)
{
if (input.multiInputOverride === Phaser.Input.MOUSE_OVERRIDES_TOUCH ||
input.multiInputOverride === Phaser.Input.MOUSE_TOUCH_COMBINE ||
(input.multiInputOverride === Phaser.Input.TOUCH_OVERRIDES_MOUSE && input.totalActivePointers === 0))
{
input.onHold.dispatch(this);
}
this._holdSent = true;
}
// Update the droppings history
if (input.recordPointerHistory && this.game.time.time >= this._nextDrop)
{
this._nextDrop = this.game.time.time + input.recordRate;
this._history.push({
x: this.position.x,
y: this.position.y
});
if (this._history.length > input.recordLimit)
{
this._history.shift();
}
}
}
},
/**
* Called when the Pointer is moved.
*
* @method Phaser.Pointer#move
* @param {MouseEvent|PointerEvent|TouchEvent} event - The event passed up from the input handler.
* @param {boolean} [fromClick=false] - Was this called from the click event?
*/
move: function (event, fromClick) {
var input = this.game.input;
if (input.pollLocked)
{
return;
}
if (fromClick === undefined) { fromClick = false; }
if (event.button !== undefined)
{
this.button = event.button;
}
if (fromClick && this.isMouse)
{
this.updateButtons(event);
}
this.clientX = event.clientX;
this.clientY = event.clientY;
this.pageX = event.pageX;
this.pageY = event.pageY;
this.screenX = event.screenX;
this.screenY = event.screenY;
if (this.isMouse && input.mouse.locked && !fromClick)
{
this.rawMovementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
this.rawMovementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
this.movementX += this.rawMovementX;
this.movementY += this.rawMovementY;
}
this.x = (this.pageX - this.game.scale.offset.x) * input.scale.x;
this.y = (this.pageY - this.game.scale.offset.y) * input.scale.y;
this.position.setTo(this.x, this.y);
this.circle.x = this.x;
this.circle.y = this.y;
if (input.multiInputOverride === Phaser.Input.MOUSE_OVERRIDES_TOUCH ||
input.multiInputOverride === Phaser.Input.MOUSE_TOUCH_COMBINE ||
(input.multiInputOverride === Phaser.Input.TOUCH_OVERRIDES_MOUSE && input.totalActivePointers === 0))
{
input.activePointer = this;
input.x = this.x;
input.y = this.y;
input.position.setTo(input.x, input.y);
input.circle.x = input.x;
input.circle.y = input.y;
}
this.withinGame = this.game.scale.bounds.contains(this.pageX, this.pageY);
// If the game is paused we don't process any target objects or callbacks
if (this.game.paused)
{
return this;
}
var i = input.moveCallbacks.length;
while (i--)
{
input.moveCallbacks[i].callback.call(input.moveCallbacks[i].context, this, this.x, this.y, fromClick);
}
// Easy out if we're dragging something and it still exists
if (this.targetObject !== null && this.targetObject.isDragged === true)
{
if (this.targetObject.update(this) === false)
{
this.targetObject = null;
}
}
else if (input.interactiveItems.total > 0)
{
this.processInteractiveObjects(fromClick);
}
return this;
},
/**
* Process all interactive objects to find out which ones were updated in the recent Pointer move.
*
* @method Phaser.Pointer#processInteractiveObjects
* @protected
* @param {boolean} [fromClick=false] - Was this called from the click event?
* @return {boolean} True if this method processes an object (i.e. a Sprite becomes the Pointers currentTarget), otherwise false.
*/
processInteractiveObjects: function (fromClick) {
// Work out which object is on the top
var highestRenderOrderID = 0;
var highestInputPriorityID = -1;
var candidateTarget = null;
// First pass gets all objects that the pointer is over that DON'T use pixelPerfect checks and get the highest ID
// We know they'll be valid for input detection but not which is the top just yet
var currentNode = this.game.input.interactiveItems.first;
this.interactiveCandidates = [];
while (currentNode)
{
// Reset checked status
currentNode.checked = false;
if (currentNode.validForInput(highestInputPriorityID, highestRenderOrderID, false))
{
// Flag it as checked so we don't re-scan it on the next phase
currentNode.checked = true;
if ((fromClick && currentNode.checkPointerDown(this, true)) ||
(!fromClick && currentNode.checkPointerOver(this, true)))
{
highestRenderOrderID = currentNode.sprite.renderOrderID;
highestInputPriorityID = currentNode.priorityID;
candidateTarget = currentNode;
this.interactiveCandidates.push(currentNode);
}
}
currentNode = this.game.input.interactiveItems.next;
}
// Then in the second sweep we process ONLY the pixel perfect ones that are checked and who have a higher ID
// because if their ID is lower anyway then we can just automatically discount them
// (A node that was previously checked did not request a pixel-perfect check.)
currentNode = this.game.input.interactiveItems.first;
while (currentNode)
{
if (!currentNode.checked &&
currentNode.validForInput(highestInputPriorityID, highestRenderOrderID, true))
{
if ((fromClick && currentNode.checkPointerDown(this, false)) ||
(!fromClick && currentNode.checkPointerOver(this, false)))
{
highestRenderOrderID = currentNode.sprite.renderOrderID;
highestInputPriorityID = currentNode.priorityID;
candidateTarget = currentNode;
this.interactiveCandidates.push(currentNode);
}
}
currentNode = this.game.input.interactiveItems.next;
}
if (this.game.input.customCandidateHandler)
{
candidateTarget = this.game.input.customCandidateHandler.call(this.game.input.customCandidateHandlerContext, this, this.interactiveCandidates, candidateTarget);
}
this.swapTarget(candidateTarget, false);
return (this.targetObject !== null);
},
/**
* This will change the `Pointer.targetObject` object to be the one provided.
*
* This allows you to have fine-grained control over which object the Pointer is targeting.
*
* Note that even if you set a new Target here, it is still able to be replaced by any other valid
* target during the next Pointer update.
*
* @method Phaser.Pointer#swapTarget
* @param {Phaser.InputHandler} newTarget - The new target for this Pointer. Note this is an `InputHandler`, so don't pass a Sprite, instead pass `sprite.input` to it.
* @param {boolean} [silent=false] - If true the new target AND the old one will NOT dispatch their `onInputOver` or `onInputOut` events.
*/
swapTarget: function (newTarget, silent) {
if (silent === undefined) { silent = false; }
// Now we know the top-most item (if any) we can process it
if (newTarget === null)
{
// The pointer isn't currently over anything, check if we've got a lingering previous target
if (this.targetObject)
{
this.targetObject._pointerOutHandler(this, silent);
this.targetObject = null;
}
}
else
{
if (this.targetObject === null)
{
// And now set the new one
this.targetObject = newTarget;
newTarget._pointerOverHandler(this, silent);
}
else
{
// We've got a target from the last update
if (this.targetObject === newTarget)
{
// Same target as before, so update it
if (newTarget.update(this) === false)
{
this.targetObject = null;
}
}
else
{
// The target has changed, so tell the old one we've left it
this.targetObject._pointerOutHandler(this, silent);
// And now set the new one
this.targetObject = newTarget;
this.targetObject._pointerOverHandler(this, silent);
}
}
}
},
/**
* Called when the Pointer leaves the target area.
*
* @method Phaser.Pointer#leave
* @param {MouseEvent|PointerEvent|TouchEvent} event - The event passed up from the input handler.
*/
leave: function (event) {
this.withinGame = false;
this.move(event, false);
},
/**
* Called when the Pointer leaves the touchscreen.
*
* @method Phaser.Pointer#stop
* @param {MouseEvent|PointerEvent|TouchEvent} event - The event passed up from the input handler.
*/
stop: function (event) {
var input = this.game.input;
if (this._stateReset && this.withinGame)
{
event.preventDefault();
return;
}
this.timeUp = this.game.time.time;
if (input.multiInputOverride === Phaser.Input.MOUSE_OVERRIDES_TOUCH ||
input.multiInputOverride === Phaser.Input.MOUSE_TOUCH_COMBINE ||
(input.multiInputOverride === Phaser.Input.TOUCH_OVERRIDES_MOUSE && input.totalActivePointers === 0))
{
input.onUp.dispatch(this, event);
// Was it a tap?
if (this.duration >= 0 && this.duration <= input.tapRate)
{
// Was it a double-tap?
if (this.timeUp - this.previousTapTime < input.doubleTapRate)
{
// Yes, let's dispatch the signal then with the 2nd parameter set to true
input.onTap.dispatch(this, true);
}
else
{
// Wasn't a double-tap, so dispatch a single tap signal
input.onTap.dispatch(this, false);
}
this.previousTapTime = this.timeUp;
}
}
if (this.isMouse)
{
this.updateButtons(event);
}
else
{
this.isDown = false;
this.isUp = true;
}
// Mouse is always active
if (this.id > 0)
{
this.active = false;
}
this.withinGame = this.game.scale.bounds.contains(event.pageX, event.pageY);
this.pointerId = null;
this.identifier = null;
this.positionUp.setTo(this.x, this.y);
if (this.isMouse === false)
{
input.currentPointers--;
}
input.interactiveItems.callAll('_releasedHandler', this);
if (this._clickTrampolines)
{
this._trampolineTargetObject = this.targetObject;
}
this.targetObject = null;
return this;
},
/**
* The Pointer is considered justPressed if the time it was pressed onto the touchscreen or clicked is less than justPressedRate.
* Note that calling justPressed doesn't reset the pressed status of the Pointer, it will return `true` for as long as the duration is valid.
* If you wish to check if the Pointer was pressed down just once then see the Sprite.events.onInputDown event.
* @method Phaser.Pointer#justPressed
* @param {number} [duration] - The time to check against. If none given it will use InputManager.justPressedRate.
* @return {boolean} true if the Pointer was pressed down within the duration given.
*/
justPressed: function (duration) {
duration = duration || this.game.input.justPressedRate;
return (this.isDown === true && (this.timeDown + duration) > this.game.time.time);
},
/**
* The Pointer is considered justReleased if the time it left the touchscreen is less than justReleasedRate.
* Note that calling justReleased doesn't reset the pressed status of the Pointer, it will return `true` for as long as the duration is valid.
* If you wish to check if the Pointer was released just once then see the Sprite.events.onInputUp event.
* @method Phaser.Pointer#justReleased
* @param {number} [duration] - The time to check against. If none given it will use InputManager.justReleasedRate.
* @return {boolean} true if the Pointer was released within the duration given.
*/
justReleased: function (duration) {
duration = duration || this.game.input.justReleasedRate;
return (this.isUp && (this.timeUp + duration) > this.game.time.time);
},
/**
* Add a click trampoline to this pointer.
*
* A click trampoline is a callback that is run on the DOM 'click' event; this is primarily
* needed with certain browsers (ie. IE11) which restrict some actions like requestFullscreen
* to the DOM 'click' event and rejects it for 'pointer*' and 'mouse*' events.
*
* This is used internally by the ScaleManager; click trampoline usage is uncommon.
* Click trampolines can only be added to pointers that are currently down.
*
* @method Phaser.Pointer#addClickTrampoline
* @protected
* @param {string} name - The name of the trampoline; must be unique among active trampolines in this pointer.
* @param {function} callback - Callback to run/trampoline.
* @param {object} callbackContext - Context of the callback.
* @param {object[]|null} callbackArgs - Additional callback args, if any. Supplied as an array.
*/
addClickTrampoline: function (name, callback, callbackContext, callbackArgs) {
if (!this.isDown)
{
return;
}
var trampolines = (this._clickTrampolines = this._clickTrampolines || []);
for (var i = 0; i < trampolines.length; i++)
{
if (trampolines[i].name === name)
{
trampolines.splice(i, 1);
break;
}
}
trampolines.push({
name: name,
targetObject: this.targetObject,
callback: callback,
callbackContext: callbackContext,
callbackArgs: callbackArgs
});
},
/**
* Fire all click trampolines for which the pointers are still referring to the registered object.
* @method Phaser.Pointer#processClickTrampolines
* @private
*/
processClickTrampolines: function () {
var trampolines = this._clickTrampolines;
if (!trampolines)
{
return;
}
for (var i = 0; i < trampolines.length; i++)
{
var trampoline = trampolines[i];
if (trampoline.targetObject === this._trampolineTargetObject)
{
trampoline.callback.apply(trampoline.callbackContext, trampoline.callbackArgs);
}
}
this._clickTrampolines = null;
this._trampolineTargetObject = null;
},
/**
* Resets the Pointer properties. Called by InputManager.reset when you perform a State change.
* @method Phaser.Pointer#reset
*/
reset: function () {
if (this.isMouse === false)
{
this.active = false;
}
this.pointerId = null;
this.identifier = null;
this.dirty = false;
this.totalTouches = 0;
this._holdSent = false;
this._history.length = 0;
this._stateReset = true;
this.resetButtons();
if (this.targetObject)
{
this.targetObject._releasedHandler(this);
}
this.targetObject = null;
},
/**
* Resets the movementX and movementY properties. Use in your update handler after retrieving the values.
* @method Phaser.Pointer#resetMovement
*/
resetMovement: function() {
this.movementX = 0;
this.movementY = 0;
}
};
Phaser.Pointer.prototype.constructor = Phaser.Pointer;
/**
* How long the Pointer has been depressed on the touchscreen or *any* of the mouse buttons have been held down.
* If not currently down it returns -1.
* If you need to test a specific mouse or pen button then access the buttons directly, i.e. `Pointer.rightButton.duration`.
*
* @name Phaser.Pointer#duration
* @property {number} duration
* @readonly
*/
Object.defineProperty(Phaser.Pointer.prototype, "duration", {
get: function () {
if (this.isUp)
{
return -1;
}
return this.game.time.time - this.timeDown;
}
});
/**
* Gets the X value of this Pointer in world coordinates based on the world camera.
* @name Phaser.Pointer#worldX
* @property {number} worldX - The X value of this Pointer in world coordinates based on the world camera.
* @readonly
*/
Object.defineProperty(Phaser.Pointer.prototype, "worldX", {
get: function () {
return this.game.world.camera.x + this.x;
}
});
/**
* Gets the Y value of this Pointer in world coordinates based on the world camera.
* @name Phaser.Pointer#worldY
* @property {number} worldY - The Y value of this Pointer in world coordinates based on the world camera.
* @readonly
*/
Object.defineProperty(Phaser.Pointer.prototype, "worldY", {
get: function () {
return this.game.world.camera.y + this.y;
}
});
/**
* Enumeration categorizing operational modes of pointers.
*
* PointerType values represent valid bitmasks.
* For example, a value representing both Mouse and Touch devices
* can be expressed as `PointerMode.CURSOR | PointerMode.CONTACT`.
*
* Values may be added for future mode categorizations.
* @class Phaser.PointerMode
*/
Phaser.PointerMode = {
/**
* A 'CURSOR' is a pointer with a *passive cursor* such as a mouse, touchpad, watcom stylus, or even TV-control arrow-pad.
*
* It has the property that a cursor is passively moved without activating the input.
* This currently corresponds with {@link Phaser.Pointer#isMouse} property.
* @constant
*/
CURSOR: 1 << 0,
/**
* A 'CONTACT' pointer has an *active cursor* that only tracks movement when actived; notably this is a touch-style input.
* @constant
*/
CONTACT: 1 << 1
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Phaser.Touch handles touch events with your game. Note: Android 2.x only supports 1 touch event at once, no multi-touch.
*
* You should not normally access this class directly, but instead use a Phaser.Pointer object which normalises all game input for you.
*
* @class Phaser.Touch
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.Touch = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = game;
/**
* Touch events will only be processed if enabled.
* @property {boolean} enabled
* @default
*/
this.enabled = true;
/**
* An array of callbacks that will be fired every time a native touch start or touch end event is received from the browser.
* This is used internally to handle audio and video unlocking on mobile devices.
* To add a callback to this array please use `Touch.addTouchLockCallback`.
* @property {array} touchLockCallbacks
* @protected
*/
this.touchLockCallbacks = [];
/**
* @property {object} callbackContext - The context under which callbacks are called.
*/
this.callbackContext = this.game;
/**
* @property {function} touchStartCallback - A callback that can be fired on a touchStart event.
*/
this.touchStartCallback = null;
/**
* @property {function} touchMoveCallback - A callback that can be fired on a touchMove event.
*/
this.touchMoveCallback = null;
/**
* @property {function} touchEndCallback - A callback that can be fired on a touchEnd event.
*/
this.touchEndCallback = null;
/**
* @property {function} touchEnterCallback - A callback that can be fired on a touchEnter event.
*/
this.touchEnterCallback = null;
/**
* @property {function} touchLeaveCallback - A callback that can be fired on a touchLeave event.
*/
this.touchLeaveCallback = null;
/**
* @property {function} touchCancelCallback - A callback that can be fired on a touchCancel event.
*/
this.touchCancelCallback = null;
/**
* @property {boolean} preventDefault - If true the TouchEvent will have prevent.default called on it.
* @default
*/
this.preventDefault = true;
/**
* @property {TouchEvent} event - The browser touch DOM event. Will be set to null if no touch event has ever been received.
* @default
*/
this.event = null;
/**
* @property {function} _onTouchStart - Internal event handler reference.
* @private
*/
this._onTouchStart = null;
/**
* @property {function} _onTouchMove - Internal event handler reference.
* @private
*/
this._onTouchMove = null;
/**
* @property {function} _onTouchEnd - Internal event handler reference.
* @private
*/
this._onTouchEnd = null;
/**
* @property {function} _onTouchEnter - Internal event handler reference.
* @private
*/
this._onTouchEnter = null;
/**
* @property {function} _onTouchLeave - Internal event handler reference.
* @private
*/
this._onTouchLeave = null;
/**
* @property {function} _onTouchCancel - Internal event handler reference.
* @private
*/
this._onTouchCancel = null;
/**
* @property {function} _onTouchMove - Internal event handler reference.
* @private
*/
this._onTouchMove = null;
};
Phaser.Touch.prototype = {
/**
* Starts the event listeners running.
* @method Phaser.Touch#start
*/
start: function () {
if (this._onTouchStart !== null)
{
// Avoid setting multiple listeners
return;
}
var _this = this;
if (this.game.device.touch)
{
this._onTouchStart = function (event) {
return _this.onTouchStart(event);
};
this._onTouchMove = function (event) {
return _this.onTouchMove(event);
};
this._onTouchEnd = function (event) {
return _this.onTouchEnd(event);
};
this._onTouchEnter = function (event) {
return _this.onTouchEnter(event);
};
this._onTouchLeave = function (event) {
return _this.onTouchLeave(event);
};
this._onTouchCancel = function (event) {
return _this.onTouchCancel(event);
};
this.game.canvas.addEventListener('touchstart', this._onTouchStart, false);
this.game.canvas.addEventListener('touchmove', this._onTouchMove, false);
this.game.canvas.addEventListener('touchend', this._onTouchEnd, false);
this.game.canvas.addEventListener('touchcancel', this._onTouchCancel, false);
if (!this.game.device.cocoonJS)
{
this.game.canvas.addEventListener('touchenter', this._onTouchEnter, false);
this.game.canvas.addEventListener('touchleave', this._onTouchLeave, false);
}
}
},
/**
* Consumes all touchmove events on the document (only enable this if you know you need it!).
* @method Phaser.Touch#consumeTouchMove
*/
consumeDocumentTouches: function () {
this._documentTouchMove = function (event) {
event.preventDefault();
};
document.addEventListener('touchmove', this._documentTouchMove, false);
},
/**
* Adds a callback that is fired when a browser touchstart or touchend event is received.
*
* This is used internally to handle audio and video unlocking on mobile devices.
*
* If the callback returns 'true' then the callback is automatically deleted once invoked.
*
* The callback is added to the Phaser.Touch.touchLockCallbacks array and should be removed with Phaser.Touch.removeTouchLockCallback.
*
* @method Phaser.Touch#addTouchLockCallback
* @param {function} callback - The callback that will be called when a touchstart event is received.
* @param {object} context - The context in which the callback will be called.
* @param {boolean} [onEnd=false] - Will the callback fire on a touchstart (default) or touchend event?
*/
addTouchLockCallback: function (callback, context, onEnd) {
if (onEnd === undefined) { onEnd = false; }
this.touchLockCallbacks.push({ callback: callback, context: context, onEnd: onEnd });
},
/**
* Removes the callback at the defined index from the Phaser.Touch.touchLockCallbacks array
*
* @method Phaser.Touch#removeTouchLockCallback
* @param {function} callback - The callback to be removed.
* @param {object} context - The context in which the callback exists.
* @return {boolean} True if the callback was deleted, otherwise false.
*/
removeTouchLockCallback: function (callback, context) {
var i = this.touchLockCallbacks.length;
while (i--)
{
if (this.touchLockCallbacks[i].callback === callback && this.touchLockCallbacks[i].context === context)
{
this.touchLockCallbacks.splice(i, 1);
return true;
}
}
return false;
},
/**
* The internal method that handles the touchstart event from the browser.
* @method Phaser.Touch#onTouchStart
* @param {TouchEvent} event - The native event from the browser. This gets stored in Touch.event.
*/
onTouchStart: function (event) {
var i = this.touchLockCallbacks.length;
while (i--)
{
var cb = this.touchLockCallbacks[i];
if (!cb.onEnd && cb.callback.call(cb.context, this, event))
{
this.touchLockCallbacks.splice(i, 1);
}
}
this.event = event;
if (!this.game.input.enabled || !this.enabled)
{
return;
}
if (this.touchStartCallback)
{
this.touchStartCallback.call(this.callbackContext, event);
}
if (this.preventDefault)
{
event.preventDefault();
}
// event.targetTouches = list of all touches on the TARGET ELEMENT (i.e. game dom element)
// event.touches = list of all touches on the ENTIRE DOCUMENT, not just the target element
// event.changedTouches = the touches that CHANGED in this event, not the total number of them
for (var i = 0; i < event.changedTouches.length; i++)
{
this.game.input.startPointer(event.changedTouches[i]);
}
},
/**
* Touch cancel - touches that were disrupted (perhaps by moving into a plugin or browser chrome).
* Occurs for example on iOS when you put down 4 fingers and the app selector UI appears.
* @method Phaser.Touch#onTouchCancel
* @param {TouchEvent} event - The native event from the browser. This gets stored in Touch.event.
*/
onTouchCancel: function (event) {
this.event = event;
if (this.touchCancelCallback)
{
this.touchCancelCallback.call(this.callbackContext, event);
}
if (!this.game.input.enabled || !this.enabled)
{
return;
}
if (this.preventDefault)
{
event.preventDefault();
}
// Touch cancel - touches that were disrupted (perhaps by moving into a plugin or browser chrome)
// http://www.w3.org/TR/touch-events/#dfn-touchcancel
for (var i = 0; i < event.changedTouches.length; i++)
{
this.game.input.stopPointer(event.changedTouches[i]);
}
},
/**
* For touch enter and leave its a list of the touch points that have entered or left the target.
* Doesn't appear to be supported by most browsers on a canvas element yet.
* @method Phaser.Touch#onTouchEnter
* @param {TouchEvent} event - The native event from the browser. This gets stored in Touch.event.
*/
onTouchEnter: function (event) {
this.event = event;
if (this.touchEnterCallback)
{
this.touchEnterCallback.call(this.callbackContext, event);
}
if (!this.game.input.enabled || !this.enabled)
{
return;
}
if (this.preventDefault)
{
event.preventDefault();
}
},
/**
* For touch enter and leave its a list of the touch points that have entered or left the target.
* Doesn't appear to be supported by most browsers on a canvas element yet.
* @method Phaser.Touch#onTouchLeave
* @param {TouchEvent} event - The native event from the browser. This gets stored in Touch.event.
*/
onTouchLeave: function (event) {
this.event = event;
if (this.touchLeaveCallback)
{
this.touchLeaveCallback.call(this.callbackContext, event);
}
if (this.preventDefault)
{
event.preventDefault();
}
},
/**
* The handler for the touchmove events.
* @method Phaser.Touch#onTouchMove
* @param {TouchEvent} event - The native event from the browser. This gets stored in Touch.event.
*/
onTouchMove: function (event) {
this.event = event;
if (this.touchMoveCallback)
{
this.touchMoveCallback.call(this.callbackContext, event);
}
if (this.preventDefault)
{
event.preventDefault();
}
for (var i = 0; i < event.changedTouches.length; i++)
{
this.game.input.updatePointer(event.changedTouches[i]);
}
},
/**
* The handler for the touchend events.
* @method Phaser.Touch#onTouchEnd
* @param {TouchEvent} event - The native event from the browser. This gets stored in Touch.event.
*/
onTouchEnd: function (event) {
var i = this.touchLockCallbacks.length;
while (i--)
{
var cb = this.touchLockCallbacks[i];
if (cb.onEnd && cb.callback.call(cb.context, this, event))
{
this.touchLockCallbacks.splice(i, 1);
}
}
this.event = event;
if (this.touchEndCallback)
{
this.touchEndCallback.call(this.callbackContext, event);
}
if (this.preventDefault)
{
event.preventDefault();
}
// For touch end its a list of the touch points that have been removed from the surface
// https://developer.mozilla.org/en-US/docs/DOM/TouchList
// event.changedTouches = the touches that CHANGED in this event, not the total number of them
for (var i = 0; i < event.changedTouches.length; i++)
{
this.game.input.stopPointer(event.changedTouches[i]);
}
},
/**
* Stop the event listeners.
* @method Phaser.Touch#stop
*/
stop: function () {
if (this.game.device.touch)
{
this.game.canvas.removeEventListener('touchstart', this._onTouchStart);
this.game.canvas.removeEventListener('touchmove', this._onTouchMove);
this.game.canvas.removeEventListener('touchend', this._onTouchEnd);
this.game.canvas.removeEventListener('touchenter', this._onTouchEnter);
this.game.canvas.removeEventListener('touchleave', this._onTouchLeave);
this.game.canvas.removeEventListener('touchcancel', this._onTouchCancel);
}
}
};
Phaser.Touch.prototype.constructor = Phaser.Touch;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Input Handler is bound to a specific Sprite and is responsible for managing all Input events on that Sprite.
*
* @class Phaser.InputHandler
* @constructor
* @param {Phaser.Sprite} sprite - The Sprite object to which this Input Handler belongs.
*/
Phaser.InputHandler = function (sprite) {
/**
* @property {Phaser.Sprite} sprite - The Sprite object to which this Input Handler belongs.
*/
this.sprite = sprite;
/**
* @property {Phaser.Game} game - A reference to the currently running game.
*/
this.game = sprite.game;
/**
* @property {boolean} enabled - If enabled the Input Handler will process input requests and monitor pointer activity.
* @default
*/
this.enabled = false;
/**
* @property {boolean} checked - A disposable flag used by the Pointer class when performing priority checks.
* @protected
*/
this.checked = false;
/**
* The priorityID is used to determine which game objects should get priority when input events occur. For example if you have
* several Sprites that overlap, by default the one at the top of the display list is given priority for input events. You can
* stop this from happening by controlling the priorityID value. The higher the value, the more important they are considered to the Input events.
* @property {number} priorityID
* @default
*/
this.priorityID = 0;
/**
* @property {boolean} useHandCursor - On a desktop browser you can set the 'hand' cursor to appear when moving over the Sprite.
* @default
*/
this.useHandCursor = false;
/**
* @property {boolean} _setHandCursor - Did this Sprite trigger the hand cursor?
* @private
*/
this._setHandCursor = false;
/**
* @property {boolean} isDragged - true if the Sprite is being currently dragged.
* @default
*/
this.isDragged = false;
/**
* @property {boolean} allowHorizontalDrag - Controls if the Sprite is allowed to be dragged horizontally.
* @default
*/
this.allowHorizontalDrag = true;
/**
* @property {boolean} allowVerticalDrag - Controls if the Sprite is allowed to be dragged vertically.
* @default
*/
this.allowVerticalDrag = true;
/**
* @property {boolean} bringToTop - If true when this Sprite is clicked or dragged it will automatically be bought to the top of the Group it is within.
* @default
*/
this.bringToTop = false;
/**
* @property {Phaser.Point} snapOffset - A Point object that contains by how far the Sprite snap is offset.
* @default
*/
this.snapOffset = null;
/**
* @property {boolean} snapOnDrag - When the Sprite is dragged this controls if the center of the Sprite will snap to the pointer on drag or not.
* @default
*/
this.snapOnDrag = false;
/**
* @property {boolean} snapOnRelease - When the Sprite is dragged this controls if the Sprite will be snapped on release.
* @default
*/
this.snapOnRelease = false;
/**
* @property {number} snapX - When a Sprite has snapping enabled this holds the width of the snap grid.
* @default
*/
this.snapX = 0;
/**
* @property {number} snapY - When a Sprite has snapping enabled this holds the height of the snap grid.
* @default
*/
this.snapY = 0;
/**
* @property {number} snapOffsetX - This defines the top-left X coordinate of the snap grid.
* @default
*/
this.snapOffsetX = 0;
/**
* @property {number} snapOffsetY - This defines the top-left Y coordinate of the snap grid..
* @default
*/
this.snapOffsetY = 0;
/**
* Set to true to use pixel perfect hit detection when checking if the pointer is over this Sprite.
* The x/y coordinates of the pointer are tested against the image in combination with the InputHandler.pixelPerfectAlpha value.
* This feature only works for display objects with image based textures such as Sprites. It won't work on BitmapText or Rope.
* Warning: This is expensive, especially on mobile (where it's not even needed!) so only enable if required. Also see the less-expensive InputHandler.pixelPerfectClick.
* @property {boolean} pixelPerfectOver - Use a pixel perfect check when testing for pointer over.
* @default
*/
this.pixelPerfectOver = false;
/**
* Set to true to use pixel perfect hit detection when checking if the pointer is over this Sprite when it's clicked or touched.
* The x/y coordinates of the pointer are tested against the image in combination with the InputHandler.pixelPerfectAlpha value.
* This feature only works for display objects with image based textures such as Sprites. It won't work on BitmapText or Rope.
* Warning: This is expensive so only enable if you really need it.
* @property {boolean} pixelPerfectClick - Use a pixel perfect check when testing for clicks or touches on the Sprite.
* @default
*/
this.pixelPerfectClick = false;
/**
* @property {number} pixelPerfectAlpha - The alpha tolerance threshold. If the alpha value of the pixel matches or is above this value, it's considered a hit.
* @default
*/
this.pixelPerfectAlpha = 255;
/**
* @property {boolean} draggable - Is this sprite allowed to be dragged by the mouse? true = yes, false = no
* @default
*/
this.draggable = false;
/**
* @property {Phaser.Rectangle} boundsRect - A region of the game world within which the sprite is restricted during drag.
* @default
*/
this.boundsRect = null;
/**
* @property {Phaser.Sprite} boundsSprite - A Sprite the bounds of which this sprite is restricted during drag.
* @default
*/
this.boundsSprite = null;
/**
* @property {boolean} scaleLayer - EXPERIMENTAL: Please do not use this property unless you know what it does. Likely to change in the future.
*/
this.scaleLayer = false;
/**
* @property {Phaser.Point} dragOffset - The offset from the Sprites position that dragging takes place from.
*/
this.dragOffset = new Phaser.Point();
/**
* @property {boolean} dragFromCenter - Is the Sprite dragged from its center, or the point at which the Pointer was pressed down upon it?
*/
this.dragFromCenter = false;
/**
* @property {boolean} dragStopBlocksInputUp - If enabled, when the Sprite stops being dragged, it will only dispatch the `onDragStop` event, and not the `onInputUp` event. If set to `false` it will dispatch both events.
*/
this.dragStopBlocksInputUp = false;
/**
* @property {Phaser.Point} dragStartPoint - The Point from which the most recent drag started from. Useful if you need to return an object to its starting position.
*/
this.dragStartPoint = new Phaser.Point();
/**
* @property {integer} dragDistanceThreshold - The distance, in pixels, the pointer has to move while being held down, before the Sprite thinks it is being dragged.
*/
this.dragDistanceThreshold = 0;
/**
* @property {integer} dragTimeThreshold - The amount of time, in ms, the pointer has to be held down over the Sprite before it thinks it is being dragged.
*/
this.dragTimeThreshold = 0;
/**
* @property {Phaser.Point} downPoint - A Point object containing the coordinates of the Pointer when it was first pressed down onto this Sprite.
*/
this.downPoint = new Phaser.Point();
/**
* @property {Phaser.Point} snapPoint - If the sprite is set to snap while dragging this holds the point of the most recent 'snap' event.
*/
this.snapPoint = new Phaser.Point();
/**
* @property {Phaser.Point} _dragPoint - Internal cache var.
* @private
*/
this._dragPoint = new Phaser.Point();
/**
* @property {boolean} _dragPhase - Internal cache var.
* @private
*/
this._dragPhase = false;
/**
* @property {boolean} _pendingDrag - Internal cache var.
* @private
*/
this._pendingDrag = false;
/**
* @property {boolean} _dragTimePass - Internal cache var.
* @private
*/
this._dragTimePass = false;
/**
* @property {boolean} _dragDistancePass - Internal cache var.
* @private
*/
this._dragDistancePass = false;
/**
* @property {boolean} _wasEnabled - Internal cache var.
* @private
*/
this._wasEnabled = false;
/**
* @property {Phaser.Point} _tempPoint - Internal cache var.
* @private
*/
this._tempPoint = new Phaser.Point();
/**
* @property {array} _pointerData - Internal cache var.
* @private
*/
this._pointerData = [];
this._pointerData.push({
id: 0,
x: 0,
y: 0,
camX: 0,
camY: 0,
isDown: false,
isUp: false,
isOver: false,
isOut: false,
timeOver: 0,
timeOut: 0,
timeDown: 0,
timeUp: 0,
downDuration: 0,
isDragged: false
});
};
Phaser.InputHandler.prototype = {
/**
* Starts the Input Handler running. This is called automatically when you enable input on a Sprite, or can be called directly if you need to set a specific priority.
*
* @method Phaser.InputHandler#start
* @param {number} [priority=0] - Higher priority sprites take click priority over low-priority sprites when they are stacked on-top of each other.
* @param {boolean} [useHandCursor=false] - If true the Sprite will show the hand cursor on mouse-over (doesn't apply to mobile browsers)
* @return {Phaser.Sprite} The Sprite object to which the Input Handler is bound.
*/
start: function (priority, useHandCursor) {
priority = priority || 0;
if (useHandCursor === undefined) { useHandCursor = false; }
// Turning on
if (this.enabled === false)
{
// Register, etc
this.game.input.interactiveItems.add(this);
this.useHandCursor = useHandCursor;
this.priorityID = priority;
for (var i = 0; i < 10; i++)
{
this._pointerData[i] = {
id: i,
x: 0,
y: 0,
isDown: false,
isUp: false,
isOver: false,
isOut: false,
timeOver: 0,
timeOut: 0,
timeDown: 0,
timeUp: 0,
downDuration: 0,
isDragged: false
};
}
this.snapOffset = new Phaser.Point();
this.enabled = true;
this._wasEnabled = true;
}
this.sprite.events.onAddedToGroup.add(this.addedToGroup, this);
this.sprite.events.onRemovedFromGroup.add(this.removedFromGroup, this);
return this.sprite;
},
/**
* Handles when the parent Sprite is added to a new Group.
*
* @method Phaser.InputHandler#addedToGroup
* @private
*/
addedToGroup: function () {
if (this._dragPhase)
{
return;
}
if (this._wasEnabled && !this.enabled)
{
this.start();
}
},
/**
* Handles when the parent Sprite is removed from a Group.
*
* @method Phaser.InputHandler#removedFromGroup
* @private
*/
removedFromGroup: function () {
if (this._dragPhase)
{
return;
}
if (this.enabled)
{
this._wasEnabled = true;
this.stop();
}
else
{
this._wasEnabled = false;
}
},
/**
* Resets the Input Handler and disables it.
* @method Phaser.InputHandler#reset
*/
reset: function () {
this.enabled = false;
for (var i = 0; i < 10; i++)
{
this._pointerData[i] = {
id: i,
x: 0,
y: 0,
isDown: false,
isUp: false,
isOver: false,
isOut: false,
timeOver: 0,
timeOut: 0,
timeDown: 0,
timeUp: 0,
downDuration: 0,
isDragged: false
};
}
},
/**
* Stops the Input Handler from running.
* @method Phaser.InputHandler#stop
*/
stop: function () {
// Turning off
if (this.enabled === false)
{
return;
}
else
{
// De-register, etc
this.enabled = false;
this.game.input.interactiveItems.remove(this);
}
},
/**
* Clean up memory.
* @method Phaser.InputHandler#destroy
*/
destroy: function () {
if (this.sprite)
{
if (this._setHandCursor)
{
this.game.canvas.style.cursor = "default";
this._setHandCursor = false;
}
this.enabled = false;
this.game.input.interactiveItems.remove(this);
this._pointerData.length = 0;
this.boundsRect = null;
this.boundsSprite = null;
this.sprite = null;
}
},
/**
* Checks if the object this InputHandler is bound to is valid for consideration in the Pointer move event.
* This is called by Phaser.Pointer and shouldn't typically be called directly.
*
* @method Phaser.InputHandler#validForInput
* @protected
* @param {number} highestID - The highest ID currently processed by the Pointer.
* @param {number} highestRenderID - The highest Render Order ID currently processed by the Pointer.
* @param {boolean} [includePixelPerfect=true] - If this object has `pixelPerfectClick` or `pixelPerfectOver` set should it be considered as valid?
* @return {boolean} True if the object this InputHandler is bound to should be considered as valid for input detection.
*/
validForInput: function (highestID, highestRenderID, includePixelPerfect) {
if (includePixelPerfect === undefined) { includePixelPerfect = true; }
if (!this.enabled ||
this.sprite.scale.x === 0 ||
this.sprite.scale.y === 0 ||
this.priorityID < this.game.input.minPriorityID ||
(this.sprite.parent && this.sprite.parent.ignoreChildInput))
{
return false;
}
// If we're trying to specifically IGNORE pixel perfect objects, then set includePixelPerfect to false and skip it
if (!includePixelPerfect && (this.pixelPerfectClick || this.pixelPerfectOver))
{
return false;
}
if (this.priorityID > highestID || (this.priorityID === highestID && this.sprite.renderOrderID > highestRenderID))
{
return true;
}
return false;
},
/**
* Is this object using pixel perfect checking?
*
* @method Phaser.InputHandler#isPixelPerfect
* @return {boolean} True if the this InputHandler has either `pixelPerfectClick` or `pixelPerfectOver` set to `true`.
*/
isPixelPerfect: function () {
return (this.pixelPerfectClick || this.pixelPerfectOver);
},
/**
* The x coordinate of the Input pointer, relative to the top-left of the parent Sprite.
* This value is only set when the pointer is over this Sprite.
*
* @method Phaser.InputHandler#pointerX
* @param {integer} [pointerId=0]
* @return {number} The x coordinate of the Input pointer.
*/
pointerX: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].x;
},
/**
* The y coordinate of the Input pointer, relative to the top-left of the parent Sprite
* This value is only set when the pointer is over this Sprite.
*
* @method Phaser.InputHandler#pointerY
* @param {integer} [pointerId=0]
* @return {number} The y coordinate of the Input pointer.
*/
pointerY: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].y;
},
/**
* If the Pointer is down this returns true.
* This *only* checks if the Pointer is down, not if it's down over any specific Sprite.
*
* @method Phaser.InputHandler#pointerDown
* @param {integer} [pointerId=0]
* @return {boolean} - True if the given pointer is down, otherwise false.
*/
pointerDown: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].isDown;
},
/**
* If the Pointer is up this returns true.
* This *only* checks if the Pointer is up, not if it's up over any specific Sprite.
*
* @method Phaser.InputHandler#pointerUp
* @param {integer} [pointerId=0]
* @return {boolean} - True if the given pointer is up, otherwise false.
*/
pointerUp: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].isUp;
},
/**
* A timestamp representing when the Pointer first touched the touchscreen.
*
* @method Phaser.InputHandler#pointerTimeDown
* @param {integer} [pointerId=(check all)]
* @return {number}
*/
pointerTimeDown: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].timeDown;
},
/**
* A timestamp representing when the Pointer left the touchscreen.
*
* @method Phaser.InputHandler#pointerTimeUp
* @param {integer} [pointerId=0]
* @return {number}
*/
pointerTimeUp: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].timeUp;
},
/**
* Is the Pointer over this Sprite?
*
* @method Phaser.InputHandler#pointerOver
* @param {integer} [pointerId=(check all)] The ID number of a Pointer to check. If you don't provide a number it will check all Pointers.
* @return {boolean} - True if the given pointer (if a index was given, or any pointer if not) is over this object.
*/
pointerOver: function (pointerId) {
if (!this.enabled)
{
return false;
}
if (pointerId === undefined)
{
for (var i = 0; i < 10; i++)
{
if (this._pointerData[i].isOver)
{
return true;
}
}
return false;
}
else
{
return this._pointerData[pointerId].isOver;
}
},
/**
* Is the Pointer outside of this Sprite?
*
* @method Phaser.InputHandler#pointerOut
* @param {integer} [pointerId=(check all)] The ID number of a Pointer to check. If you don't provide a number it will check all Pointers.
* @return {boolean} True if the given pointer (if a index was given, or any pointer if not) is out of this object.
*/
pointerOut: function (pointerId) {
if (!this.enabled)
{
return false;
}
if (pointerId === undefined)
{
for (var i = 0; i < 10; i++)
{
if (this._pointerData[i].isOut)
{
return true;
}
}
}
else
{
return this._pointerData[pointerId].isOut;
}
},
/**
* A timestamp representing when the Pointer first touched the touchscreen.
*
* @method Phaser.InputHandler#pointerTimeOver
* @param {integer} [pointerId=0]
* @return {number}
*/
pointerTimeOver: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].timeOver;
},
/**
* A timestamp representing when the Pointer left the touchscreen.
*
* @method Phaser.InputHandler#pointerTimeOut
* @param {integer} [pointerId=0]
* @return {number}
*/
pointerTimeOut: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].timeOut;
},
/**
* Is this sprite being dragged by the mouse or not?
*
* @method Phaser.InputHandler#pointerDragged
* @param {integer} [pointerId=0]
* @return {boolean} True if the pointer is dragging an object, otherwise false.
*/
pointerDragged: function (pointerId) {
pointerId = pointerId || 0;
return this._pointerData[pointerId].isDragged;
},
/**
* Checks if the given pointer is both down and over the Sprite this InputHandler belongs to.
* Use the `fastTest` flag is to quickly check just the bounding hit area even if `InputHandler.pixelPerfectOver` is `true`.
*
* @method Phaser.InputHandler#checkPointerDown
* @param {Phaser.Pointer} pointer
* @param {boolean} [fastTest=false] - Force a simple hit area check even if `pixelPerfectOver` is true for this object?
* @return {boolean} True if the pointer is down, otherwise false.
*/
checkPointerDown: function (pointer, fastTest) {
if (!pointer.isDown ||
!this.enabled ||
!this.sprite ||
!this.sprite.parent ||
!this.sprite.visible ||
!this.sprite.parent.visible ||
this.sprite.worldScale.x === 0 ||
this.sprite.worldScale.y === 0)
{
return false;
}
// Need to pass it a temp point, in case we need it again for the pixel check
if (this.game.input.hitTest(this.sprite, pointer, this._tempPoint))
{
if (fastTest === undefined)
{
fastTest = false;
}
if (!fastTest && this.pixelPerfectClick)
{
return this.checkPixel(this._tempPoint.x, this._tempPoint.y);
}
else
{
return true;
}
}
return false;
},
/**
* Checks if the given pointer is over the Sprite this InputHandler belongs to.
* Use the `fastTest` flag is to quickly check just the bounding hit area even if `InputHandler.pixelPerfectOver` is `true`.
*
* @method Phaser.InputHandler#checkPointerOver
* @param {Phaser.Pointer} pointer
* @param {boolean} [fastTest=false] - Force a simple hit area check even if `pixelPerfectOver` is true for this object?
* @return {boolean}
*/
checkPointerOver: function (pointer, fastTest) {
if (!this.enabled ||
!this.sprite ||
!this.sprite.parent ||
!this.sprite.visible ||
!this.sprite.parent.visible ||
this.sprite.worldScale.x === 0 ||
this.sprite.worldScale.y === 0)
{
return false;
}
// Need to pass it a temp point, in case we need it again for the pixel check
if (this.game.input.hitTest(this.sprite, pointer, this._tempPoint))
{
if (fastTest === undefined)
{
fastTest = false;
}
if (!fastTest && this.pixelPerfectOver)
{
return this.checkPixel(this._tempPoint.x, this._tempPoint.y);
}
else
{
return true;
}
}
return false;
},
/**
* Runs a pixel perfect check against the given x/y coordinates of the Sprite this InputHandler is bound to.
* It compares the alpha value of the pixel and if >= InputHandler.pixelPerfectAlpha it returns true.
*
* @method Phaser.InputHandler#checkPixel
* @param {number} x - The x coordinate to check.
* @param {number} y - The y coordinate to check.
* @param {Phaser.Pointer} [pointer] - The pointer to get the x/y coordinate from if not passed as the first two parameters.
* @return {boolean} true if there is the alpha of the pixel is >= InputHandler.pixelPerfectAlpha
*/
checkPixel: function (x, y, pointer) {
// Grab a pixel from our image into the hitCanvas and then test it
if (this.sprite.texture.baseTexture.source)
{
if (x === null && y === null)
{
// Use the pointer parameter
this.game.input.getLocalPosition(this.sprite, pointer, this._tempPoint);
var x = this._tempPoint.x;
var y = this._tempPoint.y;
}
if (this.sprite.anchor.x !== 0)
{
x -= -this.sprite.texture.frame.width * this.sprite.anchor.x;
}
if (this.sprite.anchor.y !== 0)
{
y -= -this.sprite.texture.frame.height * this.sprite.anchor.y;
}
x += this.sprite.texture.frame.x;
y += this.sprite.texture.frame.y;
if (this.sprite.texture.trim)
{
x -= this.sprite.texture.trim.x;
y -= this.sprite.texture.trim.y;
// If the coordinates are outside the trim area we return false immediately, to save doing a draw call
if (x < this.sprite.texture.crop.x || x > this.sprite.texture.crop.right || y < this.sprite.texture.crop.y || y > this.sprite.texture.crop.bottom)
{
this._dx = x;
this._dy = y;
return false;
}
}
this._dx = x;
this._dy = y;
this.game.input.hitContext.clearRect(0, 0, 1, 1);
this.game.input.hitContext.drawImage(this.sprite.texture.baseTexture.source, x, y, 1, 1, 0, 0, 1, 1);
var rgb = this.game.input.hitContext.getImageData(0, 0, 1, 1);
if (rgb.data[3] >= this.pixelPerfectAlpha)
{
return true;
}
}
return false;
},
/**
* Internal Update method. This is called automatically and handles the Pointer
* and drag update loops.
*
* @method Phaser.InputHandler#update
* @protected
* @param {Phaser.Pointer} pointer
* @return {boolean} True if the pointer is still active, otherwise false.
*/
update: function (pointer) {
if (this.sprite === null || this.sprite.parent === undefined)
{
// Abort. We've been destroyed.
return;
}
if (!this.enabled || !this.sprite.visible || !this.sprite.parent.visible)
{
this._pointerOutHandler(pointer);
return false;
}
if (this._pendingDrag)
{
if (!this._dragDistancePass)
{
this._dragDistancePass = (Phaser.Math.distance(pointer.x, pointer.y, this.downPoint.x, this.downPoint.y) >= this.dragDistanceThreshold);
}
if (this._dragDistancePass && this._dragTimePass)
{
this.startDrag(pointer);
}
return true;
}
else if (this.draggable && this._draggedPointerID === pointer.id)
{
return this.updateDrag(pointer, false);
}
else if (this._pointerData[pointer.id].isOver)
{
if (this.checkPointerOver(pointer))
{
this._pointerData[pointer.id].x = pointer.x - this.sprite.x;
this._pointerData[pointer.id].y = pointer.y - this.sprite.y;
return true;
}
else
{
this._pointerOutHandler(pointer);
return false;
}
}
},
/**
* Internal method handling the pointer over event.
*
* @method Phaser.InputHandler#_pointerOverHandler
* @private
* @param {Phaser.Pointer} pointer - The pointer that triggered the event
* @param {boolean} [silent=false] - If silent is `true` then this method will not dispatch any Signals from the parent Sprite.
*/
_pointerOverHandler: function (pointer, silent) {
if (this.sprite === null)
{
// Abort. We've been destroyed.
return;
}
var data = this._pointerData[pointer.id];
if (data.isOver === false || pointer.dirty)
{
var sendEvent = (data.isOver === false);
data.isOver = true;
data.isOut = false;
data.timeOver = this.game.time.time;
data.x = pointer.x - this.sprite.x;
data.y = pointer.y - this.sprite.y;
if (this.useHandCursor && data.isDragged === false)
{
this.game.canvas.style.cursor = "pointer";
this._setHandCursor = true;
}
if (!silent && sendEvent && this.sprite && this.sprite.events)
{
this.sprite.events.onInputOver$dispatch(this.sprite, pointer);
}
if (this.sprite.parent && this.sprite.parent.type === Phaser.GROUP)
{
this.sprite.parent.onChildInputOver.dispatch(this.sprite, pointer);
}
}
},
/**
* Internal method handling the pointer out event.
*
* @method Phaser.InputHandler#_pointerOutHandler
* @private
* @param {Phaser.Pointer} pointer - The pointer that triggered the event.
* @param {boolean} [silent=false] - If silent is `true` then this method will not dispatch any Signals from the parent Sprite.
*/
_pointerOutHandler: function (pointer, silent) {
if (this.sprite === null)
{
// Abort. We've been destroyed.
return;
}
var data = this._pointerData[pointer.id];
data.isOver = false;
data.isOut = true;
data.timeOut = this.game.time.time;
if (this.useHandCursor && data.isDragged === false)
{
this.game.canvas.style.cursor = "default";
this._setHandCursor = false;
}
if (!silent && this.sprite && this.sprite.events)
{
this.sprite.events.onInputOut$dispatch(this.sprite, pointer);
if (this.sprite && this.sprite.parent && this.sprite.parent.type === Phaser.GROUP)
{
this.sprite.parent.onChildInputOut.dispatch(this.sprite, pointer);
}
}
},
/**
* Internal method handling the touched / clicked event.
*
* @method Phaser.InputHandler#_touchedHandler
* @private
* @param {Phaser.Pointer} pointer - The pointer that triggered the event.
*/
_touchedHandler: function (pointer) {
if (this.sprite === null)
{
// Abort. We've been destroyed.
return;
}
var data = this._pointerData[pointer.id];
if (!data.isDown && data.isOver)
{
if (this.pixelPerfectClick && !this.checkPixel(null, null, pointer))
{
return;
}
data.isDown = true;
data.isUp = false;
data.timeDown = this.game.time.time;
this.downPoint.set(pointer.x, pointer.y);
// It's possible the onInputDown event creates a new Sprite that is on-top of this one, so we ought to force a Pointer update
pointer.dirty = true;
if (this.sprite && this.sprite.events)
{
this.sprite.events.onInputDown$dispatch(this.sprite, pointer);
// The event above might have destroyed this sprite.
if (this.sprite && this.sprite.parent && this.sprite.parent.type === Phaser.GROUP)
{
this.sprite.parent.onChildInputDown.dispatch(this.sprite, pointer);
}
// The events might have destroyed this sprite.
if (this.sprite === null)
{
return;
}
}
// Start drag
if (this.draggable && this.isDragged === false)
{
if (this.dragTimeThreshold === 0 && this.dragDistanceThreshold === 0)
{
this.startDrag(pointer);
}
else
{
this._pendingDrag = true;
this._dragDistancePass = (this.dragDistanceThreshold === 0);
if (this.dragTimeThreshold > 0)
{
this._dragTimePass = false;
this.game.time.events.add(this.dragTimeThreshold, this.dragTimeElapsed, this, pointer);
}
else
{
this._dragTimePass = true;
}
}
}
if (this.bringToTop)
{
this.sprite.bringToTop();
}
}
},
/**
* Internal method handling the drag threshold timer.
*
* @method Phaser.InputHandler#dragTimeElapsed
* @private
* @param {Phaser.Pointer} pointer
*/
dragTimeElapsed: function (pointer) {
this._dragTimePass = true;
if (this._pendingDrag && this.sprite)
{
if (this._dragDistancePass)
{
this.startDrag(pointer);
}
}
},
/**
* Internal method handling the pointer released event.
* @method Phaser.InputHandler#_releasedHandler
* @private
* @param {Phaser.Pointer} pointer
*/
_releasedHandler: function (pointer) {
if (this.sprite === null)
{
// Abort. We've been destroyed.
return;
}
var data = this._pointerData[pointer.id];
// If was previously touched by this Pointer, check if still is AND still over this item
if (data.isDown && pointer.isUp)
{
data.isDown = false;
data.isUp = true;
data.timeUp = this.game.time.time;
data.downDuration = data.timeUp - data.timeDown;
// Only release the InputUp signal if the pointer is still over this sprite
var isOver = this.checkPointerOver(pointer);
if (this.sprite && this.sprite.events)
{
if (!this.dragStopBlocksInputUp ||
this.dragStopBlocksInputUp && !(this.draggable && this.isDragged && this._draggedPointerID === pointer.id))
{
this.sprite.events.onInputUp$dispatch(this.sprite, pointer, isOver);
}
if (this.sprite && this.sprite.parent && this.sprite.parent.type === Phaser.GROUP)
{
this.sprite.parent.onChildInputUp.dispatch(this.sprite, pointer, isOver);
}
// The onInputUp event may have changed the sprite so that checkPointerOver is no longer true, so update it.
if (isOver)
{
isOver = this.checkPointerOver(pointer);
}
}
data.isOver = isOver;
if (!isOver && this.useHandCursor)
{
this.game.canvas.style.cursor = "default";
this._setHandCursor = false;
}
// It's possible the onInputUp event created a new Sprite that is on-top of this one, so force a Pointer update
pointer.dirty = true;
this._pendingDrag = false;
// Stop drag
if (this.draggable && this.isDragged && this._draggedPointerID === pointer.id)
{
this.stopDrag(pointer);
}
}
},
/**
* Called as a Pointer actively drags this Game Object.
*
* @method Phaser.InputHandler#updateDrag
* @private
* @param {Phaser.Pointer} pointer - The Pointer causing the drag update.
* @param {boolean} fromStart - True if this is the first update, immediately after the drag has started.
* @return {boolean}
*/
updateDrag: function (pointer, fromStart) {
if (fromStart === undefined) { fromStart = false; }
if (pointer.isUp)
{
this.stopDrag(pointer);
return false;
}
var px = this.globalToLocalX(pointer.x) + this._dragPoint.x + this.dragOffset.x;
var py = this.globalToLocalY(pointer.y) + this._dragPoint.y + this.dragOffset.y;
if (this.sprite.fixedToCamera)
{
if (this.allowHorizontalDrag)
{
this.sprite.cameraOffset.x = px;
}
if (this.allowVerticalDrag)
{
this.sprite.cameraOffset.y = py;
}
if (this.boundsRect)
{
this.checkBoundsRect();
}
if (this.boundsSprite)
{
this.checkBoundsSprite();
}
if (this.snapOnDrag)
{
this.sprite.cameraOffset.x = Math.round((this.sprite.cameraOffset.x - (this.snapOffsetX % this.snapX)) / this.snapX) * this.snapX + (this.snapOffsetX % this.snapX);
this.sprite.cameraOffset.y = Math.round((this.sprite.cameraOffset.y - (this.snapOffsetY % this.snapY)) / this.snapY) * this.snapY + (this.snapOffsetY % this.snapY);
this.snapPoint.set(this.sprite.cameraOffset.x, this.sprite.cameraOffset.y);
}
}
else
{
var cx = this.game.camera.x - this._pointerData[pointer.id].camX;
var cy = this.game.camera.y - this._pointerData[pointer.id].camY;
if (this.allowHorizontalDrag)
{
this.sprite.x = px + cx;
}
if (this.allowVerticalDrag)
{
this.sprite.y = py + cy;
}
if (this.boundsRect)
{
this.checkBoundsRect();
}
if (this.boundsSprite)
{
this.checkBoundsSprite();
}
if (this.snapOnDrag)
{
this.sprite.x = Math.round((this.sprite.x - (this.snapOffsetX % this.snapX)) / this.snapX) * this.snapX + (this.snapOffsetX % this.snapX);
this.sprite.y = Math.round((this.sprite.y - (this.snapOffsetY % this.snapY)) / this.snapY) * this.snapY + (this.snapOffsetY % this.snapY);
this.snapPoint.set(this.sprite.x, this.sprite.y);
}
}
this.sprite.events.onDragUpdate.dispatch(this.sprite, pointer, px, py, this.snapPoint, fromStart);
return true;
},
/**
* Returns true if the pointer has entered the Sprite within the specified delay time (defaults to 500ms, half a second)
*
* @method Phaser.InputHandler#justOver
* @param {integer} [pointerId=0]
* @param {number} delay - The time below which the pointer is considered as just over.
* @return {boolean}
*/
justOver: function (pointerId, delay) {
pointerId = pointerId || 0;
delay = delay || 500;
return (this._pointerData[pointerId].isOver && this.overDuration(pointerId) < delay);
},
/**
* Returns true if the pointer has left the Sprite within the specified delay time (defaults to 500ms, half a second)
*
* @method Phaser.InputHandler#justOut
* @param {integer} [pointerId=0]
* @param {number} delay - The time below which the pointer is considered as just out.
* @return {boolean}
*/
justOut: function (pointerId, delay) {
pointerId = pointerId || 0;
delay = delay || 500;
return (this._pointerData[pointerId].isOut && (this.game.time.time - this._pointerData[pointerId].timeOut < delay));
},
/**
* Returns true if the pointer has touched or clicked on the Sprite within the specified delay time (defaults to 500ms, half a second)
*
* @method Phaser.InputHandler#justPressed
* @param {integer} [pointerId=0]
* @param {number} delay - The time below which the pointer is considered as just over.
* @return {boolean}
*/
justPressed: function (pointerId, delay) {
pointerId = pointerId || 0;
delay = delay || 500;
return (this._pointerData[pointerId].isDown && this.downDuration(pointerId) < delay);
},
/**
* Returns true if the pointer was touching this Sprite, but has been released within the specified delay time (defaults to 500ms, half a second)
*
* @method Phaser.InputHandler#justReleased
* @param {integer} [pointerId=0]
* @param {number} delay - The time below which the pointer is considered as just out.
* @return {boolean}
*/
justReleased: function (pointerId, delay) {
pointerId = pointerId || 0;
delay = delay || 500;
return (this._pointerData[pointerId].isUp && (this.game.time.time - this._pointerData[pointerId].timeUp < delay));
},
/**
* If the pointer is currently over this Sprite this returns how long it has been there for in milliseconds.
*
* @method Phaser.InputHandler#overDuration
* @param {integer} [pointerId=0]
* @return {number} The number of milliseconds the pointer has been over the Sprite, or -1 if not over.
*/
overDuration: function (pointerId) {
pointerId = pointerId || 0;
if (this._pointerData[pointerId].isOver)
{
return this.game.time.time - this._pointerData[pointerId].timeOver;
}
return -1;
},
/**
* If the pointer is currently over this Sprite this returns how long it has been there for in milliseconds.
*
* @method Phaser.InputHandler#downDuration
* @param {integer} [pointerId=0]
* @return {number} The number of milliseconds the pointer has been pressed down on the Sprite, or -1 if not over.
*/
downDuration: function (pointerId) {
pointerId = pointerId || 0;
if (this._pointerData[pointerId].isDown)
{
return this.game.time.time - this._pointerData[pointerId].timeDown;
}
return -1;
},
/**
* Allow this Sprite to be dragged by any valid pointer.
*
* When the drag begins the Sprite.events.onDragStart event will be dispatched.
*
* When the drag completes by way of the user letting go of the pointer that was dragging the sprite, the Sprite.events.onDragStop event is dispatched.
*
* You can control the thresholds over when a drag starts via the properties:
*
* `Pointer.dragDistanceThreshold` the distance, in pixels, that the pointer has to move
* before the drag will start.
*
* `Pointer.dragTimeThreshold` the time, in ms, that the pointer must be held down on
* the Sprite before the drag will start.
*
* You can set either (or both) of these properties after enabling a Sprite for drag.
*
* For the duration of the drag the Sprite.events.onDragUpdate event is dispatched. This event is only dispatched when the pointer actually
* changes position and moves. The event sends 5 parameters: `sprite`, `pointer`, `dragX`, `dragY` and `snapPoint`.
*
* @method Phaser.InputHandler#enableDrag
* @param {boolean} [lockCenter=false] - If false the Sprite will drag from where you click it minus the dragOffset. If true it will center itself to the tip of the mouse pointer.
* @param {boolean} [bringToTop=false] - If true the Sprite will be bought to the top of the rendering list in its current Group.
* @param {boolean} [pixelPerfect=false] - If true it will use a pixel perfect test to see if you clicked the Sprite. False uses the bounding box.
* @param {boolean} [alphaThreshold=255] - If using pixel perfect collision this specifies the alpha level from 0 to 255 above which a collision is processed.
* @param {Phaser.Rectangle} [boundsRect=null] - If you want to restrict the drag of this sprite to a specific Rectangle, pass the Phaser.Rectangle here, otherwise it's free to drag anywhere.
* @param {Phaser.Sprite} [boundsSprite=null] - If you want to restrict the drag of this sprite to within the bounding box of another sprite, pass it here.
*/
enableDrag: function (lockCenter, bringToTop, pixelPerfect, alphaThreshold, boundsRect, boundsSprite) {
if (lockCenter === undefined) { lockCenter = false; }
if (bringToTop === undefined) { bringToTop = false; }
if (pixelPerfect === undefined) { pixelPerfect = false; }
if (alphaThreshold === undefined) { alphaThreshold = 255; }
if (boundsRect === undefined) { boundsRect = null; }
if (boundsSprite === undefined) { boundsSprite = null; }
this._dragPoint = new Phaser.Point();
this.draggable = true;
this.bringToTop = bringToTop;
this.dragOffset = new Phaser.Point();
this.dragFromCenter = lockCenter;
this.pixelPerfectClick = pixelPerfect;
this.pixelPerfectAlpha = alphaThreshold;
if (boundsRect)
{
this.boundsRect = boundsRect;
}
if (boundsSprite)
{
this.boundsSprite = boundsSprite;
}
},
/**
* Stops this sprite from being able to be dragged.
* If it is currently the target of an active drag it will be stopped immediately; also disables any set callbacks.
*
* @method Phaser.InputHandler#disableDrag
*/
disableDrag: function () {
if (this._pointerData)
{
for (var i = 0; i < 10; i++)
{
this._pointerData[i].isDragged = false;
}
}
this.draggable = false;
this.isDragged = false;
this._draggedPointerID = -1;
this._pendingDrag = false;
},
/**
* Called by Pointer when drag starts on this Sprite. Should not usually be called directly.
*
* @method Phaser.InputHandler#startDrag
* @param {Phaser.Pointer} pointer
*/
startDrag: function (pointer) {
var x = this.sprite.x;
var y = this.sprite.y;
this.isDragged = true;
this._draggedPointerID = pointer.id;
this._pointerData[pointer.id].camX = this.game.camera.x;
this._pointerData[pointer.id].camY = this.game.camera.y;
this._pointerData[pointer.id].isDragged = true;
if (this.sprite.fixedToCamera)
{
if (this.dragFromCenter)
{
var bounds = this.sprite.getBounds();
this.sprite.cameraOffset.x = this.globalToLocalX(pointer.x) + (this.sprite.cameraOffset.x - bounds.centerX);
this.sprite.cameraOffset.y = this.globalToLocalY(pointer.y) + (this.sprite.cameraOffset.y - bounds.centerY);
}
this._dragPoint.setTo(this.sprite.cameraOffset.x - pointer.x, this.sprite.cameraOffset.y - pointer.y);
}
else
{
if (this.dragFromCenter)
{
var bounds = this.sprite.getBounds();
this.sprite.x = this.globalToLocalX(pointer.x) + (this.sprite.x - bounds.centerX);
this.sprite.y = this.globalToLocalY(pointer.y) + (this.sprite.y - bounds.centerY);
}
this._dragPoint.setTo(this.sprite.x - this.globalToLocalX(pointer.x), this.sprite.y - this.globalToLocalY(pointer.y));
}
this.updateDrag(pointer, true);
if (this.bringToTop)
{
this._dragPhase = true;
this.sprite.bringToTop();
}
this.dragStartPoint.set(x, y);
this.sprite.events.onDragStart$dispatch(this.sprite, pointer, x, y);
this._pendingDrag = false;
},
/**
* Warning: EXPERIMENTAL
*
* @method Phaser.InputHandler#globalToLocalX
* @param {number} x
*/
globalToLocalX: function (x) {
if (this.scaleLayer)
{
x -= this.game.scale.grid.boundsFluid.x;
x *= this.game.scale.grid.scaleFluidInversed.x;
}
return x;
},
/**
* Warning: EXPERIMENTAL
*
* @method Phaser.InputHandler#globalToLocalY
* @param {number} y
*/
globalToLocalY: function (y) {
if (this.scaleLayer)
{
y -= this.game.scale.grid.boundsFluid.y;
y *= this.game.scale.grid.scaleFluidInversed.y;
}
return y;
},
/**
* Called by Pointer when drag is stopped on this Sprite. Should not usually be called directly.
*
* @method Phaser.InputHandler#stopDrag
* @param {Phaser.Pointer} pointer
*/
stopDrag: function (pointer) {
this.isDragged = false;
this._draggedPointerID = -1;
this._pointerData[pointer.id].isDragged = false;
this._dragPhase = false;
this._pendingDrag = false;
if (this.snapOnRelease)
{
if (this.sprite.fixedToCamera)
{
this.sprite.cameraOffset.x = Math.round((this.sprite.cameraOffset.x - (this.snapOffsetX % this.snapX)) / this.snapX) * this.snapX + (this.snapOffsetX % this.snapX);
this.sprite.cameraOffset.y = Math.round((this.sprite.cameraOffset.y - (this.snapOffsetY % this.snapY)) / this.snapY) * this.snapY + (this.snapOffsetY % this.snapY);
}
else
{
this.sprite.x = Math.round((this.sprite.x - (this.snapOffsetX % this.snapX)) / this.snapX) * this.snapX + (this.snapOffsetX % this.snapX);
this.sprite.y = Math.round((this.sprite.y - (this.snapOffsetY % this.snapY)) / this.snapY) * this.snapY + (this.snapOffsetY % this.snapY);
}
}
this.sprite.events.onDragStop$dispatch(this.sprite, pointer);
if (this.checkPointerOver(pointer) === false)
{
this._pointerOutHandler(pointer);
}
},
/**
* Restricts this sprite to drag movement only on the given axis. Note: If both are set to false the sprite will never move!
*
* @method Phaser.InputHandler#setDragLock
* @param {boolean} [allowHorizontal=true] - To enable the sprite to be dragged horizontally set to true, otherwise false.
* @param {boolean} [allowVertical=true] - To enable the sprite to be dragged vertically set to true, otherwise false.
*/
setDragLock: function (allowHorizontal, allowVertical) {
if (allowHorizontal === undefined) { allowHorizontal = true; }
if (allowVertical === undefined) { allowVertical = true; }
this.allowHorizontalDrag = allowHorizontal;
this.allowVerticalDrag = allowVertical;
},
/**
* Make this Sprite snap to the given grid either during drag or when it's released.
* For example 16x16 as the snapX and snapY would make the sprite snap to every 16 pixels.
*
* @method Phaser.InputHandler#enableSnap
* @param {number} snapX - The width of the grid cell to snap to.
* @param {number} snapY - The height of the grid cell to snap to.
* @param {boolean} [onDrag=true] - If true the sprite will snap to the grid while being dragged.
* @param {boolean} [onRelease=false] - If true the sprite will snap to the grid when released.
* @param {number} [snapOffsetX=0] - Used to offset the top-left starting point of the snap grid.
* @param {number} [snapOffsetY=0] - Used to offset the top-left starting point of the snap grid.
*/
enableSnap: function (snapX, snapY, onDrag, onRelease, snapOffsetX, snapOffsetY) {
if (onDrag === undefined) { onDrag = true; }
if (onRelease === undefined) { onRelease = false; }
if (snapOffsetX === undefined) { snapOffsetX = 0; }
if (snapOffsetY === undefined) { snapOffsetY = 0; }
this.snapX = snapX;
this.snapY = snapY;
this.snapOffsetX = snapOffsetX;
this.snapOffsetY = snapOffsetY;
this.snapOnDrag = onDrag;
this.snapOnRelease = onRelease;
},
/**
* Stops the sprite from snapping to a grid during drag or release.
*
* @method Phaser.InputHandler#disableSnap
*/
disableSnap: function () {
this.snapOnDrag = false;
this.snapOnRelease = false;
},
/**
* Bounds Rect check for the sprite drag
*
* @method Phaser.InputHandler#checkBoundsRect
*/
checkBoundsRect: function () {
if (this.sprite.fixedToCamera)
{
if (this.sprite.cameraOffset.x < this.boundsRect.left)
{
this.sprite.cameraOffset.x = this.boundsRect.left;
}
else if ((this.sprite.cameraOffset.x + this.sprite.width) > this.boundsRect.right)
{
this.sprite.cameraOffset.x = this.boundsRect.right - this.sprite.width;
}
if (this.sprite.cameraOffset.y < this.boundsRect.top)
{
this.sprite.cameraOffset.y = this.boundsRect.top;
}
else if ((this.sprite.cameraOffset.y + this.sprite.height) > this.boundsRect.bottom)
{
this.sprite.cameraOffset.y = this.boundsRect.bottom - this.sprite.height;
}
}
else
{
if (this.sprite.left < this.boundsRect.left)
{
this.sprite.x = this.boundsRect.x + this.sprite.offsetX;
}
else if (this.sprite.right > this.boundsRect.right)
{
this.sprite.x = this.boundsRect.right - (this.sprite.width - this.sprite.offsetX);
}
if (this.sprite.top < this.boundsRect.top)
{
this.sprite.y = this.boundsRect.top + this.sprite.offsetY;
}
else if (this.sprite.bottom > this.boundsRect.bottom)
{
this.sprite.y = this.boundsRect.bottom - (this.sprite.height - this.sprite.offsetY);
}
}
},
/**
* Parent Sprite Bounds check for the sprite drag.
*
* @method Phaser.InputHandler#checkBoundsSprite
*/
checkBoundsSprite: function () {
if (this.sprite.fixedToCamera && this.boundsSprite.fixedToCamera)
{
if (this.sprite.cameraOffset.x < this.boundsSprite.cameraOffset.x)
{
this.sprite.cameraOffset.x = this.boundsSprite.cameraOffset.x;
}
else if ((this.sprite.cameraOffset.x + this.sprite.width) > (this.boundsSprite.cameraOffset.x + this.boundsSprite.width))
{
this.sprite.cameraOffset.x = (this.boundsSprite.cameraOffset.x + this.boundsSprite.width) - this.sprite.width;
}
if (this.sprite.cameraOffset.y < this.boundsSprite.cameraOffset.y)
{
this.sprite.cameraOffset.y = this.boundsSprite.cameraOffset.y;
}
else if ((this.sprite.cameraOffset.y + this.sprite.height) > (this.boundsSprite.cameraOffset.y + this.boundsSprite.height))
{
this.sprite.cameraOffset.y = (this.boundsSprite.cameraOffset.y + this.boundsSprite.height) - this.sprite.height;
}
}
else
{
if (this.sprite.left < this.boundsSprite.left)
{
this.sprite.x = this.boundsSprite.left + this.sprite.offsetX;
}
else if (this.sprite.right > this.boundsSprite.right)
{
this.sprite.x = this.boundsSprite.right - (this.sprite.width - this.sprite.offsetX);
}
if (this.sprite.top < this.boundsSprite.top)
{
this.sprite.y = this.boundsSprite.top + this.sprite.offsetY;
}
else if (this.sprite.bottom > this.boundsSprite.bottom)
{
this.sprite.y = this.boundsSprite.bottom - (this.sprite.height - this.sprite.offsetY);
}
}
}
};
Phaser.InputHandler.prototype.constructor = Phaser.InputHandler;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
Phaser.Component = function () {};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Angle Component provides access to an `angle` property; the rotation of a Game Object in degrees.
*
* @class
*/
Phaser.Component.Angle = function () {};
Phaser.Component.Angle.prototype = {
/**
* The angle property is the rotation of the Game Object 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 player.angle = 450 is the same as player.angle = 90.
*
* If you wish to work in radians instead of degrees you can use the property `rotation` instead.
* Working in radians is slightly faster as it doesn't have to perform any calculations.
*
* @property {number} angle
*/
angle: {
get: function() {
return Phaser.Math.wrapAngle(Phaser.Math.radToDeg(this.rotation));
},
set: function(value) {
this.rotation = Phaser.Math.degToRad(Phaser.Math.wrapAngle(value));
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Animation Component provides a `play` method, which is a proxy to the `AnimationManager.play` method.
*
* @class
*/
Phaser.Component.Animation = function () {};
Phaser.Component.Animation.prototype = {
/**
* Plays an Animation.
*
* The animation should have previously been created via `animations.add`.
*
* If the animation is already playing calling this again won't do anything.
* If you need to reset an already running animation do so directly on the Animation object itself or via `AnimationManager.stop`.
*
* @method
* @param {string} name - The name of the animation to be played, e.g. "fire", "walk", "jump". Must have been previously created via 'AnimationManager.add'.
* @param {number} [frameRate=null] - The framerate to play the animation at. The speed is given in frames per second. If not provided the previously set frameRate of the Animation is used.
* @param {boolean} [loop=false] - Should the animation be looped after playback. If not provided the previously set loop value of the Animation is used.
* @param {boolean} [killOnComplete=false] - If set to true when the animation completes (only happens if loop=false) the parent Sprite will be killed.
* @return {Phaser.Animation} A reference to playing Animation.
*/
play: function (name, frameRate, loop, killOnComplete) {
if (this.animations)
{
return this.animations.play(name, frameRate, loop, killOnComplete);
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The AutoCull Component is responsible for providing methods that check if a Game Object is within the bounds of the World Camera.
* It is used by the InWorld component.
*
* @class
*/
Phaser.Component.AutoCull = function () {};
Phaser.Component.AutoCull.prototype = {
/**
* A Game Object with `autoCull` set to true will check its bounds against the World Camera every frame.
* If it is not intersecting the Camera bounds at any point then it has its `renderable` property set to `false`.
* This keeps the Game Object alive and still processing updates, but forces it to skip the render step entirely.
*
* This is a relatively expensive operation, especially if enabled on hundreds of Game Objects. So enable it only if you know it's required,
* or you have tested performance and find it acceptable.
*
* @property {boolean} autoCull
* @default
*/
autoCull: false,
/**
* Checks if the Game Objects bounds intersect with the Game Camera bounds.
* Returns `true` if they do, otherwise `false` if fully outside of the Cameras bounds.
*
* @property {boolean} inCamera
* @readonly
*/
inCamera: {
get: function() {
if (!this.autoCull && !this.checkWorldBounds)
{
this._bounds.copyFrom(this.getBounds());
this._bounds.x += this.game.camera.view.x;
this._bounds.y += this.game.camera.view.y;
}
return this.game.world.camera.view.intersects(this._bounds);
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Bounds component contains properties related to the bounds of the Game Object.
*
* @class
*/
Phaser.Component.Bounds = function () {};
Phaser.Component.Bounds.prototype = {
/**
* The amount the Game Object is visually offset from its x coordinate.
* This is the same as `width * anchor.x`.
* It will only be > 0 if anchor.x is not equal to zero.
*
* @property {number} offsetX
* @readOnly
*/
offsetX: {
get: function () {
return this.anchor.x * this.width;
}
},
/**
* The amount the Game Object is visually offset from its y coordinate.
* This is the same as `height * anchor.y`.
* It will only be > 0 if anchor.y is not equal to zero.
*
* @property {number} offsetY
* @readOnly
*/
offsetY: {
get: function () {
return this.anchor.y * this.height;
}
},
/**
* The center x coordinate of the Game Object.
* This is the same as `(x - offsetX) + (width / 2)`.
*
* @property {number} centerX
*/
centerX: {
get: function () {
return (this.x - this.offsetX) + (this.width * 0.5);
},
set: function (value) {
this.x = (value + this.offsetX) - (this.width * 0.5);
}
},
/**
* The center y coordinate of the Game Object.
* This is the same as `(y - offsetY) + (height / 2)`.
*
* @property {number} centerY
*/
centerY: {
get: function () {
return (this.y - this.offsetY) + (this.height * 0.5);
},
set: function (value) {
this.y = (value + this.offsetY) - (this.height * 0.5);
}
},
/**
* The left coordinate of the Game Object.
* This is the same as `x - offsetX`.
*
* @property {number} left
*/
left: {
get: function () {
return this.x - this.offsetX;
},
set: function (value) {
this.x = value + this.offsetX;
}
},
/**
* The right coordinate of the Game Object.
* This is the same as `x + width - offsetX`.
*
* @property {number} right
*/
right: {
get: function () {
return (this.x + this.width) - this.offsetX;
},
set: function (value) {
this.x = value - (this.width) + this.offsetX;
}
},
/**
* The y coordinate of the Game Object.
* This is the same as `y - offsetY`.
*
* @property {number} top
*/
top: {
get: function () {
return this.y - this.offsetY;
},
set: function (value) {
this.y = value + this.offsetY;
}
},
/**
* The sum of the y and height properties.
* This is the same as `y + height - offsetY`.
*
* @property {number} bottom
*/
bottom: {
get: function () {
return (this.y + this.height) - this.offsetY;
},
set: function (value) {
this.y = value - (this.height) + this.offsetY;
}
},
/**
* Aligns this Game Object within another Game Object, or Rectangle, known as the
* 'container', to one of 9 possible positions.
*
* The container must be a Game Object, or Phaser.Rectangle object. This can include properties
* such as `World.bounds` or `Camera.view`, for aligning Game Objects within the world
* and camera bounds. Or it can include other Sprites, Images, Text objects, BitmapText,
* TileSprites or Buttons.
*
* Please note that aligning a Sprite to another Game Object does **not** make it a child of
* the container. It simply modifies its position coordinates so it aligns with it.
*
* The position constants you can use are:
*
* `Phaser.TOP_LEFT`, `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`,
* `Phaser.CENTER`, `Phaser.RIGHT_CENTER`, `Phaser.BOTTOM_LEFT`,
* `Phaser.BOTTOM_CENTER` and `Phaser.BOTTOM_RIGHT`.
*
* The Game Objects are placed in such a way that their _bounds_ align with the
* container, taking into consideration rotation, scale and the anchor property.
* This allows you to neatly align Game Objects, irrespective of their position value.
*
* The optional `offsetX` and `offsetY` arguments allow you to apply extra spacing to the final
* aligned position of the Game Object. For example:
*
* `sprite.alignIn(background, Phaser.BOTTOM_RIGHT, -20, -20)`
*
* Would align the `sprite` to the bottom-right, but moved 20 pixels in from the corner.
* Think of the offsets as applying an adjustment to the containers bounds before the alignment takes place.
* So providing a negative offset will 'shrink' the container bounds by that amount, and providing a positive
* one expands it.
*
* @method
* @param {Phaser.Rectangle|Phaser.Sprite|Phaser.Image|Phaser.Text|Phaser.BitmapText|Phaser.Button|Phaser.Graphics|Phaser.TileSprite} container - The Game Object or Rectangle with which to align this Game Object to. Can also include properties such as `World.bounds` or `Camera.view`.
* @param {integer} [position] - The position constant. One of `Phaser.TOP_LEFT` (default), `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_CENTER`, `Phaser.CENTER`, `Phaser.RIGHT_CENTER`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER` or `Phaser.BOTTOM_RIGHT`.
* @param {integer} [offsetX=0] - A horizontal adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @param {integer} [offsetY=0] - A vertical adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @return {Object} This Game Object.
*/
alignIn: function (container, position, offsetX, offsetY) {
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
switch (position)
{
default:
case Phaser.TOP_LEFT:
this.left = container.left - offsetX;
this.top = container.top - offsetY;
break;
case Phaser.TOP_CENTER:
this.centerX = container.centerX + offsetX;
this.top = container.top - offsetY;
break;
case Phaser.TOP_RIGHT:
this.right = container.right + offsetX;
this.top = container.top - offsetY;
break;
case Phaser.LEFT_CENTER:
this.left = container.left - offsetX;
this.centerY = container.centerY + offsetY;
break;
case Phaser.CENTER:
this.centerX = container.centerX + offsetX;
this.centerY = container.centerY + offsetY;
break;
case Phaser.RIGHT_CENTER:
this.right = container.right + offsetX;
this.centerY = container.centerY + offsetY;
break;
case Phaser.BOTTOM_LEFT:
this.left = container.left - offsetX;
this.bottom = container.bottom + offsetY;
break;
case Phaser.BOTTOM_CENTER:
this.centerX = container.centerX + offsetX;
this.bottom = container.bottom + offsetY;
break;
case Phaser.BOTTOM_RIGHT:
this.right = container.right + offsetX;
this.bottom = container.bottom + offsetY;
break;
}
return this;
},
/**
* Aligns this Game Object to the side of another Game Object, or Rectangle, known as the
* 'parent', in one of 11 possible positions.
*
* The parent must be a Game Object, or Phaser.Rectangle object. This can include properties
* such as `World.bounds` or `Camera.view`, for aligning Game Objects within the world
* and camera bounds. Or it can include other Sprites, Images, Text objects, BitmapText,
* TileSprites or Buttons.
*
* Please note that aligning a Sprite to another Game Object does **not** make it a child of
* the parent. It simply modifies its position coordinates so it aligns with it.
*
* The position constants you can use are:
*
* `Phaser.TOP_LEFT` (default), `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_TOP`,
* `Phaser.LEFT_CENTER`, `Phaser.LEFT_BOTTOM`, `Phaser.RIGHT_TOP`, `Phaser.RIGHT_CENTER`,
* `Phaser.RIGHT_BOTTOM`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER`
* and `Phaser.BOTTOM_RIGHT`.
*
* The Game Objects are placed in such a way that their _bounds_ align with the
* parent, taking into consideration rotation, scale and the anchor property.
* This allows you to neatly align Game Objects, irrespective of their position value.
*
* The optional `offsetX` and `offsetY` arguments allow you to apply extra spacing to the final
* aligned position of the Game Object. For example:
*
* `sprite.alignTo(background, Phaser.BOTTOM_RIGHT, -20, -20)`
*
* Would align the `sprite` to the bottom-right, but moved 20 pixels in from the corner.
* Think of the offsets as applying an adjustment to the parents bounds before the alignment takes place.
* So providing a negative offset will 'shrink' the parent bounds by that amount, and providing a positive
* one expands it.
*
* @method
* @param {Phaser.Rectangle|Phaser.Sprite|Phaser.Image|Phaser.Text|Phaser.BitmapText|Phaser.Button|Phaser.Graphics|Phaser.TileSprite} parent - The Game Object or Rectangle with which to align this Game Object to. Can also include properties such as `World.bounds` or `Camera.view`.
* @param {integer} [position] - The position constant. One of `Phaser.TOP_LEFT`, `Phaser.TOP_CENTER`, `Phaser.TOP_RIGHT`, `Phaser.LEFT_TOP`, `Phaser.LEFT_CENTER`, `Phaser.LEFT_BOTTOM`, `Phaser.RIGHT_TOP`, `Phaser.RIGHT_CENTER`, `Phaser.RIGHT_BOTTOM`, `Phaser.BOTTOM_LEFT`, `Phaser.BOTTOM_CENTER` or `Phaser.BOTTOM_RIGHT`.
* @param {integer} [offsetX=0] - A horizontal adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @param {integer} [offsetY=0] - A vertical adjustment of the Containers bounds, applied to the aligned position of the Game Object. Use a negative value to shrink the bounds, positive to increase it.
* @return {Object} This Game Object.
*/
alignTo: function (parent, position, offsetX, offsetY) {
if (offsetX === undefined) { offsetX = 0; }
if (offsetY === undefined) { offsetY = 0; }
switch (position)
{
default:
case Phaser.TOP_LEFT:
this.left = parent.left - offsetX;
this.bottom = parent.top - offsetY;
break;
case Phaser.TOP_CENTER:
this.centerX = parent.centerX + offsetX;
this.bottom = parent.top - offsetY;
break;
case Phaser.TOP_RIGHT:
this.right = parent.right + offsetX;
this.bottom = parent.top - offsetY;
break;
case Phaser.LEFT_TOP:
this.right = parent.left - offsetX;
this.top = parent.top - offsetY;
break;
case Phaser.LEFT_CENTER:
this.right = parent.left - offsetX;
this.centerY = parent.centerY + offsetY;
break;
case Phaser.LEFT_BOTTOM:
this.right = parent.left - offsetX;
this.bottom = parent.bottom + offsetY;
break;
case Phaser.RIGHT_TOP:
this.left = parent.right + offsetX;
this.top = parent.top - offsetY;
break;
case Phaser.RIGHT_CENTER:
this.left = parent.right + offsetX;
this.centerY = parent.centerY + offsetY;
break;
case Phaser.RIGHT_BOTTOM:
this.left = parent.right + offsetX;
this.bottom = parent.bottom + offsetY;
break;
case Phaser.BOTTOM_LEFT:
this.left = parent.left - offsetX;
this.top = parent.bottom + offsetY;
break;
case Phaser.BOTTOM_CENTER:
this.centerX = parent.centerX + offsetX;
this.top = parent.bottom + offsetY;
break;
case Phaser.BOTTOM_RIGHT:
this.right = parent.right + offsetX;
this.top = parent.bottom + offsetY;
break;
}
return this;
}
};
// Phaser.Group extensions
Phaser.Group.prototype.alignIn = Phaser.Component.Bounds.prototype.alignIn;
Phaser.Group.prototype.alignTo = Phaser.Component.Bounds.prototype.alignTo;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The BringToTop Component features quick access to Group sorting related methods.
*
* @class
*/
Phaser.Component.BringToTop = function () {};
/**
* Brings this Game Object to the top of its parents display list.
* Visually this means it will render over the top of any old child in the same Group.
*
* If this Game Object hasn't been added to a custom Group then this method will bring it to the top of the Game World,
* because the World is the root Group from which all Game Objects descend.
*
* @method
* @return {PIXI.DisplayObject} This instance.
*/
Phaser.Component.BringToTop.prototype.bringToTop = function() {
if (this.parent)
{
this.parent.bringToTop(this);
}
return this;
};
/**
* Sends this Game Object to the bottom of its parents display list.
* Visually this means it will render below all other children in the same Group.
*
* If this Game Object hasn't been added to a custom Group then this method will send it to the bottom of the Game World,
* because the World is the root Group from which all Game Objects descend.
*
* @method
* @return {PIXI.DisplayObject} This instance.
*/
Phaser.Component.BringToTop.prototype.sendToBack = function() {
if (this.parent)
{
this.parent.sendToBack(this);
}
return this;
};
/**
* Moves this Game Object up one place in its parents display list.
* This call has no effect if the Game Object is already at the top of the display list.
*
* If this Game Object hasn't been added to a custom Group then this method will move it one object up within the Game World,
* because the World is the root Group from which all Game Objects descend.
*
* @method
* @return {PIXI.DisplayObject} This instance.
*/
Phaser.Component.BringToTop.prototype.moveUp = function () {
if (this.parent)
{
this.parent.moveUp(this);
}
return this;
};
/**
* Moves this Game Object down one place in its parents display list.
* This call has no effect if the Game Object is already at the bottom of the display list.
*
* If this Game Object hasn't been added to a custom Group then this method will move it one object down within the Game World,
* because the World is the root Group from which all Game Objects descend.
*
* @method
* @return {PIXI.DisplayObject} This instance.
*/
Phaser.Component.BringToTop.prototype.moveDown = function () {
if (this.parent)
{
this.parent.moveDown(this);
}
return this;
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Core Component Features.
*
* @class
*/
Phaser.Component.Core = function () {};
/**
* Installs / registers mixin components.
*
* The `this` context should be that of the applicable object instance or prototype.
*
* @method
* @protected
*/
Phaser.Component.Core.install = function (components) {
// Always install 'Core' first
Phaser.Utils.mixinPrototype(this, Phaser.Component.Core.prototype);
this.components = {};
for (var i = 0; i < components.length; i++)
{
var id = components[i];
var replace = false;
if (id === 'Destroy')
{
replace = true;
}
Phaser.Utils.mixinPrototype(this, Phaser.Component[id].prototype, replace);
this.components[id] = true;
}
};
/**
* Initializes the mixin components.
*
* The `this` context should be an instance of the component mixin target.
*
* @method
* @protected
*/
Phaser.Component.Core.init = function (game, x, y, key, frame) {
this.game = game;
this.key = key;
this.data = {};
this.position.set(x, y);
this.world = new Phaser.Point(x, y);
this.previousPosition = new Phaser.Point(x, y);
this.events = new Phaser.Events(this);
this._bounds = new Phaser.Rectangle();
if (this.components.PhysicsBody)
{
// Enable-body checks for hasOwnProperty; makes sure to lift property from prototype.
this.body = this.body;
}
if (this.components.Animation)
{
this.animations = new Phaser.AnimationManager(this);
}
if (this.components.LoadTexture && key !== null)
{
this.loadTexture(key, frame);
}
if (this.components.FixedToCamera)
{
this.cameraOffset = new Phaser.Point(x, y);
}
};
Phaser.Component.Core.preUpdate = function () {
if (this.pendingDestroy)
{
this.destroy();
return;
}
this.previousPosition.set(this.world.x, this.world.y);
this.previousRotation = this.rotation;
if (!this.exists || !this.parent.exists)
{
this.renderOrderID = -1;
return false;
}
this.world.setTo(this.game.camera.x + this.worldTransform.tx, this.game.camera.y + this.worldTransform.ty);
if (this.visible)
{
this.renderOrderID = this.game.stage.currentRenderOrderID++;
}
if (this.animations)
{
this.animations.update();
}
if (this.body)
{
this.body.preUpdate();
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i].preUpdate();
}
return true;
};
Phaser.Component.Core.prototype = {
/**
* A reference to the currently running Game.
* @property {Phaser.Game} game
*/
game: null,
/**
* A user defined name given to this Game Object.
* This value isn't ever used internally by Phaser, it is meant as a game level property.
* @property {string} name
* @default
*/
name: '',
/**
* An empty Object that belongs to this Game Object.
* This value isn't ever used internally by Phaser, but may be used by your own code, or
* by Phaser Plugins, to store data that needs to be associated with the Game Object,
* without polluting the Game Object directly.
* @property {Object} data
* @default
*/
data: {},
/**
* The components this Game Object has installed.
* @property {object} components
* @protected
*/
components: {},
/**
* The z depth of this Game Object within its parent Group.
* No two objects in a Group can have the same z value.
* This value is adjusted automatically whenever the Group hierarchy changes.
* If you wish to re-order the layering of a Game Object then see methods like Group.moveUp or Group.bringToTop.
* @property {number} z
* @readOnly
*/
z: 0,
/**
* All Phaser Game Objects have an Events class which contains all of the events that are dispatched when certain things happen to this
* Game Object, or any of its components.
* @see Phaser.Events
* @property {Phaser.Events} events
*/
events: undefined,
/**
* If the Game Object is enabled for animation (such as a Phaser.Sprite) this is a reference to its AnimationManager instance.
* Through it you can create, play, pause and stop animations.
* @see Phaser.AnimationManager
* @property {Phaser.AnimationManager} animations
*/
animations: undefined,
/**
* The key of the image or texture used by this Game Object during rendering.
* If it is a string it's the string used to retrieve the texture from the Phaser Image Cache.
* It can also be an instance of a RenderTexture, BitmapData, Video or PIXI.Texture.
* If a Game Object is created without a key it is automatically assigned the key `__default` which is a 32x32 transparent PNG stored within the Cache.
* If a Game Object is given a key which doesn't exist in the Image Cache it is re-assigned the key `__missing` which is a 32x32 PNG of a green box with a line through it.
* @property {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} key
*/
key: '',
/**
* The world coordinates of this Game Object in pixels.
* Depending on where in the display list this Game Object is placed this value can differ from `position`,
* which contains the x/y coordinates relative to the Game Objects parent.
* @property {Phaser.Point} world
*/
world: null,
/**
* A debug flag designed for use with `Game.enableStep`.
* @property {boolean} debug
* @default
*/
debug: false,
/**
* The position the Game Object was located in the previous frame.
* @property {Phaser.Point} previousPosition
* @readOnly
*/
previousPosition: null,
/**
* The rotation the Game Object was in set to in the previous frame. Value is in radians.
* @property {number} previousRotation
* @readOnly
*/
previousRotation: 0,
/**
* The render order ID is used internally by the renderer and Input Manager and should not be modified.
* This property is mostly used internally by the renderers, but is exposed for the use of plugins.
* @property {number} renderOrderID
* @readOnly
*/
renderOrderID: 0,
/**
* A Game Object is considered `fresh` if it has just been created or reset and is yet to receive a renderer transform update.
* This property is mostly used internally by the physics systems, but is exposed for the use of plugins.
* @property {boolean} fresh
* @readOnly
*/
fresh: true,
/**
* A Game Object is that is pendingDestroy is flagged to have its destroy method called on the next logic update.
* You can set it directly to allow you to flag an object to be destroyed on its next update.
*
* This is extremely useful if you wish to destroy an object from within one of its own callbacks
* such as with Buttons or other Input events.
*
* @property {boolean} pendingDestroy
*/
pendingDestroy: false,
/**
* @property {Phaser.Rectangle} _bounds - Internal cache var.
* @private
*/
_bounds: null,
/**
* @property {boolean} _exists - Internal cache var.
* @private
*/
_exists: true,
/**
* Controls if this Game Object is processed by the core game loop.
* If this Game Object has a physics body it also controls if its physics body is updated or not.
* When `exists` is set to `false` it will remove its physics body from the physics world if it has one.
* It also toggles the `visible` property to false as well.
*
* Setting `exists` to true will add its physics body back in to the physics world, if it has one.
* It will also set the `visible` property to `true`.
*
* @property {boolean} exists
*/
exists: {
get: function () {
return this._exists;
},
set: function (value) {
if (value)
{
this._exists = true;
if (this.body && this.body.type === Phaser.Physics.P2JS)
{
this.body.addToWorld();
}
this.visible = true;
}
else
{
this._exists = false;
if (this.body && this.body.type === Phaser.Physics.P2JS)
{
this.body.removeFromWorld();
}
this.visible = false;
}
}
},
/**
* Override this method in your own custom objects to handle any update requirements.
* It is called immediately after `preUpdate` and before `postUpdate`.
* Remember if this Game Object has any children you should call update on those too.
*
* @method
*/
update: function() {
},
/**
* Internal method called by the World postUpdate cycle.
*
* @method
* @protected
*/
postUpdate: function() {
if (this.customRender)
{
this.key.render();
}
if (this.components.PhysicsBody)
{
Phaser.Component.PhysicsBody.postUpdate.call(this);
}
if (this.components.FixedToCamera)
{
Phaser.Component.FixedToCamera.postUpdate.call(this);
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i].postUpdate();
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Crop component provides the ability to crop a texture based Game Object to a defined rectangle,
* which can be updated in real-time.
*
* @class
*/
Phaser.Component.Crop = function () {};
Phaser.Component.Crop.prototype = {
/**
* The Rectangle used to crop the texture this Game Object uses.
* Set this property via `crop`.
* If you modify this property directly you must call `updateCrop` in order to have the change take effect.
* @property {Phaser.Rectangle} cropRect
* @default
*/
cropRect: null,
/**
* @property {Phaser.Rectangle} _crop - Internal cache var.
* @private
*/
_crop: null,
/**
* Crop allows you to crop the texture being used to display this Game Object.
* Setting a crop rectangle modifies the core texture frame. The Game Object width and height properties will be adjusted accordingly.
*
* Cropping takes place from the top-left and can be modified in real-time either by providing an updated rectangle object to this method,
* or by modifying `cropRect` property directly and then calling `updateCrop`.
*
* The rectangle object given to this method can be either a `Phaser.Rectangle` or any other object
* so long as it has public `x`, `y`, `width`, `height`, `right` and `bottom` properties.
*
* A reference to the rectangle is stored in `cropRect` unless the `copy` parameter is `true`,
* in which case the values are duplicated to a local object.
*
* @method
* @param {Phaser.Rectangle} rect - The Rectangle used during cropping. Pass null or no parameters to clear a previously set crop rectangle.
* @param {boolean} [copy=false] - If false `cropRect` will be stored as a reference to the given rect. If true it will copy the rect values into a local Phaser Rectangle object stored in cropRect.
*/
crop: function (rect, copy) {
if (copy === undefined) { copy = false; }
if (rect)
{
if (copy && this.cropRect !== null)
{
this.cropRect.setTo(rect.x, rect.y, rect.width, rect.height);
}
else if (copy && this.cropRect === null)
{
this.cropRect = new Phaser.Rectangle(rect.x, rect.y, rect.width, rect.height);
}
else
{
this.cropRect = rect;
}
this.updateCrop();
}
else
{
this._crop = null;
this.cropRect = null;
this.resetFrame();
}
},
/**
* If you have set a crop rectangle on this Game Object via `crop` and since modified the `cropRect` property,
* or the rectangle it references, then you need to update the crop frame by calling this method.
*
* @method
*/
updateCrop: function () {
if (!this.cropRect)
{
return;
}
var oldX = this.texture.crop.x;
var oldY = this.texture.crop.y;
var oldW = this.texture.crop.width;
var oldH = this.texture.crop.height;
this._crop = Phaser.Rectangle.clone(this.cropRect, this._crop);
this._crop.x += this._frame.x;
this._crop.y += this._frame.y;
var cx = Math.max(this._frame.x, this._crop.x);
var cy = Math.max(this._frame.y, this._crop.y);
var cw = Math.min(this._frame.right, this._crop.right) - cx;
var ch = Math.min(this._frame.bottom, this._crop.bottom) - cy;
this.texture.crop.x = cx;
this.texture.crop.y = cy;
this.texture.crop.width = cw;
this.texture.crop.height = ch;
this.texture.frame.width = Math.min(cw, this.cropRect.width);
this.texture.frame.height = Math.min(ch, this.cropRect.height);
this.texture.width = this.texture.frame.width;
this.texture.height = this.texture.frame.height;
this.texture._updateUvs();
if (this.tint !== 0xffffff && (oldX !== cx || oldY !== cy || oldW !== cw || oldH !== ch))
{
this.texture.requiresReTint = true;
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Delta component provides access to delta values between the Game Objects current and previous position.
*
* @class
*/
Phaser.Component.Delta = function () {};
Phaser.Component.Delta.prototype = {
/**
* Returns the delta x value. The difference between world.x now and in the previous frame.
*
* The value will be positive if the Game Object has moved to the right or negative if to the left.
*
* @property {number} deltaX
* @readonly
*/
deltaX: {
get: function() {
return this.world.x - this.previousPosition.x;
}
},
/**
* Returns the delta y value. The difference between world.y now and in the previous frame.
*
* The value will be positive if the Game Object has moved down or negative if up.
*
* @property {number} deltaY
* @readonly
*/
deltaY: {
get: function() {
return this.world.y - this.previousPosition.y;
}
},
/**
* Returns the delta z value. The difference between rotation now and in the previous frame.
*
* @property {number} deltaZ - The delta value.
* @readonly
*/
deltaZ: {
get: function() {
return this.rotation - this.previousRotation;
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Destroy component is responsible for destroying a Game Object.
*
* @class
*/
Phaser.Component.Destroy = function () {};
Phaser.Component.Destroy.prototype = {
/**
* As a Game Object runs through its destroy method this flag is set to true,
* and can be checked in any sub-systems or plugins it is being destroyed from.
* @property {boolean} destroyPhase
* @readOnly
*/
destroyPhase: false,
/**
* Destroys the Game Object. This removes it from its parent group, destroys the input, event and animation handlers if present
* and nulls its reference to `game`, freeing it up for garbage collection.
*
* If this Game Object has the Events component it will also dispatch the `onDestroy` event.
*
* You can optionally also destroy the BaseTexture this Game Object is using. Be careful if you've
* more than one Game Object sharing the same BaseTexture.
*
* @method
* @param {boolean} [destroyChildren=true] - Should every child of this object have its destroy method called as well?
* @param {boolean} [destroyTexture=false] - Destroy the BaseTexture this Game Object is using? Note that if another Game Object is sharing the same BaseTexture it will invalidate it.
*/
destroy: function (destroyChildren, destroyTexture) {
if (this.game === null || this.destroyPhase) { return; }
if (destroyChildren === undefined) { destroyChildren = true; }
if (destroyTexture === undefined) { destroyTexture = false; }
this.destroyPhase = true;
if (this.events)
{
this.events.onDestroy$dispatch(this);
}
if (this.parent)
{
if (this.parent instanceof Phaser.Group)
{
this.parent.remove(this);
}
else
{
this.parent.removeChild(this);
}
}
if (this.input)
{
this.input.destroy();
}
if (this.animations)
{
this.animations.destroy();
}
if (this.body)
{
this.body.destroy();
}
if (this.events)
{
this.events.destroy();
}
this.game.tweens.removeFrom(this);
var i = this.children.length;
if (destroyChildren)
{
while (i--)
{
this.children[i].destroy(destroyChildren);
}
}
else
{
while (i--)
{
this.removeChild(this.children[i]);
}
}
if (this._crop)
{
this._crop = null;
this.cropRect = null;
}
if (this._frame)
{
this._frame = null;
}
if (Phaser.Video && this.key instanceof Phaser.Video)
{
this.key.onChangeSource.remove(this.resizeFrame, this);
}
if (Phaser.BitmapText && this._glyphs)
{
this._glyphs = [];
}
this.alive = false;
this.exists = false;
this.visible = false;
this.filters = null;
this.mask = null;
this.game = null;
this.data = {};
// In case Pixi is still going to try and render it even though destroyed
this.renderable = false;
if (this.transformCallback)
{
this.transformCallback = null;
this.transformCallbackContext = null;
}
// Pixi level DisplayObject destroy
this.hitArea = null;
this.parent = null;
this.stage = null;
this.worldTransform = null;
this.filterArea = null;
this._bounds = null;
this._currentBounds = null;
this._mask = null;
this._destroyCachedSprite();
// Texture?
if (destroyTexture)
{
this.texture.destroy(true);
}
this.destroyPhase = false;
this.pendingDestroy = false;
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Events component is a collection of events fired by the parent Game Object.
*
* Phaser uses what are known as 'Signals' for all event handling. All of the events in
* this class are signals you can subscribe to, much in the same way you'd "listen" for
* an event.
*
* For example to tell when a Sprite has been added to a new group, you can bind a function
* to the `onAddedToGroup` signal:
*
* `sprite.events.onAddedToGroup.add(yourFunction, this);`
*
* Where `yourFunction` is the function you want called when this event occurs.
*
* For more details about how signals work please see the Phaser.Signal class.
*
* The Input-related events will only be dispatched if the Sprite has had `inputEnabled` set to `true`
* and the Animation-related events only apply to game objects with animations like {@link Phaser.Sprite}.
*
* @class Phaser.Events
* @constructor
* @param {Phaser.Sprite} sprite - A reference to the game object / Sprite that owns this Events object.
*/
Phaser.Events = function (sprite) {
/**
* @property {Phaser.Sprite} parent - The Sprite that owns these events.
*/
this.parent = sprite;
// The signals are automatically added by the corresponding proxy properties
};
Phaser.Events.prototype = {
/**
* Removes all events.
*
* @method Phaser.Events#destroy
*/
destroy: function () {
this._parent = null;
if (this._onDestroy) { this._onDestroy.dispose(); }
if (this._onAddedToGroup) { this._onAddedToGroup.dispose(); }
if (this._onRemovedFromGroup) { this._onRemovedFromGroup.dispose(); }
if (this._onRemovedFromWorld) { this._onRemovedFromWorld.dispose(); }
if (this._onKilled) { this._onKilled.dispose(); }
if (this._onRevived) { this._onRevived.dispose(); }
if (this._onEnterBounds) { this._onEnterBounds.dispose(); }
if (this._onOutOfBounds) { this._onOutOfBounds.dispose(); }
if (this._onInputOver) { this._onInputOver.dispose(); }
if (this._onInputOut) { this._onInputOut.dispose(); }
if (this._onInputDown) { this._onInputDown.dispose(); }
if (this._onInputUp) { this._onInputUp.dispose(); }
if (this._onDragStart) { this._onDragStart.dispose(); }
if (this._onDragUpdate) { this._onDragUpdate.dispose(); }
if (this._onDragStop) { this._onDragStop.dispose(); }
if (this._onAnimationStart) { this._onAnimationStart.dispose(); }
if (this._onAnimationComplete) { this._onAnimationComplete.dispose(); }
if (this._onAnimationLoop) { this._onAnimationLoop.dispose(); }
},
// The following properties are sentinels that will be replaced with getters
/**
* This signal is dispatched when this Game Object is added to a new Group.
* It is sent two arguments:
* {any} The Game Object that was added to the Group.
* {Phaser.Group} The Group it was added to.
* @property {Phaser.Signal} onAddedToGroup
*/
onAddedToGroup: null,
/**
* This signal is dispatched when the Game Object is removed from a Group.
* It is sent two arguments:
* {any} The Game Object that was removed from the Group.
* {Phaser.Group} The Group it was removed from.
* @property {Phaser.Signal} onRemovedFromGroup
*/
onRemovedFromGroup: null,
/**
* This Signal is never used internally by Phaser and is now deprecated.
* @deprecated
* @property {Phaser.Signal} onRemovedFromWorld
*/
onRemovedFromWorld: null,
/**
* This signal is dispatched when the Game Object is destroyed.
* This happens when `Sprite.destroy()` is called, or `Group.destroy()` with `destroyChildren` set to true.
* It is sent one argument:
* {any} The Game Object that was destroyed.
* @property {Phaser.Signal} onDestroy
*/
onDestroy: null,
/**
* This signal is dispatched when the Game Object is killed.
* This happens when `Sprite.kill()` is called.
* Please understand the difference between `kill` and `destroy` by looking at their respective methods.
* It is sent one argument:
* {any} The Game Object that was killed.
* @property {Phaser.Signal} onKilled
*/
onKilled: null,
/**
* This signal is dispatched when the Game Object is revived from a previously killed state.
* This happens when `Sprite.revive()` is called.
* It is sent one argument:
* {any} The Game Object that was revived.
* @property {Phaser.Signal} onRevived
*/
onRevived: null,
/**
* This signal is dispatched when the Game Object leaves the Phaser.World bounds.
* This signal is only if `Sprite.checkWorldBounds` is set to `true`.
* It is sent one argument:
* {any} The Game Object that left the World bounds.
* @property {Phaser.Signal} onOutOfBounds
*/
onOutOfBounds: null,
/**
* This signal is dispatched when the Game Object returns within the Phaser.World bounds, having previously been outside of them.
* This signal is only if `Sprite.checkWorldBounds` is set to `true`.
* It is sent one argument:
* {any} The Game Object that entered the World bounds.
* @property {Phaser.Signal} onEnterBounds
*/
onEnterBounds: null,
/**
* This signal is dispatched if the Game Object has `inputEnabled` set to `true`,
* and receives an over event from a Phaser.Pointer.
* It is sent two arguments:
* {any} The Game Object that received the event.
* {Phaser.Pointer} The Phaser.Pointer object that caused the event.
* @property {Phaser.Signal} onInputOver
*/
onInputOver: null,
/**
* This signal is dispatched if the Game Object has `inputEnabled` set to `true`,
* and receives an out event from a Phaser.Pointer, which was previously over it.
* It is sent two arguments:
* {any} The Game Object that received the event.
* {Phaser.Pointer} The Phaser.Pointer object that caused the event.
* @property {Phaser.Signal} onInputOut
*/
onInputOut: null,
/**
* This signal is dispatched if the Game Object has `inputEnabled` set to `true`,
* and receives a down event from a Phaser.Pointer. This effectively means the Pointer has been
* pressed down (but not yet released) on the Game Object.
* It is sent two arguments:
* {any} The Game Object that received the event.
* {Phaser.Pointer} The Phaser.Pointer object that caused the event.
* @property {Phaser.Signal} onInputDown
*/
onInputDown: null,
/**
* This signal is dispatched if the Game Object has `inputEnabled` set to `true`,
* and receives an up event from a Phaser.Pointer. This effectively means the Pointer had been
* pressed down, and was then released on the Game Object.
* It is sent three arguments:
* {any} The Game Object that received the event.
* {Phaser.Pointer} The Phaser.Pointer object that caused the event.
* {boolean} isOver - Is the Pointer still over the Game Object?
* @property {Phaser.Signal} onInputUp
*/
onInputUp: null,
/**
* This signal is dispatched if the Game Object has been `inputEnabled` and `enableDrag` has been set.
* It is sent when a Phaser.Pointer starts to drag the Game Object, taking into consideration the various
* drag limitations that may be set.
* It is sent four arguments:
* {any} The Game Object that received the event.
* {Phaser.Pointer} The Phaser.Pointer object that caused the event.
* {number} The x coordinate that the drag started from.
* {number} The y coordinate that the drag started from.
* @property {Phaser.Signal} onDragStart
*/
onDragStart: null,
/**
* This signal is dispatched if the Game Object has been `inputEnabled` and `enableDrag` has been set.
* It is sent when a Phaser.Pointer is actively dragging the Game Object.
* Be warned: This is a high volume Signal. Be careful what you bind to it.
* It is sent six arguments:
* {any} The Game Object that received the event.
* {Phaser.Pointer} The Phaser.Pointer object that caused the event.
* {number} The new x coordinate of the Game Object.
* {number} The new y coordinate of the Game Object.
* {Phaser.Point} A Point object that contains the point the Game Object was snapped to, if `snapOnDrag` has been enabled.
* {boolean} The `fromStart` boolean, indicates if this is the first update immediately after the drag has started.
* @property {Phaser.Signal} onDragUpdate
*/
onDragUpdate: null,
/**
* This signal is dispatched if the Game Object has been `inputEnabled` and `enableDrag` has been set.
* It is sent when a Phaser.Pointer stops dragging the Game Object.
* It is sent two arguments:
* {any} The Game Object that received the event.
* {Phaser.Pointer} The Phaser.Pointer object that caused the event.
* @property {Phaser.Signal} onDragStop
*/
onDragStop: null,
/**
* This signal is dispatched if the Game Object has the AnimationManager component,
* and an Animation has been played.
* You can also listen to `Animation.onStart` rather than via the Game Objects events.
* It is sent two arguments:
* {any} The Game Object that received the event.
* {Phaser.Animation} The Phaser.Animation that was started.
* @property {Phaser.Signal} onAnimationStart
*/
onAnimationStart: null,
/**
* This signal is dispatched if the Game Object has the AnimationManager component,
* and an Animation has been stopped (via `animation.stop()` and the `dispatchComplete` argument has been set.
* You can also listen to `Animation.onComplete` rather than via the Game Objects events.
* It is sent two arguments:
* {any} The Game Object that received the event.
* {Phaser.Animation} The Phaser.Animation that was stopped.
* @property {Phaser.Signal} onAnimationComplete
*/
onAnimationComplete: null,
/**
* This signal is dispatched if the Game Object has the AnimationManager component,
* and an Animation has looped playback.
* You can also listen to `Animation.onLoop` rather than via the Game Objects events.
* It is sent two arguments:
* {any} The Game Object that received the event.
* {Phaser.Animation} The Phaser.Animation that looped.
* @property {Phaser.Signal} onAnimationLoop
*/
onAnimationLoop: null
};
Phaser.Events.prototype.constructor = Phaser.Events;
// Create an auto-create proxy getter and dispatch method for all events.
// The backing property is the same as the event name, prefixed with '_'
// and the dispatch method is the same as the event name postfixed with '$dispatch'.
for (var prop in Phaser.Events.prototype)
{
if (!Phaser.Events.prototype.hasOwnProperty(prop) ||
prop.indexOf('on') !== 0 ||
Phaser.Events.prototype[prop] !== null)
{
continue;
}
(function (prop, backing) {
'use strict';
// The accessor creates a new Signal; and so it should only be used from user-code.
Object.defineProperty(Phaser.Events.prototype, prop, {
get: function () {
return this[backing] || (this[backing] = new Phaser.Signal());
}
});
// The dispatcher will only broadcast on an already-created signal; call this internally.
Phaser.Events.prototype[prop + '$dispatch'] = function () {
return this[backing] ? this[backing].dispatch.apply(this[backing], arguments) : null;
};
})(prop, '_' + prop);
}
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The FixedToCamera component enables a Game Object to be rendered relative to the game camera coordinates, regardless
* of where in the world the camera is. This is used for things like sticking game UI to the camera that scrolls as it moves around the world.
*
* @class
*/
Phaser.Component.FixedToCamera = function () {};
/**
* The FixedToCamera component postUpdate handler.
* Called automatically by the Game Object.
*
* @method
*/
Phaser.Component.FixedToCamera.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;
}
};
Phaser.Component.FixedToCamera.prototype = {
/**
* @property {boolean} _fixedToCamera
* @private
*/
_fixedToCamera: false,
/**
* A Game Object that is "fixed" to the camera uses its x/y coordinates as offsets from the top left of the camera during rendering.
*
* The values are adjusted at the rendering stage, overriding the Game Objects actual world position.
*
* The end result is that the Game Object will appear to be 'fixed' to the camera, regardless of where in the game world
* the camera is viewing. This is useful if for example this Game Object is a UI item that you wish to be visible at all times
* regardless where in the world the camera is.
*
* The offsets are stored in the `cameraOffset` property.
*
* Note that the `cameraOffset` values are in addition to any parent of this Game Object on the display list.
*
* Be careful not to set `fixedToCamera` on Game Objects which are in Groups that already have `fixedToCamera` enabled on them.
*
* @property {boolean} fixedToCamera
*/
fixedToCamera: {
get: function () {
return this._fixedToCamera;
},
set: function (value) {
if (value)
{
this._fixedToCamera = true;
this.cameraOffset.set(this.x, this.y);
}
else
{
this._fixedToCamera = false;
}
}
},
/**
* The x/y coordinate offset applied to the top-left of the camera that this Game Object will be drawn at if `fixedToCamera` is true.
*
* The values are relative to the top-left of the camera view and in addition to any parent of the Game Object on the display list.
* @property {Phaser.Point} cameraOffset
*/
cameraOffset: new Phaser.Point()
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Health component provides the ability for Game Objects to have a `health` property
* that can be damaged and reset through game code.
* Requires the LifeSpan component.
*
* @class
*/
Phaser.Component.Health = function () {};
Phaser.Component.Health.prototype = {
/**
* The Game Objects health value. This is a handy property for setting and manipulating health on a Game Object.
*
* It can be used in combination with the `damage` method or modified directly.
*
* @property {number} health
* @default
*/
health: 1,
/**
* The Game Objects maximum health value. This works in combination with the `heal` method to ensure
* the health value never exceeds the maximum.
*
* @property {number} maxHealth
* @default
*/
maxHealth: 100,
/**
* Damages the Game Object. This removes the given amount of health from the `health` property.
*
* If health is taken below or is equal to zero then the `kill` method is called.
*
* @member
* @param {number} amount - The amount to subtract from the current `health` value.
* @return {Phaser.Sprite} This instance.
*/
damage: function (amount) {
if (this.alive)
{
this.health -= amount;
if (this.health <= 0)
{
this.kill();
}
}
return this;
},
/**
* Sets the health property of the Game Object to the given amount.
* Will never exceed the `maxHealth` value.
*
* @member
* @param {number} amount - The amount to set the `health` value to. The total will never exceed `maxHealth`.
* @return {Phaser.Sprite} This instance.
*/
setHealth: function (amount) {
this.health = amount;
if (this.health > this.maxHealth)
{
this.health = this.maxHealth;
}
return this;
},
/**
* Heal the Game Object. This adds the given amount of health to the `health` property.
*
* @member
* @param {number} amount - The amount to add to the current `health` value. The total will never exceed `maxHealth`.
* @return {Phaser.Sprite} This instance.
*/
heal: function (amount) {
if (this.alive)
{
this.health += amount;
if (this.health > this.maxHealth)
{
this.health = this.maxHealth;
}
}
return this;
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The InCamera component checks if the Game Object intersects with the Game Camera.
*
* @class
*/
Phaser.Component.InCamera = function () {};
Phaser.Component.InCamera.prototype = {
/**
* Checks if this Game Objects bounds intersects with the Game Cameras bounds.
*
* It will be `true` if they intersect, or `false` if the Game Object is fully outside of the Cameras bounds.
*
* An object outside the bounds can be considered for camera culling if it has the AutoCull component.
*
* @property {boolean} inCamera
* @readonly
*/
inCamera: {
get: function() {
return this.game.world.camera.view.intersects(this._bounds);
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The InputEnabled component allows a Game Object to have its own InputHandler and process input related events.
*
* @class
*/
Phaser.Component.InputEnabled = function () {};
Phaser.Component.InputEnabled.prototype = {
/**
* The Input Handler for this Game Object.
*
* By default it is disabled. If you wish this Game Object to process input events you should enable it with: `inputEnabled = true`.
*
* After you have done this, this property will be a reference to the Phaser InputHandler.
* @property {Phaser.InputHandler|null} input
*/
input: null,
/**
* By default a Game Object won't process any input events. By setting `inputEnabled` to true a Phaser.InputHandler is created
* for this Game Object and it will then start to process click / touch events and more.
*
* You can then access the Input Handler via `this.input`.
*
* Note that Input related events are dispatched from `this.events`, i.e.: `events.onInputDown`.
*
* If you set this property to false it will stop the Input Handler from processing any more input events.
*
* If you want to _temporarily_ disable input for a Game Object, then it's better to set
* `input.enabled = false`, as it won't reset any of the Input Handlers internal properties.
* You can then toggle this back on as needed.
*
* @property {boolean} inputEnabled
*/
inputEnabled: {
get: function () {
return (this.input && this.input.enabled);
},
set: function (value) {
if (value)
{
if (this.input === null)
{
this.input = new Phaser.InputHandler(this);
this.input.start();
}
else if (this.input && !this.input.enabled)
{
this.input.start();
}
}
else
{
if (this.input && this.input.enabled)
{
this.input.stop();
}
}
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The InWorld component checks if a Game Object is within the Game World Bounds.
* An object is considered as being "in bounds" so long as its own bounds intersects at any point with the World bounds.
* If the AutoCull component is enabled on the Game Object then it will check the Game Object against the Camera bounds as well.
*
* @class
*/
Phaser.Component.InWorld = function () {};
/**
* The InWorld component preUpdate handler.
* Called automatically by the Game Object.
*
* @method
*/
Phaser.Component.InWorld.preUpdate = function () {
// Cache the bounds if we need it
if (this.autoCull || this.checkWorldBounds)
{
this._bounds.copyFrom(this.getBounds());
this._bounds.x += this.game.camera.view.x;
this._bounds.y += this.game.camera.view.y;
if (this.autoCull)
{
// Won't get rendered but will still get its transform updated
if (this.game.world.camera.view.intersects(this._bounds))
{
this.renderable = true;
this.game.world.camera.totalInView++;
}
else
{
this.renderable = false;
if (this.outOfCameraBoundsKill)
{
this.kill();
return false;
}
}
}
if (this.checkWorldBounds)
{
// The Sprite is already out of the world bounds, so let's check to see if it has come back again
if (this._outOfBoundsFired && this.game.world.bounds.intersects(this._bounds))
{
this._outOfBoundsFired = false;
this.events.onEnterBounds$dispatch(this);
}
else if (!this._outOfBoundsFired && !this.game.world.bounds.intersects(this._bounds))
{
// The Sprite WAS in the screen, but has now left.
this._outOfBoundsFired = true;
this.events.onOutOfBounds$dispatch(this);
if (this.outOfBoundsKill)
{
this.kill();
return false;
}
}
}
}
return true;
};
Phaser.Component.InWorld.prototype = {
/**
* If this is set to `true` the Game Object checks if it is within the World bounds each frame.
*
* When it is no longer intersecting the world bounds it dispatches the `onOutOfBounds` event.
*
* If it was *previously* out of bounds but is now intersecting the world bounds again it dispatches the `onEnterBounds` event.
*
* It also optionally kills the Game Object if `outOfBoundsKill` is `true`.
*
* When `checkWorldBounds` is enabled it forces the Game Object to calculate its full bounds every frame.
*
* This is a relatively expensive operation, especially if enabled on hundreds of Game Objects. So enable it only if you know it's required,
* or you have tested performance and find it acceptable.
*
* @property {boolean} checkWorldBounds
* @default
*/
checkWorldBounds: false,
/**
* If this and the `checkWorldBounds` property are both set to `true` then the `kill` method is called as soon as `inWorld` returns false.
*
* @property {boolean} outOfBoundsKill
* @default
*/
outOfBoundsKill: false,
/**
* If this and the `autoCull` property are both set to `true`, then the `kill` method
* is called as soon as the Game Object leaves the camera bounds.
*
* @property {boolean} outOfCameraBoundsKill
* @default
*/
outOfCameraBoundsKill: false,
/**
* @property {boolean} _outOfBoundsFired - Internal state var.
* @private
*/
_outOfBoundsFired: false,
/**
* Checks if the Game Objects bounds are within, or intersect at any point with the Game World bounds.
*
* @property {boolean} inWorld
* @readonly
*/
inWorld: {
get: function () {
return this.game.world.bounds.intersects(this.getBounds());
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* LifeSpan Component Features.
*
* @class
*/
Phaser.Component.LifeSpan = function () {};
/**
* The LifeSpan component preUpdate handler.
* Called automatically by the Game Object.
*
* @method
*/
Phaser.Component.LifeSpan.preUpdate = function () {
if (this.lifespan > 0)
{
this.lifespan -= this.game.time.physicsElapsedMS;
if (this.lifespan <= 0)
{
this.kill();
return false;
}
}
return true;
};
Phaser.Component.LifeSpan.prototype = {
/**
* A useful flag to control if the Game Object is alive or dead.
*
* This is set automatically by the Health components `damage` method should the object run out of health.
* Or you can toggle it via your game code.
*
* This property is mostly just provided to be used by your game - it doesn't effect rendering or logic updates.
* However you can use `Group.getFirstAlive` in conjunction with this property for fast object pooling and recycling.
* @property {boolean} alive
* @default
*/
alive: true,
/**
* The lifespan allows you to give a Game Object a lifespan in milliseconds.
*
* Once the Game Object is 'born' you can set this to a positive value.
*
* It is automatically decremented by the millisecond equivalent of `game.time.physicsElapsed` each frame.
* When it reaches zero it will call the `kill` method.
*
* Very handy for particles, bullets, collectibles, or any other short-lived entity.
*
* @property {number} lifespan
* @default
*/
lifespan: 0,
/**
* Brings a 'dead' Game Object back to life, optionally resetting its health value in the process.
*
* A resurrected Game Object has its `alive`, `exists` and `visible` properties all set to true.
*
* It will dispatch the `onRevived` event. Listen to `events.onRevived` for the signal.
*
* @method
* @param {number} [health=100] - The health to give the Game Object. Only set if the GameObject has the Health component.
* @return {PIXI.DisplayObject} This instance.
*/
revive: function (health) {
if (health === undefined) { health = 100; }
this.alive = true;
this.exists = true;
this.visible = true;
if (typeof this.setHealth === 'function')
{
this.setHealth(health);
}
if (this.events)
{
this.events.onRevived$dispatch(this);
}
return this;
},
/**
* Kills a Game Object. A killed Game Object has its `alive`, `exists` and `visible` properties all set to false.
*
* It will dispatch the `onKilled` event. You can listen to `events.onKilled` for the signal.
*
* Note that killing a Game Object is a way for you to quickly recycle it in an object pool,
* it doesn't destroy the object or free it up from memory.
*
* If you don't need this Game Object any more you should call `destroy` instead.
*
* @method
* @return {PIXI.DisplayObject} This instance.
*/
kill: function () {
this.alive = false;
this.exists = false;
this.visible = false;
if (this.events)
{
this.events.onKilled$dispatch(this);
}
return this;
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The LoadTexture component manages the loading of a texture into the Game Object and the changing of frames.
*
* @class
*/
Phaser.Component.LoadTexture = function () {};
Phaser.Component.LoadTexture.prototype = {
/**
* @property {boolean} customRender - Does this texture require a custom render call? (as set by BitmapData, Video, etc)
* @private
*/
customRender: false,
/**
* @property {Phaser.Rectangle} _frame - Internal cache var.
* @private
*/
_frame: null,
/**
* Changes the base texture the Game Object is using. The old texture is removed and the new one is referenced or fetched from the Cache.
*
* If your Game Object is using a frame from a texture atlas and you just wish to change to another frame, then see the `frame` or `frameName` properties instead.
*
* You should only use `loadTexture` if you want to replace the base texture entirely.
*
* Calling this method causes a WebGL texture update, so use sparingly or in low-intensity portions of your game, or if you know the new texture is already on the GPU.
*
* You can use the new const `Phaser.PENDING_ATLAS` as the texture key for any sprite.
* Doing this then sets the key to be the `frame` argument (the frame is set to zero).
*
* This allows you to create sprites using `load.image` during development, and then change them
* to use a Texture Atlas later in development by simply searching your code for 'PENDING_ATLAS'
* and swapping it to be the key of the atlas data.
*
* Note: You cannot use a RenderTexture as a texture for a TileSprite.
*
* @method
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} key - This is the image or texture used by the Sprite 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 Sprite 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.
* @param {boolean} [stopAnimation=true] - If an animation is already playing on this Sprite you can choose to stop it or let it carry on playing.
*/
loadTexture: function (key, frame, stopAnimation) {
if (key === Phaser.PENDING_ATLAS)
{
key = frame;
frame = 0;
}
else
{
frame = frame || 0;
}
if ((stopAnimation || stopAnimation === undefined) && this.animations)
{
this.animations.stop();
}
this.key = key;
this.customRender = false;
var cache = this.game.cache;
var setFrame = true;
var smoothed = !this.texture.baseTexture.scaleMode;
if (Phaser.RenderTexture && key instanceof Phaser.RenderTexture)
{
this.key = key.key;
this.setTexture(key);
}
else if (Phaser.BitmapData && key instanceof Phaser.BitmapData)
{
this.customRender = true;
this.setTexture(key.texture);
if (cache.hasFrameData(key.key, Phaser.Cache.BITMAPDATA))
{
setFrame = !this.animations.loadFrameData(cache.getFrameData(key.key, Phaser.Cache.BITMAPDATA), frame);
}
else
{
setFrame = !this.animations.loadFrameData(key.frameData, 0);
}
}
else if (Phaser.Video && key instanceof Phaser.Video)
{
this.customRender = true;
// This works from a reference, which probably isn't what we need here
var valid = key.texture.valid;
this.setTexture(key.texture);
this.setFrame(key.texture.frame.clone());
key.onChangeSource.add(this.resizeFrame, this);
this.texture.valid = valid;
}
else if (Phaser.Tilemap && key instanceof Phaser.TilemapLayer)
{
// this.customRender = true;
this.setTexture(PIXI.Texture.fromCanvas(key.canvas));
}
else if (key instanceof PIXI.Texture)
{
this.setTexture(key);
}
else
{
var img = cache.getImage(key, true);
this.key = img.key;
this.setTexture(new PIXI.Texture(img.base));
if (key === '__default')
{
this.texture.baseTexture.skipRender = true;
}
else
{
this.texture.baseTexture.skipRender = false;
}
setFrame = !this.animations.loadFrameData(img.frameData, frame);
}
if (setFrame)
{
this._frame = Phaser.Rectangle.clone(this.texture.frame);
}
if (!smoothed)
{
this.texture.baseTexture.scaleMode = 1;
}
},
/**
* Sets the texture frame the Game Object uses for rendering.
*
* This is primarily an internal method used by `loadTexture`, but is exposed for the use of plugins and custom classes.
*
* @method
* @param {Phaser.Frame} frame - The Frame to be used by the texture.
*/
setFrame: function (frame) {
this._frame = frame;
this.texture.frame.x = frame.x;
this.texture.frame.y = frame.y;
this.texture.frame.width = frame.width;
this.texture.frame.height = frame.height;
this.texture.crop.x = frame.x;
this.texture.crop.y = frame.y;
this.texture.crop.width = frame.width;
this.texture.crop.height = frame.height;
if (frame.trimmed)
{
if (this.texture.trim)
{
this.texture.trim.x = frame.spriteSourceSizeX;
this.texture.trim.y = frame.spriteSourceSizeY;
this.texture.trim.width = frame.sourceSizeW;
this.texture.trim.height = frame.sourceSizeH;
}
else
{
this.texture.trim = { x: frame.spriteSourceSizeX, y: frame.spriteSourceSizeY, width: frame.sourceSizeW, height: frame.sourceSizeH };
}
this.texture.width = frame.sourceSizeW;
this.texture.height = frame.sourceSizeH;
this.texture.frame.width = frame.sourceSizeW;
this.texture.frame.height = frame.sourceSizeH;
}
else if (!frame.trimmed && this.texture.trim)
{
this.texture.trim = null;
}
if (frame.rotated)
{
this.texture.rotated = true;
}
if (this.cropRect)
{
this.updateCrop();
}
this.texture.requiresReTint = true;
this.texture._updateUvs();
if (this.tilingTexture)
{
this.refreshTexture = true;
}
},
/**
* Resizes the Frame dimensions that the Game Object uses for rendering.
*
* You shouldn't normally need to ever call this, but in the case of special texture types such as Video or BitmapData
* it can be useful to adjust the dimensions directly in this way.
*
* @method
* @param {object} parent - The parent texture object that caused the resize, i.e. a Phaser.Video object.
* @param {integer} width - The new width of the texture.
* @param {integer} height - The new height of the texture.
*/
resizeFrame: function (parent, width, height) {
this.texture.frame.resize(width, height);
this.texture.setFrame(this.texture.frame);
},
/**
* Resets the texture frame dimensions that the Game Object uses for rendering.
*
* @method
*/
resetFrame: function () {
if (this._frame)
{
this.setFrame(this._frame);
}
},
/**
* Gets or sets the current frame index of the texture being used to render this Game Object.
*
* To change the frame set `frame` to the index of the new frame in the sprite sheet you wish this Game Object to use,
* for example: `player.frame = 4`.
*
* If the frame index given doesn't exist it will revert to the first frame found in the texture.
*
* If you are using a texture atlas then you should use the `frameName` property instead.
*
* If you wish to fully replace the texture being used see `loadTexture`.
* @property {integer} frame
*/
frame: {
get: function () {
return this.animations.frame;
},
set: function (value) {
this.animations.frame = value;
}
},
/**
* Gets or sets the current frame name of the texture being used to render this Game Object.
*
* To change the frame set `frameName` to the name of the new frame in the texture atlas you wish this Game Object to use,
* for example: `player.frameName = "idle"`.
*
* If the frame name given doesn't exist it will revert to the first frame found in the texture and throw a console warning.
*
* If you are using a sprite sheet then you should use the `frame` property instead.
*
* If you wish to fully replace the texture being used see `loadTexture`.
* @property {string} frameName
*/
frameName: {
get: function () {
return this.animations.frameName;
},
set: function (value) {
this.animations.frameName = value;
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Overlap component allows a Game Object to check if it overlaps with the bounds of another Game Object.
*
* @class
*/
Phaser.Component.Overlap = function () {};
Phaser.Component.Overlap.prototype = {
/**
* Checks to see if the bounds of this Game Object overlaps with the bounds of the given Display Object,
* which can be a Sprite, Image, TileSprite or anything that extends those such as Button or provides a `getBounds` method and result.
*
* This check ignores the `hitArea` property if set and runs a `getBounds` comparison on both objects to determine the result.
*
* Therefore it's relatively expensive to use in large quantities, i.e. with lots of Sprites at a high frequency.
* It should be fine for low-volume testing where physics isn't required.
*
* @method
* @param {Phaser.Sprite|Phaser.Image|Phaser.TileSprite|Phaser.Button|PIXI.DisplayObject} displayObject - The display object to check against.
* @return {boolean} True if the bounds of this Game Object intersects at any point with the bounds of the given display object.
*/
overlap: function (displayObject) {
return Phaser.Rectangle.intersects(this.getBounds(), displayObject.getBounds());
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The PhysicsBody component manages the Game Objects physics body and physics enabling.
* It also overrides the x and y properties, ensuring that any manual adjustment of them is reflected in the physics body itself.
*
* @class
*/
Phaser.Component.PhysicsBody = function () {};
/**
* The PhysicsBody component preUpdate handler.
* Called automatically by the Game Object.
*
* @method
*/
Phaser.Component.PhysicsBody.preUpdate = function () {
if (this.fresh && this.exists)
{
this.world.setTo(this.parent.position.x + this.position.x, this.parent.position.y + this.position.y);
this.worldTransform.tx = this.world.x;
this.worldTransform.ty = this.world.y;
this.previousPosition.set(this.world.x, this.world.y);
this.previousRotation = this.rotation;
if (this.body)
{
this.body.preUpdate();
}
this.fresh = false;
return false;
}
this.previousPosition.set(this.world.x, this.world.y);
this.previousRotation = this.rotation;
if (!this._exists || !this.parent.exists)
{
this.renderOrderID = -1;
return false;
}
return true;
};
/**
* The PhysicsBody component postUpdate handler.
* Called automatically by the Game Object.
*
* @method
*/
Phaser.Component.PhysicsBody.postUpdate = function () {
if (this.exists && this.body)
{
this.body.postUpdate();
}
};
Phaser.Component.PhysicsBody.prototype = {
/**
* `body` is the Game Objects physics body. Once a Game Object is enabled for physics you access all associated
* properties and methods via it.
*
* By default Game Objects won't add themselves to any physics system and their `body` property will be `null`.
*
* To enable this Game Object for physics you need to call `game.physics.enable(object, system)` where `object` is this object
* and `system` is the Physics system you are using. If none is given it defaults to `Phaser.Physics.Arcade`.
*
* You can alternatively call `game.physics.arcade.enable(object)`, or add this Game Object to a physics enabled Group.
*
* Important: Enabling a Game Object for P2 or Ninja physics will automatically set its `anchor` property to 0.5,
* so the physics body is centered on the Game Object.
*
* If you need a different result then adjust or re-create the Body shape offsets manually or reset the anchor after enabling physics.
*
* @property {Phaser.Physics.Arcade.Body|Phaser.Physics.P2.Body|Phaser.Physics.Ninja.Body|null} body
* @default
*/
body: null,
/**
* The position of the Game Object on the x axis relative to the local coordinates of the parent.
*
* @property {number} x
*/
x: {
get: function () {
return this.position.x;
},
set: function (value) {
this.position.x = value;
if (this.body && !this.body.dirty)
{
this.body._reset = true;
}
}
},
/**
* The position of the Game Object on the y axis relative to the local coordinates of the parent.
*
* @property {number} y
*/
y: {
get: function () {
return this.position.y;
},
set: function (value) {
this.position.y = value;
if (this.body && !this.body.dirty)
{
this.body._reset = true;
}
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Reset component allows a Game Object to be reset and repositioned to a new location.
*
* @class
*/
Phaser.Component.Reset = function () {};
/**
* Resets the Game Object.
*
* This moves the Game Object to the given x/y world coordinates and sets `fresh`, `exists`,
* `visible` and `renderable` to true.
*
* If this Game Object has the LifeSpan component it will also set `alive` to true and `health` to the given value.
*
* If this Game Object has a Physics Body it will reset the Body.
*
* @method
* @param {number} x - The x coordinate (in world space) to position the Game Object at.
* @param {number} y - The y coordinate (in world space) to position the Game Object at.
* @param {number} [health=1] - The health to give the Game Object if it has the Health component.
* @return {PIXI.DisplayObject} This instance.
*/
Phaser.Component.Reset.prototype.reset = function (x, y, health) {
if (health === undefined) { health = 1; }
this.world.set(x, y);
this.position.set(x, y);
this.fresh = true;
this.exists = true;
this.visible = true;
this.renderable = true;
if (this.components.InWorld)
{
this._outOfBoundsFired = false;
}
if (this.components.LifeSpan)
{
this.alive = true;
this.health = health;
}
if (this.components.PhysicsBody)
{
if (this.body)
{
this.body.reset(x, y, false, false);
}
}
return this;
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The ScaleMinMax component allows a Game Object to limit how far it can be scaled by its parent.
*
* @class
*/
Phaser.Component.ScaleMinMax = function () {};
Phaser.Component.ScaleMinMax.prototype = {
/**
* The callback that will apply any scale limiting to the worldTransform.
* @property {function} transformCallback
*/
transformCallback: null,
/**
* The context under which `transformCallback` is called.
* @property {object} transformCallbackContext
*/
transformCallbackContext: this,
/**
* The minimum scale this Game Object will scale down to.
*
* It allows you to prevent a parent from scaling this Game Object lower than the given value.
*
* Set it to `null` to remove the limit.
* @property {Phaser.Point} scaleMin
*/
scaleMin: null,
/**
* The maximum scale this Game Object will scale up to.
*
* It allows you to prevent a parent from scaling this Game Object higher than the given value.
*
* Set it to `null` to remove the limit.
* @property {Phaser.Point} scaleMax
*/
scaleMax: null,
/**
* Adjust scaling limits, if set, to this Game Object.
*
* @method
* @private
* @param {PIXI.Matrix} wt - The updated worldTransform matrix.
*/
checkTransform: function (wt) {
if (this.scaleMin)
{
if (wt.a < this.scaleMin.x)
{
wt.a = this.scaleMin.x;
}
if (wt.d < this.scaleMin.y)
{
wt.d = this.scaleMin.y;
}
}
if (this.scaleMax)
{
if (wt.a > this.scaleMax.x)
{
wt.a = this.scaleMax.x;
}
if (wt.d > this.scaleMax.y)
{
wt.d = this.scaleMax.y;
}
}
},
/**
* Sets the scaleMin and scaleMax values. These values are used to limit how far this Game Object will scale based on its parent.
*
* For example if this Game Object has a `minScale` value of 1 and its parent has a `scale` value of 0.5, the 0.5 will be ignored
* and the scale value of 1 will be used, as the parents scale is lower than the minimum scale this Game Object should adhere to.
*
* By setting these values you can carefully control how Game Objects deal with responsive scaling.
*
* If only one parameter is given then that value will be used for both scaleMin and scaleMax:
* `setScaleMinMax(1)` = scaleMin.x, scaleMin.y, scaleMax.x and scaleMax.y all = 1
*
* If only two parameters are given the first is set as scaleMin.x and y and the second as scaleMax.x and y:
* `setScaleMinMax(0.5, 2)` = scaleMin.x and y = 0.5 and scaleMax.x and y = 2
*
* If you wish to set `scaleMin` with different values for x and y then either modify Game Object.scaleMin directly,
* or pass `null` for the `maxX` and `maxY` parameters.
*
* Call `setScaleMinMax(null)` to clear all previously set values.
*
* @method
* @param {number|null} minX - The minimum horizontal scale value this Game Object can scale down to.
* @param {number|null} minY - The minimum vertical scale value this Game Object can scale down to.
* @param {number|null} maxX - The maximum horizontal scale value this Game Object can scale up to.
* @param {number|null} maxY - The maximum vertical scale value this Game Object can scale up to.
*/
setScaleMinMax: function (minX, minY, maxX, maxY) {
if (minY === undefined)
{
// 1 parameter, set all to it
minY = maxX = maxY = minX;
}
else if (maxX === undefined)
{
// 2 parameters, the first is min, the second max
maxX = maxY = minY;
minY = minX;
}
if (minX === null)
{
this.scaleMin = null;
}
else
{
if (this.scaleMin)
{
this.scaleMin.set(minX, minY);
}
else
{
this.scaleMin = new Phaser.Point(minX, minY);
}
}
if (maxX === null)
{
this.scaleMax = null;
}
else
{
if (this.scaleMax)
{
this.scaleMax.set(maxX, maxY);
}
else
{
this.scaleMax = new Phaser.Point(maxX, maxY);
}
}
if (this.scaleMin === null)
{
this.transformCallback = null;
}
else
{
this.transformCallback = this.checkTransform;
this.transformCallbackContext = this;
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Smoothed component allows a Game Object to control anti-aliasing of an image based texture.
*
* @class
*/
Phaser.Component.Smoothed = function () {};
Phaser.Component.Smoothed.prototype = {
/**
* Enable or disable texture smoothing for this Game Object.
*
* It only takes effect if the Game Object is using an image based texture.
*
* Smoothing is enabled by default.
*
* @property {boolean} smoothed
*/
smoothed: {
get: function () {
return !this.texture.baseTexture.scaleMode;
},
set: function (value) {
if (value)
{
if (this.texture)
{
this.texture.baseTexture.scaleMode = 0;
}
}
else
{
if (this.texture)
{
this.texture.baseTexture.scaleMode = 1;
}
}
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The GameObjectFactory is a quick way to create many common game objects
* using {@linkcode Phaser.Game#add `game.add`}.
*
* Created objects are _automatically added_ to the appropriate Manager, World, or manually specified parent Group.
*
* @class Phaser.GameObjectFactory
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.GameObjectFactory = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
* @protected
*/
this.game = game;
/**
* @property {Phaser.World} world - A reference to the game world.
* @protected
*/
this.world = this.game.world;
};
Phaser.GameObjectFactory.prototype = {
/**
* Adds an existing display object to the game world.
*
* @method Phaser.GameObjectFactory#existing
* @param {any} object - An instance of Phaser.Sprite, Phaser.Button or any other display object.
* @return {any} The child that was added to the World.
*/
existing: function (object) {
return this.world.add(object);
},
/**
* Weapons provide the ability to easily create a bullet pool and manager.
*
* Weapons fire Phaser.Bullet objects, which are essentially Sprites with a few extra properties.
* The Bullets are enabled for Arcade Physics. They do not currently work with P2 Physics.
*
* The Bullets are created inside of `Weapon.bullets`, which is a 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.
*
* @method Phaser.GameObjectFactory#weapon
* @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|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - The image used as a texture by the bullets during rendering. If a string Phaser will get for an entry in the Image Cache. Or it can be an instance of a RenderTexture, BitmapData, Video or PIXI.Texture.
* @param {string|number} [frame] - If a Texture Atlas or Sprite Sheet is used this allows you to specify the frame to be used by the bullets. Use either an integer for a Frame ID or a string for a frame name.
* @param {Phaser.Group} [group] - Optional Group to add the Weapon to. If not specified it will be added to the World group.
* @returns {Phaser.Weapon} A Weapon instance.
*/
weapon: function (quantity, key, frame, group) {
var weapon = this.game.plugins.add(Phaser.Weapon);
weapon.createBullets(quantity, key, frame, group);
return weapon;
},
/**
* Create a new `Image` object.
*
* An Image is a light-weight object you can use to display anything that doesn't need physics or animation.
*
* It can still rotate, scale, crop and receive input events.
* This makes it perfect for logos, backgrounds, simple buttons and other non-Sprite graphics.
*
* @method Phaser.GameObjectFactory#image
* @param {number} [x=0] - The x coordinate of the Image. The coordinate is relative to any parent container this Image may be in.
* @param {number} [y=0] - The y coordinate of the Image. The coordinate is relative to any parent container this Image may be in.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - The image used as a texture by this display object during rendering. If a string Phaser will get for an entry in the Image Cache. Or it can be an instance of a RenderTexture, BitmapData, Video or PIXI.Texture.
* @param {string|number} [frame] - If a Texture Atlas or Sprite Sheet is used this allows you to specify the frame to be used. Use either an integer for a Frame ID or a string for a frame name.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @returns {Phaser.Image} The newly created Image object.
*/
image: function (x, y, key, frame, group) {
if (group === undefined) { group = this.world; }
return group.add(new Phaser.Image(this.game, x, y, key, frame));
},
/**
* Create a new Sprite with specific position and sprite sheet key.
*
* At its most basic a Sprite consists of a set of coordinates and a texture that is used when rendered.
* They also contain additional properties allowing for physics motion (via Sprite.body), input handling (via Sprite.input),
* events (via Sprite.events), animation (via Sprite.animations), camera culling and more. Please see the Examples for use cases.
*
* @method Phaser.GameObjectFactory#sprite
* @param {number} [x=0] - The x coordinate of the sprite. The coordinate is relative to any parent container this sprite may be in.
* @param {number} [y=0] - The y coordinate of the sprite. The coordinate is relative to any parent container this sprite may be in.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - The image used as a texture by this display object during rendering. If a string Phaser will get for an entry in the Image Cache. Or it can be an instance of a RenderTexture, BitmapData, Video or PIXI.Texture.
* @param {string|number} [frame] - If a Texture Atlas or Sprite Sheet is used this allows you to specify the frame to be used. Use either an integer for a Frame ID or a string for a frame name.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @returns {Phaser.Sprite} The newly created Sprite object.
*/
sprite: function (x, y, key, frame, group) {
if (group === undefined) { group = this.world; }
return group.create(x, y, key, frame);
},
/**
* Create a new Creature Animation object.
*
* Creature is a custom Game Object used in conjunction with the Creature Runtime libraries by Kestrel Moon Studios.
*
* It allows you to display animated Game Objects that were created with the [Creature Automated Animation Tool](http://www.kestrelmoon.com/creature/).
*
* Note 1: You can only use Phaser.Creature objects in WebGL enabled games. They do not work in Canvas mode games.
*
* Note 2: You must use a build of Phaser that includes the CreatureMeshBone.js runtime and gl-matrix.js, or have them
* loaded before your Phaser game boots.
*
* See the Phaser custom build process for more details.
*
* @method Phaser.GameObjectFactory#creature
* @param {number} [x=0] - The x coordinate of the creature. The coordinate is relative to any parent container this creature may be in.
* @param {number} [y=0] - The y coordinate of the creature. The coordinate is relative to any parent container this creature may be in.
* @param {string|PIXI.Texture} [key] - The image used as a texture by this creature object during rendering. If a string Phaser will get for an entry in the Image Cache. Or it can be an instance of a PIXI.Texture.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @returns {Phaser.Creature} The newly created Sprite object.
*/
creature: function (x, y, key, mesh, group) {
if (group === undefined) { group = this.world; }
var obj = new Phaser.Creature(this.game, x, y, key, mesh);
group.add(obj);
return obj;
},
/**
* Create a tween on a specific object.
*
* The object can be any JavaScript object or Phaser object such as Sprite.
*
* @method Phaser.GameObjectFactory#tween
* @param {object} object - Object the tween will be run on.
* @return {Phaser.Tween} The newly created Phaser.Tween object.
*/
tween: function (object) {
return this.game.tweens.create(object);
},
/**
* A Group is a container for display objects that allows for fast pooling, recycling and collision checks.
*
* @method Phaser.GameObjectFactory#group
* @param {any} [parent] - The parent Group or DisplayObjectContainer that will hold this group, if any. If set to null the Group won't be added to the display list. If undefined it will be added to World by default.
* @param {string} [name='group'] - A name for this Group. Not used internally but useful for debugging.
* @param {boolean} [addToStage=false] - If set to true this Group will be added directly to the Game.Stage instead of Game.World.
* @param {boolean} [enableBody=false] - If true all Sprites created with `Group.create` or `Group.createMulitple` will have a physics body created on them. Change the body type with physicsBodyType.
* @param {number} [physicsBodyType=0] - If enableBody is true this is the type of physics body that is created on new Sprites. Phaser.Physics.ARCADE, Phaser.Physics.P2, Phaser.Physics.NINJA, etc.
* @return {Phaser.Group} The newly created Group.
*/
group: function (parent, name, addToStage, enableBody, physicsBodyType) {
return new Phaser.Group(this.game, parent, name, addToStage, enableBody, physicsBodyType);
},
/**
* A Group is a container for display objects that allows for fast pooling, recycling and collision checks.
*
* A Physics Group is the same as an ordinary Group except that is has enableBody turned on by default, so any Sprites it creates
* are automatically given a physics body.
*
* @method Phaser.GameObjectFactory#physicsGroup
* @param {number} [physicsBodyType=Phaser.Physics.ARCADE] - If enableBody is true this is the type of physics body that is created on new Sprites. Phaser.Physics.ARCADE, Phaser.Physics.P2JS, Phaser.Physics.NINJA, etc.
* @param {any} [parent] - The parent Group or DisplayObjectContainer that will hold this group, if any. If set to null the Group won't be added to the display list. If undefined it will be added to World by default.
* @param {string} [name='group'] - A name for this Group. Not used internally but useful for debugging.
* @param {boolean} [addToStage=false] - If set to true this Group will be added directly to the Game.Stage instead of Game.World.
* @return {Phaser.Group} The newly created Group.
*/
physicsGroup: function (physicsBodyType, parent, name, addToStage) {
return new Phaser.Group(this.game, parent, name, addToStage, true, physicsBodyType);
},
/**
* A SpriteBatch is a really fast version of a Phaser Group built solely for speed.
* Use when you need a lot of sprites or particles all sharing the same texture.
* The speed gains are specifically for WebGL. In Canvas mode you won't see any real difference.
*
* @method Phaser.GameObjectFactory#spriteBatch
* @param {Phaser.Group|null} parent - The parent Group that will hold this Sprite Batch. Set to `undefined` or `null` to add directly to game.world.
* @param {string} [name='group'] - A name for this Sprite Batch. Not used internally but useful for debugging.
* @param {boolean} [addToStage=false] - If set to true this Sprite Batch will be added directly to the Game.Stage instead of the parent.
* @return {Phaser.SpriteBatch} The newly created Sprite Batch.
*/
spriteBatch: function (parent, name, addToStage) {
if (parent === undefined) { parent = null; }
if (name === undefined) { name = 'group'; }
if (addToStage === undefined) { addToStage = false; }
return new Phaser.SpriteBatch(this.game, parent, name, addToStage);
},
/**
* Creates a new Sound object.
*
* @method Phaser.GameObjectFactory#audio
* @param {string} key - The Game.cache key of the sound that this object will use.
* @param {number} [volume=1] - The volume at which the sound will be played.
* @param {boolean} [loop=false] - Whether or not the sound will loop.
* @param {boolean} [connect=true] - Controls if the created Sound object will connect to the master gainNode of the SoundManager when running under WebAudio.
* @return {Phaser.Sound} The newly created sound object.
*/
audio: function (key, volume, loop, connect) {
return this.game.sound.add(key, volume, loop, connect);
},
/**
* Creates a new Sound object.
*
* @method Phaser.GameObjectFactory#sound
* @param {string} key - The Game.cache key of the sound that this object will use.
* @param {number} [volume=1] - The volume at which the sound will be played.
* @param {boolean} [loop=false] - Whether or not the sound will loop.
* @param {boolean} [connect=true] - Controls if the created Sound object will connect to the master gainNode of the SoundManager when running under WebAudio.
* @return {Phaser.Sound} The newly created sound object.
*/
sound: function (key, volume, loop, connect) {
return this.game.sound.add(key, volume, loop, connect);
},
/**
* Creates a new AudioSprite object.
*
* @method Phaser.GameObjectFactory#audioSprite
* @param {string} key - The Game.cache key of the sound that this object will use.
* @return {Phaser.AudioSprite} The newly created AudioSprite object.
*/
audioSprite: function (key) {
return this.game.sound.addSprite(key);
},
/**
* Creates a new TileSprite object.
*
* @method Phaser.GameObjectFactory#tileSprite
* @param {number} x - The x coordinate of the TileSprite. The coordinate is relative to any parent container this TileSprite may be in.
* @param {number} y - The y coordinate of the TileSprite. The coordinate is relative to any parent container this TileSprite may be in.
* @param {number} width - The width of the TileSprite.
* @param {number} height - The height of the TileSprite.
* @param {string|Phaser.BitmapData|PIXI.Texture} key - This is the image or texture used by the TileSprite during rendering. It can be a string which is a reference to the Phaser Image Cache entry, or an instance of a PIXI.Texture or BitmapData.
* @param {string|number} [frame] - If a Texture Atlas or Sprite Sheet is used this allows you to specify the frame to be used. Use either an integer for a Frame ID or a string for a frame name.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.TileSprite} The newly created TileSprite object.
*/
tileSprite: function (x, y, width, height, key, frame, group) {
if (group === undefined) { group = this.world; }
return group.add(new Phaser.TileSprite(this.game, x, y, width, height, key, frame));
},
/**
* Creates a new Rope object.
*
* Example usage: https://github.com/codevinsky/phaser-rope-demo/blob/master/dist/demo.js
*
* @method Phaser.GameObjectFactory#rope
* @param {number} [x=0] - The x coordinate of the Rope. The coordinate is relative to any parent container this rope may be in.
* @param {number} [y=0] - The y coordinate of the Rope. The coordinate is relative to any parent container this rope may be in.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|Phaser.Video|PIXI.Texture} [key] - The image used as a texture by this display object during rendering. If a string Phaser will get for an entry in the Image Cache. Or it can be an instance of a RenderTexture, BitmapData, Video or PIXI.Texture.
* @param {string|number} [frame] - If a Texture Atlas or Sprite Sheet is used this allows you to specify the frame to be used. Use either an integer for a Frame ID or a string for a frame name.
* @param {Array} points - An array of {Phaser.Point}.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.Rope} The newly created Rope object.
*/
rope: function (x, y, key, frame, points, group) {
if (group === undefined) { group = this.world; }
return group.add(new Phaser.Rope(this.game, x, y, key, frame, points));
},
/**
* Creates a new Text object.
*
* @method Phaser.GameObjectFactory#text
* @param {number} [x=0] - The x coordinate of the Text. The coordinate is relative to any parent container this text may be in.
* @param {number} [y=0] - The y coordinate of the Text. The coordinate is relative to any parent container this text may be in.
* @param {string} [text=''] - The text string that will be displayed.
* @param {object} [style] - The style object containing style attributes like font, font size , etc.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.Text} The newly created text object.
*/
text: function (x, y, text, style, group) {
if (group === undefined) { group = this.world; }
return group.add(new Phaser.Text(this.game, x, y, text, style));
},
/**
* Creates a new Button object.
*
* @method Phaser.GameObjectFactory#button
* @param {number} [x=0] - The x coordinate of the Button. The coordinate is relative to any parent container this button may be in.
* @param {number} [y=0] - The y coordinate of the Button. The coordinate is relative to any parent container this button may be in.
* @param {string} [key] - The image key as defined in the Game.Cache to use as the texture for this button.
* @param {function} [callback] - The function to call when this button is pressed
* @param {object} [callbackContext] - The context in which the callback will be called (usually 'this')
* @param {string|number} [overFrame] - This is the frame or frameName that will be set when this button is in an over state. Give either a number to use a frame ID or a string for a frame name.
* @param {string|number} [outFrame] - This is the frame or frameName that will be set when this button is in an out state. Give either a number to use a frame ID or a string for a frame name.
* @param {string|number} [downFrame] - This is the frame or frameName that will be set when this button is in a down state. Give either a number to use a frame ID or a string for a frame name.
* @param {string|number} [upFrame] - This is the frame or frameName that will be set when this button is in an up state. Give either a number to use a frame ID or a string for a frame name.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.Button} The newly created Button object.
*/
button: function (x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame, group) {
if (group === undefined) { group = this.world; }
return group.add(new Phaser.Button(this.game, x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame));
},
/**
* Creates a new Graphics object.
*
* @method Phaser.GameObjectFactory#graphics
* @param {number} [x=0] - The x coordinate of the Graphic. The coordinate is relative to any parent container this object may be in.
* @param {number} [y=0] - The y coordinate of the Graphic. The coordinate is relative to any parent container this object may be in.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.Graphics} The newly created graphics object.
*/
graphics: function (x, y, group) {
if (group === undefined) { group = this.world; }
return group.add(new Phaser.Graphics(this.game, x, y));
},
/**
* Create a new Emitter.
*
* A particle emitter 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.
*
* @method Phaser.GameObjectFactory#emitter
* @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.
* @return {Phaser.Particles.Arcade.Emitter} The newly created emitter object.
*/
emitter: function (x, y, maxParticles) {
return this.game.particles.add(new Phaser.Particles.Arcade.Emitter(this.game, x, y, maxParticles));
},
/**
* Create a new RetroFont object.
*
* A RetroFont can be used as a texture for an Image or Sprite and optionally add it to the Cache.
* A RetroFont uses a bitmap which contains fixed with characters for the font set. You use character spacing to define the set.
* If you need variable width character support then use a BitmapText object instead. The main difference between a RetroFont and a BitmapText
* is that a RetroFont creates a single texture that you can apply to a game object, where-as a BitmapText creates one Sprite object per letter of text.
* The texture can be asssigned or one or multiple images/sprites, but note that the text the RetroFont uses will be shared across them all,
* i.e. if you need each Image to have different text in it, then you need to create multiple RetroFont objects.
*
* @method Phaser.GameObjectFactory#retroFont
* @param {string} font - The key of the image in the Game.Cache that the RetroFont will use.
* @param {number} characterWidth - The width of each character in the font set.
* @param {number} characterHeight - The height of each character in the font set.
* @param {string} chars - The characters used in the font set, in display order. You can use the TEXT_SET consts for common font set arrangements.
* @param {number} charsPerRow - The number of characters per row in the font set.
* @param {number} [xSpacing=0] - If the characters in the font set have horizontal spacing between them set the required amount here.
* @param {number} [ySpacing=0] - If the characters in the font set have vertical spacing between them set the required amount here.
* @param {number} [xOffset=0] - If the font set doesn't start at the top left of the given image, specify the X coordinate offset here.
* @param {number} [yOffset=0] - If the font set doesn't start at the top left of the given image, specify the Y coordinate offset here.
* @return {Phaser.RetroFont} The newly created RetroFont texture which can be applied to an Image or Sprite.
*/
retroFont: function (font, characterWidth, characterHeight, chars, charsPerRow, xSpacing, ySpacing, xOffset, yOffset) {
return new Phaser.RetroFont(this.game, font, characterWidth, characterHeight, chars, charsPerRow, xSpacing, ySpacing, xOffset, yOffset);
},
/**
* Create a new BitmapText object.
*
* BitmapText objects work by taking a texture file and an XML file that describes the font structure.
* It then generates a new Sprite object for each letter of the text, proportionally spaced out and aligned to
* match the font structure.
*
* BitmapText objects are less flexible than Text objects, in that they have less features such as shadows, fills and the ability
* to use Web Fonts. However you trade this flexibility for pure rendering speed. You can also create visually compelling BitmapTexts by
* processing the font texture in an image editor first, applying fills and any other effects required.
*
* To create multi-line text insert \r, \n or \r\n escape codes into the text string.
*
* To create a BitmapText data files you can use:
*
* BMFont (Windows, free): http://www.angelcode.com/products/bmfont/
* Glyph Designer (OS X, commercial): http://www.71squared.com/en/glyphdesigner
* Littera (Web-based, free): http://kvazars.com/littera/
*
* @method Phaser.GameObjectFactory#bitmapText
* @param {number} x - X coordinate to display the BitmapText object at.
* @param {number} y - Y coordinate to display the BitmapText object at.
* @param {string} font - The key of the BitmapText as stored in Phaser.Cache.
* @param {string} [text=''] - The text that will be rendered. This can also be set later via BitmapText.text.
* @param {number} [size=32] - The size the font will be rendered at in pixels.
* @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group.
* @return {Phaser.BitmapText} The newly created bitmapText object.
*/
bitmapText: function (x, y, font, text, size, group) {
if (group === undefined) { group = this.world; }
return group.add(new Phaser.BitmapText(this.game, x, y, font, text, size));
},
/**
* Creates a new Phaser.Tilemap object.
*
* The map can either be populated with data from a Tiled JSON file or from a CSV file.
* 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.
*
* @method Phaser.GameObjectFactory#tilemap
* @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.
* @return {Phaser.Tilemap} The newly created tilemap object.
*/
tilemap: function (key, tileWidth, tileHeight, width, height) {
return new Phaser.Tilemap(this.game, key, tileWidth, tileHeight, width, height);
},
/**
* A dynamic initially blank canvas to which images can be drawn.
*
* @method Phaser.GameObjectFactory#renderTexture
* @param {number} [width=100] - the width of the RenderTexture.
* @param {number} [height=100] - the height of the RenderTexture.
* @param {string} [key=''] - Asset key for the RenderTexture when stored in the Cache (see addToCache parameter).
* @param {boolean} [addToCache=false] - Should this RenderTexture be added to the Game.Cache? If so you can retrieve it with Cache.getTexture(key)
* @return {Phaser.RenderTexture} The newly created RenderTexture object.
*/
renderTexture: function (width, height, key, addToCache) {
if (key === undefined || key === '') { key = this.game.rnd.uuid(); }
if (addToCache === undefined) { addToCache = false; }
var texture = new Phaser.RenderTexture(this.game, width, height, key);
if (addToCache)
{
this.game.cache.addRenderTexture(key, texture);
}
return texture;
},
/**
* Create a Video object.
*
* This will return a Phaser.Video object which you can pass to a Sprite to be used as a texture.
*
* @method Phaser.GameObjectFactory#video
* @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)
* @return {Phaser.Video} The newly created Video object.
*/
video: function (key, url) {
return new Phaser.Video(this.game, key, url);
},
/**
* Create a BitmapData object.
*
* A BitmapData object can be manipulated and drawn to like a traditional Canvas object and used to texture Sprites.
*
* @method Phaser.GameObjectFactory#bitmapData
* @param {number} [width=256] - The width of the BitmapData in pixels.
* @param {number} [height=256] - The height of the BitmapData in pixels.
* @param {string} [key=''] - Asset key for the BitmapData when stored in the Cache (see addToCache parameter).
* @param {boolean} [addToCache=false] - Should this BitmapData be added to the Game.Cache? If so you can retrieve it with Cache.getBitmapData(key)
* @return {Phaser.BitmapData} The newly created BitmapData object.
*/
bitmapData: function (width, height, key, addToCache) {
if (addToCache === undefined) { addToCache = false; }
if (key === undefined || key === '') { key = this.game.rnd.uuid(); }
var texture = new Phaser.BitmapData(this.game, key, width, height);
if (addToCache)
{
this.game.cache.addBitmapData(key, texture);
}
return texture;
},
/**
* A WebGL shader/filter that can be applied to Sprites.
*
* @method Phaser.GameObjectFactory#filter
* @param {string} filter - The name of the filter you wish to create, for example HueRotate or SineWave.
* @param {any} - Whatever parameters are needed to be passed to the filter init function.
* @return {Phaser.Filter} The newly created Phaser.Filter object.
*/
filter: function (filter) {
var args = Array.prototype.slice.call(arguments, 1);
var filter = new Phaser.Filter[filter](this.game);
filter.init.apply(filter, args);
return filter;
},
/**
* Add a new Plugin into the PluginManager.
*
* The Plugin must have 2 properties: `game` and `parent`. Plugin.game is set to the game reference the PluginManager uses, and parent is set to the PluginManager.
*
* @method Phaser.GameObjectFactory#plugin
* @param {object|Phaser.Plugin} plugin - The Plugin to add into the PluginManager. This can be a function or an existing object.
* @param {...*} parameter - Additional parameters that will be passed to the Plugin.init method.
* @return {Phaser.Plugin} The Plugin that was added to the manager.
*/
plugin: function (plugin) {
return this.game.plugins.add(plugin);
}
};
Phaser.GameObjectFactory.prototype.constructor = Phaser.GameObjectFactory;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The GameObjectCreator is a quick way to create common game objects _without_ adding them to the game world.
* The object creator can be accessed with {@linkcode Phaser.Game#make `game.make`}.
*
* @class Phaser.GameObjectCreator
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.GameObjectCreator = function (game) {
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
* @protected
*/
this.game = game;
/**
* @property {Phaser.World} world - A reference to the game world.
* @protected
*/
this.world = this.game.world;
};
Phaser.GameObjectCreator.prototype = {
/**
* Create a new Image object.
*
* An Image is a light-weight object you can use to display anything that doesn't need physics or animation.
* It can still rotate, scale, crop and receive input events. This makes it perfect for logos, backgrounds, simple buttons and other non-Sprite graphics.
*
* @method Phaser.GameObjectCreator#image
* @param {number} x - X position of the image.
* @param {number} y - Y position of the image.
* @param {string|Phaser.RenderTexture|PIXI.Texture} key - This is the image or texture used by the Sprite 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 the sprite uses an image from a texture atlas or sprite sheet you can pass the frame here. Either a number for a frame ID or a string for a frame name.
* @returns {Phaser.Image} the newly created sprite object.
*/
image: function (x, y, key, frame) {
return new Phaser.Image(this.game, x, y, key, frame);
},
/**
* Create a new Sprite with specific position and sprite sheet key.
*
* @method Phaser.GameObjectCreator#sprite
* @param {number} x - X position of the new sprite.
* @param {number} y - Y position of the new sprite.
* @param {string|Phaser.RenderTexture|PIXI.Texture} key - This is the image or texture used by the Sprite 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 the sprite uses an image from a texture atlas or sprite sheet you can pass the frame here. Either a number for a frame ID or a string for a frame name.
* @returns {Phaser.Sprite} the newly created sprite object.
*/
sprite: function (x, y, key, frame) {
return new Phaser.Sprite(this.game, x, y, key, frame);
},
/**
* Create a tween object for a specific object.
*
* The object can be any JavaScript object or Phaser object such as Sprite.
*
* @method Phaser.GameObjectCreator#tween
* @param {object} obj - Object the tween will be run on.
* @return {Phaser.Tween} The Tween object.
*/
tween: function (obj) {
return new Phaser.Tween(obj, this.game, this.game.tweens);
},
/**
* A Group is a container for display objects that allows for fast pooling, recycling and collision checks.
*
* @method Phaser.GameObjectCreator#group
* @param {any} parent - The parent Group or DisplayObjectContainer that will hold this group, if any.
* @param {string} [name='group'] - A name for this Group. Not used internally but useful for debugging.
* @param {boolean} [addToStage=false] - If set to true this Group will be added directly to the Game.Stage instead of Game.World.
* @param {boolean} [enableBody=false] - If true all Sprites created with `Group.create` or `Group.createMulitple` will have a physics body created on them. Change the body type with physicsBodyType.
* @param {number} [physicsBodyType=0] - If enableBody is true this is the type of physics body that is created on new Sprites. Phaser.Physics.ARCADE, Phaser.Physics.P2, Phaser.Physics.NINJA, etc.
* @return {Phaser.Group} The newly created Group.
*/
group: function (parent, name, addToStage, enableBody, physicsBodyType) {
return new Phaser.Group(this.game, parent, name, addToStage, enableBody, physicsBodyType);
},
/**
* Create a new SpriteBatch.
*
* @method Phaser.GameObjectCreator#spriteBatch
* @param {any} parent - The parent Group or DisplayObjectContainer that will hold this group, if any.
* @param {string} [name='group'] - A name for this Group. Not used internally but useful for debugging.
* @param {boolean} [addToStage=false] - If set to true this Group will be added directly to the Game.Stage instead of Game.World.
* @return {Phaser.SpriteBatch} The newly created group.
*/
spriteBatch: function (parent, name, addToStage) {
if (name === undefined) { name = 'group'; }
if (addToStage === undefined) { addToStage = false; }
return new Phaser.SpriteBatch(this.game, parent, name, addToStage);
},
/**
* Creates a new Sound object.
*
* @method Phaser.GameObjectCreator#audio
* @param {string} key - The Game.cache key of the sound that this object will use.
* @param {number} [volume=1] - The volume at which the sound will be played.
* @param {boolean} [loop=false] - Whether or not the sound will loop.
* @param {boolean} [connect=true] - Controls if the created Sound object will connect to the master gainNode of the SoundManager when running under WebAudio.
* @return {Phaser.Sound} The newly created text object.
*/
audio: function (key, volume, loop, connect) {
return this.game.sound.add(key, volume, loop, connect);
},
/**
* Creates a new AudioSprite object.
*
* @method Phaser.GameObjectCreator#audioSprite
* @param {string} key - The Game.cache key of the sound that this object will use.
* @return {Phaser.AudioSprite} The newly created AudioSprite object.
*/
audioSprite: function (key) {
return this.game.sound.addSprite(key);
},
/**
* Creates a new Sound object.
*
* @method Phaser.GameObjectCreator#sound
* @param {string} key - The Game.cache key of the sound that this object will use.
* @param {number} [volume=1] - The volume at which the sound will be played.
* @param {boolean} [loop=false] - Whether or not the sound will loop.
* @param {boolean} [connect=true] - Controls if the created Sound object will connect to the master gainNode of the SoundManager when running under WebAudio.
* @return {Phaser.Sound} The newly created text object.
*/
sound: function (key, volume, loop, connect) {
return this.game.sound.add(key, volume, loop, connect);
},
/**
* Creates a new TileSprite object.
*
* @method Phaser.GameObjectCreator#tileSprite
* @param {number} x - The x coordinate (in world space) to position the TileSprite at.
* @param {number} y - The y coordinate (in world space) to position the TileSprite at.
* @param {number} width - The width of the TileSprite.
* @param {number} height - The height of the TileSprite.
* @param {string|Phaser.BitmapData|PIXI.Texture} key - This is the image or texture used by the TileSprite during rendering. It can be a string which is a reference to the Phaser Image Cache entry, or an instance of a PIXI.Texture or BitmapData.
* @param {string|number} frame - If this TileSprite 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 {Phaser.TileSprite} The newly created tileSprite object.
*/
tileSprite: function (x, y, width, height, key, frame) {
return new Phaser.TileSprite(this.game, x, y, width, height, key, frame);
},
/**
* Creates a new Rope object.
*
* @method Phaser.GameObjectCreator#rope
* @param {number} x - The x coordinate (in world space) to position the Rope at.
* @param {number} y - The y coordinate (in world space) to position the Rope at.
* @param {number} width - The width of the Rope.
* @param {number} height - The height of the Rope.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|PIXI.Texture} key - This is the image or texture used by the TileSprite 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 Rope 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 {Phaser.Rope} The newly created rope object.
*/
rope: function (x, y, key, frame, points) {
return new Phaser.Rope(this.game, x, y, key, frame, points);
},
/**
* Creates a new Text object.
*
* @method Phaser.GameObjectCreator#text
* @param {number} x - X position of the new text object.
* @param {number} y - Y position of the new text object.
* @param {string} text - The actual text that will be written.
* @param {object} style - The style object containing style attributes like font, font size , etc.
* @return {Phaser.Text} The newly created text object.
*/
text: function (x, y, text, style) {
return new Phaser.Text(this.game, x, y, text, style);
},
/**
* Creates a new Button object.
*
* @method Phaser.GameObjectCreator#button
* @param {number} [x] X position of the new button object.
* @param {number} [y] Y position of the new button object.
* @param {string} [key] The image key as defined in the Game.Cache to use as the texture for this button.
* @param {function} [callback] The function to call when this button is pressed
* @param {object} [callbackContext] The context in which the callback will be called (usually 'this')
* @param {string|number} [overFrame] This is the frame or frameName that will be set when this button is in an over state. Give either a number to use a frame ID or a string for a frame name.
* @param {string|number} [outFrame] This is the frame or frameName that will be set when this button is in an out state. Give either a number to use a frame ID or a string for a frame name.
* @param {string|number} [downFrame] This is the frame or frameName that will be set when this button is in a down state. Give either a number to use a frame ID or a string for a frame name.
* @param {string|number} [upFrame] This is the frame or frameName that will be set when this button is in an up state. Give either a number to use a frame ID or a string for a frame name.
* @return {Phaser.Button} The newly created button object.
*/
button: function (x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame) {
return new Phaser.Button(this.game, x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame);
},
/**
* Creates a new Graphics object.
*
* @method Phaser.GameObjectCreator#graphics
* @param {number} [x=0] - X position of the new graphics object.
* @param {number} [y=0] - Y position of the new graphics object.
* @return {Phaser.Graphics} The newly created graphics object.
*/
graphics: function (x, y) {
return new Phaser.Graphics(this.game, x, y);
},
/**
* Creat a new Emitter.
*
* An Emitter is a lightweight particle emitter. 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 accorindgly.
*
* @method Phaser.GameObjectCreator#emitter
* @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.
* @return {Phaser.Emitter} The newly created emitter object.
*/
emitter: function (x, y, maxParticles) {
return new Phaser.Particles.Arcade.Emitter(this.game, x, y, maxParticles);
},
/**
* Create a new RetroFont object.
*
* A RetroFont can be used as a texture for an Image or Sprite and optionally add it to the Cache.
* A RetroFont uses a bitmap which contains fixed with characters for the font set. You use character spacing to define the set.
* If you need variable width character support then use a BitmapText object instead. The main difference between a RetroFont and a BitmapText
* is that a RetroFont creates a single texture that you can apply to a game object, where-as a BitmapText creates one Sprite object per letter of text.
* The texture can be asssigned or one or multiple images/sprites, but note that the text the RetroFont uses will be shared across them all,
* i.e. if you need each Image to have different text in it, then you need to create multiple RetroFont objects.
*
* @method Phaser.GameObjectCreator#retroFont
* @param {string} font - The key of the image in the Game.Cache that the RetroFont will use.
* @param {number} characterWidth - The width of each character in the font set.
* @param {number} characterHeight - The height of each character in the font set.
* @param {string} chars - The characters used in the font set, in display order. You can use the TEXT_SET consts for common font set arrangements.
* @param {number} charsPerRow - The number of characters per row in the font set.
* @param {number} [xSpacing=0] - If the characters in the font set have horizontal spacing between them set the required amount here.
* @param {number} [ySpacing=0] - If the characters in the font set have vertical spacing between them set the required amount here.
* @param {number} [xOffset=0] - If the font set doesn't start at the top left of the given image, specify the X coordinate offset here.
* @param {number} [yOffset=0] - If the font set doesn't start at the top left of the given image, specify the Y coordinate offset here.
* @return {Phaser.RetroFont} The newly created RetroFont texture which can be applied to an Image or Sprite.
*/
retroFont: function (font, characterWidth, characterHeight, chars, charsPerRow, xSpacing, ySpacing, xOffset, yOffset) {
return new Phaser.RetroFont(this.game, font, characterWidth, characterHeight, chars, charsPerRow, xSpacing, ySpacing, xOffset, yOffset);
},
/**
* Create a new BitmapText object.
*
* BitmapText objects work by taking a texture file and an XML file that describes the font structure.
* It then generates a new Sprite object for each letter of the text, proportionally spaced out and aligned to
* match the font structure.
*
* BitmapText objects are less flexible than Text objects, in that they have less features such as shadows, fills and the ability
* to use Web Fonts. However you trade this flexibility for pure rendering speed. You can also create visually compelling BitmapTexts by
* processing the font texture in an image editor first, applying fills and any other effects required.
*
* To create multi-line text insert \r, \n or \r\n escape codes into the text string.
*
* To create a BitmapText data files you can use:
*
* BMFont (Windows, free): http://www.angelcode.com/products/bmfont/
* Glyph Designer (OS X, commercial): http://www.71squared.com/en/glyphdesigner
* Littera (Web-based, free): http://kvazars.com/littera/
*
* @method Phaser.GameObjectCreator#bitmapText
* @param {number} x - X coordinate to display the BitmapText object at.
* @param {number} y - Y coordinate to display the BitmapText object at.
* @param {string} font - The key of the BitmapText as stored in Phaser.Cache.
* @param {string} [text=''] - The text that will be rendered. This can also be set later via BitmapText.text.
* @param {number} [size=32] - The size the font will be rendered at in pixels.
* @param {string} [align='left'] - The alignment of multi-line text. Has no effect if there is only one line of text.
* @return {Phaser.BitmapText} The newly created bitmapText object.
*/
bitmapText: function (x, y, font, text, size, align) {
return new Phaser.BitmapText(this.game, x, y, font, text, size, align);
},
/**
* Creates a new Phaser.Tilemap object.
*
* The map can either be populated with data from a Tiled JSON file or from a CSV file.
* 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.
*
* @method Phaser.GameObjectCreator#tilemap
* @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.
*/
tilemap: function (key, tileWidth, tileHeight, width, height) {
return new Phaser.Tilemap(this.game, key, tileWidth, tileHeight, width, height);
},
/**
* A dynamic initially blank canvas to which images can be drawn.
*
* @method Phaser.GameObjectCreator#renderTexture
* @param {number} [width=100] - the width of the RenderTexture.
* @param {number} [height=100] - the height of the RenderTexture.
* @param {string} [key=''] - Asset key for the RenderTexture when stored in the Cache (see addToCache parameter).
* @param {boolean} [addToCache=false] - Should this RenderTexture be added to the Game.Cache? If so you can retrieve it with Cache.getTexture(key)
* @return {Phaser.RenderTexture} The newly created RenderTexture object.
*/
renderTexture: function (width, height, key, addToCache) {
if (key === undefined || key === '') { key = this.game.rnd.uuid(); }
if (addToCache === undefined) { addToCache = false; }
var texture = new Phaser.RenderTexture(this.game, width, height, key);
if (addToCache)
{
this.game.cache.addRenderTexture(key, texture);
}
return texture;
},
/**
* Create a BitmpaData object.
*
* A BitmapData object can be manipulated and drawn to like a traditional Canvas object and used to texture Sprites.
*
* @method Phaser.GameObjectCreator#bitmapData
* @param {number} [width=256] - The width of the BitmapData in pixels.
* @param {number} [height=256] - The height of the BitmapData in pixels.
* @param {string} [key=''] - Asset key for the BitmapData when stored in the Cache (see addToCache parameter).
* @param {boolean} [addToCache=false] - Should this BitmapData be added to the Game.Cache? If so you can retrieve it with Cache.getBitmapData(key)
* @return {Phaser.BitmapData} The newly created BitmapData object.
*/
bitmapData: function (width, height, key, addToCache) {
if (addToCache === undefined) { addToCache = false; }
if (key === undefined || key === '') { key = this.game.rnd.uuid(); }
var texture = new Phaser.BitmapData(this.game, key, width, height);
if (addToCache)
{
this.game.cache.addBitmapData(key, texture);
}
return texture;
},
/**
* A WebGL shader/filter that can be applied to Sprites.
*
* @method Phaser.GameObjectCreator#filter
* @param {string} filter - The name of the filter you wish to create, for example HueRotate or SineWave.
* @param {any} - Whatever parameters are needed to be passed to the filter init function.
* @return {Phaser.Filter} The newly created Phaser.Filter object.
*/
filter: function (filter) {
var args = Array.prototype.slice.call(arguments, 1);
var filter = new Phaser.Filter[filter](this.game);
filter.init.apply(filter, args);
return filter;
}
};
Phaser.GameObjectCreator.prototype.constructor = Phaser.GameObjectCreator;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Sprites are the lifeblood of your game, used for nearly everything visual.
*
* At its most basic a Sprite consists of a set of coordinates and a texture that is rendered to the canvas.
* They also contain additional properties allowing for physics motion (via Sprite.body), input handling (via Sprite.input),
* events (via Sprite.events), animation (via Sprite.animations), camera culling and more. Please see the Examples for use cases.
*
* @class Phaser.Sprite
* @constructor
* @extends PIXI.Sprite
* @extends Phaser.Component.Core
* @extends Phaser.Component.Angle
* @extends Phaser.Component.Animation
* @extends Phaser.Component.AutoCull
* @extends Phaser.Component.Bounds
* @extends Phaser.Component.BringToTop
* @extends Phaser.Component.Crop
* @extends Phaser.Component.Delta
* @extends Phaser.Component.Destroy
* @extends Phaser.Component.FixedToCamera
* @extends Phaser.Component.Health
* @extends Phaser.Component.InCamera
* @extends Phaser.Component.InputEnabled
* @extends Phaser.Component.InWorld
* @extends Phaser.Component.LifeSpan
* @extends Phaser.Component.LoadTexture
* @extends Phaser.Component.Overlap
* @extends Phaser.Component.PhysicsBody
* @extends Phaser.Component.Reset
* @extends Phaser.Component.ScaleMinMax
* @extends Phaser.Component.Smoothed
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {number} x - The x coordinate (in world space) to position the Sprite at.
* @param {number} y - The y coordinate (in world space) to position the Sprite at.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|PIXI.Texture} key - This is the image or texture used by the Sprite 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 Sprite 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.Sprite = function (game, x, y, key, frame) {
x = x || 0;
y = y || 0;
key = key || null;
frame = frame || null;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.SPRITE;
/**
* @property {number} physicsType - The const physics body type of this object.
* @readonly
*/
this.physicsType = Phaser.SPRITE;
PIXI.Sprite.call(this, Phaser.Cache.DEFAULT);
Phaser.Component.Core.init.call(this, game, x, y, key, frame);
};
Phaser.Sprite.prototype = Object.create(PIXI.Sprite.prototype);
Phaser.Sprite.prototype.constructor = Phaser.Sprite;
Phaser.Component.Core.install.call(Phaser.Sprite.prototype, [
'Angle',
'Animation',
'AutoCull',
'Bounds',
'BringToTop',
'Crop',
'Delta',
'Destroy',
'FixedToCamera',
'Health',
'InCamera',
'InputEnabled',
'InWorld',
'LifeSpan',
'LoadTexture',
'Overlap',
'PhysicsBody',
'Reset',
'ScaleMinMax',
'Smoothed'
]);
Phaser.Sprite.prototype.preUpdatePhysics = Phaser.Component.PhysicsBody.preUpdate;
Phaser.Sprite.prototype.preUpdateLifeSpan = Phaser.Component.LifeSpan.preUpdate;
Phaser.Sprite.prototype.preUpdateInWorld = Phaser.Component.InWorld.preUpdate;
Phaser.Sprite.prototype.preUpdateCore = Phaser.Component.Core.preUpdate;
/**
* Automatically called by World.preUpdate.
*
* @method
* @memberof Phaser.Sprite
* @return {boolean} True if the Sprite was rendered, otherwise false.
*/
Phaser.Sprite.prototype.preUpdate = function () {
if (!this.preUpdatePhysics() || !this.preUpdateLifeSpan() || !this.preUpdateInWorld())
{
return false;
}
return this.preUpdateCore();
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* An Image is a light-weight object you can use to display anything that doesn't need physics or animation.
* It can still rotate, scale, crop and receive input events. This makes it perfect for logos, backgrounds, simple buttons and other non-Sprite graphics.
*
* @class Phaser.Image
* @extends PIXI.Sprite
* @extends Phaser.Component.Core
* @extends Phaser.Component.Angle
* @extends Phaser.Component.Animation
* @extends Phaser.Component.AutoCull
* @extends Phaser.Component.Bounds
* @extends Phaser.Component.BringToTop
* @extends Phaser.Component.Crop
* @extends Phaser.Component.Destroy
* @extends Phaser.Component.FixedToCamera
* @extends Phaser.Component.InputEnabled
* @extends Phaser.Component.LifeSpan
* @extends Phaser.Component.LoadTexture
* @extends Phaser.Component.Overlap
* @extends Phaser.Component.Reset
* @extends Phaser.Component.ScaleMinMax
* @extends Phaser.Component.Smoothed
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {number} [x=0] - The x coordinate of the Image. The coordinate is relative to any parent container this Image may be in.
* @param {number} [y=0] - The y coordinate of the Image. The coordinate is relative to any parent container this Image may be in.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|PIXI.Texture} [key] - The texture used by the Image during rendering. It can be a string which is a reference to the Cache entry, or an instance of a RenderTexture, BitmapData or PIXI.Texture.
* @param {string|number} [frame] - If this Image 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.Image = function (game, x, y, key, frame) {
x = x || 0;
y = y || 0;
key = key || null;
frame = frame || null;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.IMAGE;
PIXI.Sprite.call(this, Phaser.Cache.DEFAULT);
Phaser.Component.Core.init.call(this, game, x, y, key, frame);
};
Phaser.Image.prototype = Object.create(PIXI.Sprite.prototype);
Phaser.Image.prototype.constructor = Phaser.Image;
Phaser.Component.Core.install.call(Phaser.Image.prototype, [
'Angle',
'Animation',
'AutoCull',
'Bounds',
'BringToTop',
'Crop',
'Destroy',
'FixedToCamera',
'InputEnabled',
'LifeSpan',
'LoadTexture',
'Overlap',
'Reset',
'ScaleMinMax',
'Smoothed'
]);
Phaser.Image.prototype.preUpdateInWorld = Phaser.Component.InWorld.preUpdate;
Phaser.Image.prototype.preUpdateCore = Phaser.Component.Core.preUpdate;
/**
* Automatically called by World.preUpdate.
*
* @method Phaser.Image#preUpdate
* @memberof Phaser.Image
*/
Phaser.Image.prototype.preUpdate = function() {
if (!this.preUpdateInWorld())
{
return false;
}
return this.preUpdateCore();
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Create a new `Button` object. A Button is a special type of Sprite that is set-up to handle Pointer events automatically.
*
* The four states a Button responds to are:
*
* * 'Over' - when the Pointer moves over the Button. This is also commonly known as 'hover'.
* * 'Out' - when the Pointer that was previously over the Button moves out of it.
* * 'Down' - when the Pointer is pressed down on the Button. I.e. touched on a touch enabled device or clicked with the mouse.
* * 'Up' - when the Pointer that was pressed down on the Button is released again.
*
* A different texture/frame and activation sound can be specified for any of the states.
*
* Frames can be specified as either an integer (the frame ID) or a string (the frame name); the same values that can be used with a Sprite constructor.
*
* @class Phaser.Button
* @constructor
* @extends Phaser.Image
* @param {Phaser.Game} game Current game instance.
* @param {number} [x=0] - X position of the Button.
* @param {number} [y=0] - Y position of the Button.
* @param {string} [key] - The image key (in the Game.Cache) to use as the texture for this Button.
* @param {function} [callback] - The function to call when this Button is pressed.
* @param {object} [callbackContext] - The context in which the callback will be called (usually 'this').
* @param {string|integer} [overFrame] - The frame / frameName when the button is in the Over state.
* @param {string|integer} [outFrame] - The frame / frameName when the button is in the Out state.
* @param {string|integer} [downFrame] - The frame / frameName when the button is in the Down state.
* @param {string|integer} [upFrame] - The frame / frameName when the button is in the Up state.
*/
Phaser.Button = function (game, x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame) {
x = x || 0;
y = y || 0;
key = key || null;
callback = callback || null;
callbackContext = callbackContext || this;
Phaser.Image.call(this, game, x, y, key, outFrame);
/**
* The Phaser Object Type.
* @property {number} type
* @readonly
*/
this.type = Phaser.BUTTON;
/**
* @property {number} physicsType - The const physics body type of this object.
* @readonly
*/
this.physicsType = Phaser.SPRITE;
/**
* The name or ID of the Over state frame.
* @property {string|integer} onOverFrame
* @private
*/
this._onOverFrame = null;
/**
* The name or ID of the Out state frame.
* @property {string|integer} onOutFrame
* @private
*/
this._onOutFrame = null;
/**
* The name or ID of the Down state frame.
* @property {string|integer} onDownFrame
* @private
*/
this._onDownFrame = null;
/**
* The name or ID of the Up state frame.
* @property {string|integer} onUpFrame
* @private
*/
this._onUpFrame = null;
/**
* The Sound to be played when this Buttons Over state is activated.
* @property {Phaser.Sound|Phaser.AudioSprite|null} onOverSound
* @readonly
*/
this.onOverSound = null;
/**
* The Sound to be played when this Buttons Out state is activated.
* @property {Phaser.Sound|Phaser.AudioSprite|null} onOutSound
* @readonly
*/
this.onOutSound = null;
/**
* The Sound to be played when this Buttons Down state is activated.
* @property {Phaser.Sound|Phaser.AudioSprite|null} onDownSound
* @readonly
*/
this.onDownSound = null;
/**
* The Sound to be played when this Buttons Up state is activated.
* @property {Phaser.Sound|Phaser.AudioSprite|null} onUpSound
* @readonly
*/
this.onUpSound = null;
/**
* The Sound Marker used in conjunction with the onOverSound.
* @property {string} onOverSoundMarker
* @readonly
*/
this.onOverSoundMarker = '';
/**
* The Sound Marker used in conjunction with the onOutSound.
* @property {string} onOutSoundMarker
* @readonly
*/
this.onOutSoundMarker = '';
/**
* The Sound Marker used in conjunction with the onDownSound.
* @property {string} onDownSoundMarker
* @readonly
*/
this.onDownSoundMarker = '';
/**
* The Sound Marker used in conjunction with the onUpSound.
* @property {string} onUpSoundMarker
* @readonly
*/
this.onUpSoundMarker = '';
/**
* The Signal (or event) dispatched when this Button is in an Over state.
* @property {Phaser.Signal} onInputOver
*/
this.onInputOver = new Phaser.Signal();
/**
* The Signal (or event) dispatched when this Button is in an Out state.
* @property {Phaser.Signal} onInputOut
*/
this.onInputOut = new Phaser.Signal();
/**
* The Signal (or event) dispatched when this Button is in an Down state.
* @property {Phaser.Signal} onInputDown
*/
this.onInputDown = new Phaser.Signal();
/**
* The Signal (or event) dispatched when this Button is in an Up state.
* @property {Phaser.Signal} onInputUp
*/
this.onInputUp = new Phaser.Signal();
/**
* If true then onOver events (such as onOverSound) will only be triggered if the Pointer object causing them was the Mouse Pointer.
* The frame will still be changed as applicable.
*
* @property {boolean} onOverMouseOnly
* @default
*/
this.onOverMouseOnly = true;
/**
* Suppress the over event if a pointer was just released and it matches the given {@link Phaser.PointerModer pointer mode bitmask}.
*
* This behavior was introduced in Phaser 2.3.1; this property is a soft-revert of the change.
*
* @property {Phaser.PointerMode?} justReleasedPreventsOver=ACTIVE_CURSOR
*/
this.justReleasedPreventsOver = Phaser.PointerMode.TOUCH;
/**
* When true the the texture frame will not be automatically switched on up/down/over/out events.
* @property {boolean} freezeFrames
* @default
*/
this.freezeFrames = false;
/**
* When the Button is touched / clicked and then released you can force it to enter a state of "out" instead of "up".
*
* This can also accept a {@link Phaser.PointerModer pointer mode bitmask} for more refined control.
*
* @property {boolean|Phaser.PointerMode} forceOut=false
* @default
*/
this.forceOut = false;
this.inputEnabled = true;
this.input.start(0, true);
this.input.useHandCursor = true;
this.setFrames(overFrame, outFrame, downFrame, upFrame);
if (callback !== null)
{
this.onInputUp.add(callback, callbackContext);
}
// Redirect the input events to here so we can handle animation updates, etc
this.events.onInputOver.add(this.onInputOverHandler, this);
this.events.onInputOut.add(this.onInputOutHandler, this);
this.events.onInputDown.add(this.onInputDownHandler, this);
this.events.onInputUp.add(this.onInputUpHandler, this);
this.events.onRemovedFromWorld.add(this.removedFromWorld, this);
};
Phaser.Button.prototype = Object.create(Phaser.Image.prototype);
Phaser.Button.prototype.constructor = Phaser.Button;
// State constants; local only. These are tied to property names in Phaser.Button.
var STATE_OVER = 'Over';
var STATE_OUT = 'Out';
var STATE_DOWN = 'Down';
var STATE_UP = 'Up';
/**
* Clears all of the frames set on this Button.
*
* @method Phaser.Button#clearFrames
*/
Phaser.Button.prototype.clearFrames = function () {
this.setFrames(null, null, null, null);
};
/**
* Called when this Button is removed from the World.
*
* @method Phaser.Button#removedFromWorld
* @protected
*/
Phaser.Button.prototype.removedFromWorld = function () {
this.inputEnabled = false;
};
/**
* Set the frame name/ID for the given state.
*
* @method Phaser.Button#setStateFrame
* @private
* @param {object} state - See `STATE_*`
* @param {number|string} frame - The number or string representing the frame.
* @param {boolean} switchImmediately - Immediately switch to the frame if it was set - and this is true.
*/
Phaser.Button.prototype.setStateFrame = function (state, frame, switchImmediately)
{
var frameKey = '_on' + state + 'Frame';
if (frame !== null) // not null or undefined
{
this[frameKey] = frame;
if (switchImmediately)
{
this.changeStateFrame(state);
}
}
else
{
this[frameKey] = null;
}
};
/**
* Change the frame to that of the given state, _if_ the state has a frame assigned _and_ if the frames are not currently "frozen".
*
* @method Phaser.Button#changeStateFrame
* @private
* @param {object} state - See `STATE_*`
* @return {boolean} True only if the frame was assigned a value, possibly the same one it already had.
*/
Phaser.Button.prototype.changeStateFrame = function (state) {
if (this.freezeFrames)
{
return false;
}
var frameKey = '_on' + state + 'Frame';
var frame = this[frameKey];
if (typeof frame === 'string')
{
this.frameName = frame;
return true;
}
else if (typeof frame === 'number')
{
this.frame = frame;
return true;
}
else
{
return false;
}
};
/**
* Used to manually set the frames that will be used for the different states of the Button.
*
* Frames can be specified as either an integer (the frame ID) or a string (the frame name); these are the same values that can be used with a Sprite constructor.
*
* @method Phaser.Button#setFrames
* @public
* @param {string|integer} [overFrame] - The frame / frameName when the button is in the Over state.
* @param {string|integer} [outFrame] - The frame / frameName when the button is in the Out state.
* @param {string|integer} [downFrame] - The frame / frameName when the button is in the Down state.
* @param {string|integer} [upFrame] - The frame / frameName when the button is in the Up state.
*/
Phaser.Button.prototype.setFrames = function (overFrame, outFrame, downFrame, upFrame) {
this.setStateFrame(STATE_OVER, overFrame, this.input.pointerOver());
this.setStateFrame(STATE_OUT, outFrame, !this.input.pointerOver());
this.setStateFrame(STATE_DOWN, downFrame, this.input.pointerDown());
this.setStateFrame(STATE_UP, upFrame, this.input.pointerUp());
};
/**
* Set the sound/marker for the given state.
*
* @method Phaser.Button#setStateSound
* @private
* @param {object} state - See `STATE_*`
* @param {Phaser.Sound|Phaser.AudioSprite} [sound] - Sound.
* @param {string} [marker=''] - Sound marker.
*/
Phaser.Button.prototype.setStateSound = function (state, sound, marker) {
var soundKey = 'on' + state + 'Sound';
var markerKey = 'on' + state + 'SoundMarker';
if (sound instanceof Phaser.Sound || sound instanceof Phaser.AudioSprite)
{
this[soundKey] = sound;
this[markerKey] = typeof marker === 'string' ? marker : '';
}
else
{
this[soundKey] = null;
this[markerKey] = '';
}
};
/**
* Play the sound for the given state, _if_ the state has a sound assigned.
*
* @method Phaser.Button#playStateSound
* @private
* @param {object} state - See `STATE_*`
* @return {boolean} True only if a sound was played.
*/
Phaser.Button.prototype.playStateSound = function (state) {
var soundKey = 'on' + state + 'Sound';
var sound = this[soundKey];
if (sound)
{
var markerKey = 'on' + state + 'SoundMarker';
var marker = this[markerKey];
sound.play(marker);
return true;
}
else
{
return false;
}
};
/**
* Sets the sounds to be played whenever this Button is interacted with. Sounds can be either full Sound objects, or markers pointing to a section of a Sound object.
* The most common forms of sounds are 'hover' effects and 'click' effects, which is why the order of the parameters is overSound then downSound.
*
* Call this function with no parameters to reset all sounds on this Button.
*
* @method Phaser.Button#setSounds
* @public
* @param {Phaser.Sound|Phaser.AudioSprite} [overSound] - Over Button Sound.
* @param {string} [overMarker] - Over Button Sound Marker.
* @param {Phaser.Sound|Phaser.AudioSprite} [downSound] - Down Button Sound.
* @param {string} [downMarker] - Down Button Sound Marker.
* @param {Phaser.Sound|Phaser.AudioSprite} [outSound] - Out Button Sound.
* @param {string} [outMarker] - Out Button Sound Marker.
* @param {Phaser.Sound|Phaser.AudioSprite} [upSound] - Up Button Sound.
* @param {string} [upMarker] - Up Button Sound Marker.
*/
Phaser.Button.prototype.setSounds = function (overSound, overMarker, downSound, downMarker, outSound, outMarker, upSound, upMarker) {
this.setStateSound(STATE_OVER, overSound, overMarker);
this.setStateSound(STATE_OUT, outSound, outMarker);
this.setStateSound(STATE_DOWN, downSound, downMarker);
this.setStateSound(STATE_UP, upSound, upMarker);
};
/**
* The Sound to be played when a Pointer moves over this Button.
*
* @method Phaser.Button#setOverSound
* @public
* @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played.
* @param {string} [marker] - A Sound Marker that will be used in the playback.
*/
Phaser.Button.prototype.setOverSound = function (sound, marker) {
this.setStateSound(STATE_OVER, sound, marker);
};
/**
* The Sound to be played when a Pointer moves out of this Button.
*
* @method Phaser.Button#setOutSound
* @public
* @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played.
* @param {string} [marker] - A Sound Marker that will be used in the playback.
*/
Phaser.Button.prototype.setOutSound = function (sound, marker) {
this.setStateSound(STATE_OUT, sound, marker);
};
/**
* The Sound to be played when a Pointer presses down on this Button.
*
* @method Phaser.Button#setDownSound
* @public
* @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played.
* @param {string} [marker] - A Sound Marker that will be used in the playback.
*/
Phaser.Button.prototype.setDownSound = function (sound, marker) {
this.setStateSound(STATE_DOWN, sound, marker);
};
/**
* The Sound to be played when a Pointer has pressed down and is released from this Button.
*
* @method Phaser.Button#setUpSound
* @public
* @param {Phaser.Sound|Phaser.AudioSprite} sound - The Sound that will be played.
* @param {string} [marker] - A Sound Marker that will be used in the playback.
*/
Phaser.Button.prototype.setUpSound = function (sound, marker) {
this.setStateSound(STATE_UP, sound, marker);
};
/**
* Internal function that handles input events.
*
* @method Phaser.Button#onInputOverHandler
* @protected
* @param {Phaser.Button} sprite - The Button that the event occurred on.
* @param {Phaser.Pointer} pointer - The Pointer that activated the Button.
*/
Phaser.Button.prototype.onInputOverHandler = function (sprite, pointer) {
if (pointer.justReleased() &&
(this.justReleasedPreventsOver & pointer.pointerMode) === pointer.pointerMode)
{
// If the Pointer was only just released then we don't fire an over event
return;
}
this.changeStateFrame(STATE_OVER);
if (this.onOverMouseOnly && !pointer.isMouse)
{
return;
}
this.playStateSound(STATE_OVER);
if (this.onInputOver)
{
this.onInputOver.dispatch(this, pointer);
}
};
/**
* Internal function that handles input events.
*
* @method Phaser.Button#onInputOutHandler
* @protected
* @param {Phaser.Button} sprite - The Button that the event occurred on.
* @param {Phaser.Pointer} pointer - The Pointer that activated the Button.
*/
Phaser.Button.prototype.onInputOutHandler = function (sprite, pointer) {
this.changeStateFrame(STATE_OUT);
this.playStateSound(STATE_OUT);
if (this.onInputOut)
{
this.onInputOut.dispatch(this, pointer);
}
};
/**
* Internal function that handles input events.
*
* @method Phaser.Button#onInputDownHandler
* @protected
* @param {Phaser.Button} sprite - The Button that the event occurred on.
* @param {Phaser.Pointer} pointer - The Pointer that activated the Button.
*/
Phaser.Button.prototype.onInputDownHandler = function (sprite, pointer) {
this.changeStateFrame(STATE_DOWN);
this.playStateSound(STATE_DOWN);
if (this.onInputDown)
{
this.onInputDown.dispatch(this, pointer);
}
};
/**
* Internal function that handles input events.
*
* @method Phaser.Button#onInputUpHandler
* @protected
* @param {Phaser.Button} sprite - The Button that the event occurred on.
* @param {Phaser.Pointer} pointer - The Pointer that activated the Button.
*/
Phaser.Button.prototype.onInputUpHandler = function (sprite, pointer, isOver) {
this.playStateSound(STATE_UP);
// Input dispatched early, before state change (but after sound)
if (this.onInputUp)
{
this.onInputUp.dispatch(this, pointer, isOver);
}
if (this.freezeFrames)
{
return;
}
if (this.forceOut === true || (this.forceOut & pointer.pointerMode) === pointer.pointerMode)
{
this.changeStateFrame(STATE_OUT);
}
else
{
var changedUp = this.changeStateFrame(STATE_UP);
if (!changedUp)
{
// No Up frame to show..
if (isOver)
{
this.changeStateFrame(STATE_OVER);
}
else
{
this.changeStateFrame(STATE_OUT);
}
}
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The SpriteBatch class is a really fast version of the DisplayObjectContainer built purely for speed, so use when you need a lot of sprites or particles.
* It's worth mentioning that by default sprite batches are used through-out the renderer, so you only really need to use a SpriteBatch if you have over
* 1000 sprites that all share the same texture (or texture atlas). It's also useful if running in Canvas mode and you have a lot of un-rotated or un-scaled
* Sprites as it skips all of the Canvas setTransform calls, which helps performance, especially on mobile devices.
*
* Please note that any Sprite that is part of a SpriteBatch will not have its bounds updated, so will fail checks such as outOfBounds.
*
* @class Phaser.SpriteBatch
* @extends Phaser.Group
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {Phaser.Group|Phaser.Sprite|null} parent - The parent Group, DisplayObject or DisplayObjectContainer that this Group will be added to. If `undefined` or `null` it will use game.world.
* @param {string} [name=group] - A name for this Group. Not used internally but useful for debugging.
* @param {boolean} [addToStage=false] - If set to true this Group will be added directly to the Game.Stage instead of Game.World.
*/
Phaser.SpriteBatch = function (game, parent, name, addToStage) {
if (parent === undefined || parent === null) { parent = game.world; }
Phaser.Group.call(this, game, parent, name, addToStage);
/**
* @property {number} type - Internal Phaser Type value.
* @protected
*/
this.type = Phaser.SPRITEBATCH;
/**
* @property {Object} fastSpriteBatch - WebGL Batch Shader.
* @private
*/
this.fastSpriteBatch = null;
/**
* @property {boolean} ready - Internal flag.
* @private
*/
this.ready = false;
};
Phaser.SpriteBatch.prototype = Object.create(Phaser.Group.prototype);
Phaser.SpriteBatch.prototype.constructor = Phaser.SpriteBatch;
/**
* Renders the Sprite Batch using the WebGL renderer.
*
* @private
* @method
* @memberof Phaser.SpriteBatch
* @param {RenderSession} renderSession
*/
Phaser.SpriteBatch.prototype._renderWebGL = function (renderSession) {
if (!this.visible || this.alpha <= 0 || !this.children.length)
{
return;
}
if (!this.ready)
{
this.fastSpriteBatch = new PIXI.WebGLFastSpriteBatch(renderSession.gl);
this.ready = true;
}
if (this.fastSpriteBatch.gl !== renderSession.gl)
{
this.fastSpriteBatch.setContext(renderSession.gl);
}
renderSession.spriteBatch.stop();
renderSession.shaderManager.setShader(renderSession.shaderManager.fastShader);
this.fastSpriteBatch.begin(this, renderSession);
this.fastSpriteBatch.render(this);
renderSession.spriteBatch.start();
};
/**
* Renders the Sprite Batch using the Canvas renderer.
*
* @private
* @method
* @memberof Phaser.SpriteBatch
* @param {RenderSession} renderSession
*/
Phaser.SpriteBatch.prototype._renderCanvas = function (renderSession) {
if (!this.visible || this.alpha <= 0 || !this.children.length)
{
return;
}
var context = renderSession.context;
context.globalAlpha = this.worldAlpha;
this.displayObjectUpdateTransform();
var transform = this.worldTransform;
var isRotated = true;
for (var i = 0; i < this.children.length; i++)
{
var child = this.children[i];
if (!child.visible)
{
continue;
}
var texture = child.texture;
var frame = texture.frame;
context.globalAlpha = this.worldAlpha * child.alpha;
if (child.rotation % (Math.PI * 2) === 0)
{
// If rotation === 0 we can avoid setTransform
if (isRotated)
{
context.setTransform(transform.a, transform.b, transform.c, transform.d, transform.tx, transform.ty);
isRotated = false;
}
context.drawImage(
texture.baseTexture.source,
frame.x,
frame.y,
frame.width,
frame.height,
((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5 + renderSession.shakeX) | 0,
((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5 + renderSession.shakeY) | 0,
frame.width * child.scale.x,
frame.height * child.scale.y);
}
else
{
if (!isRotated)
{
isRotated = true;
}
child.displayObjectUpdateTransform();
var childTransform = child.worldTransform;
var tx = (childTransform.tx * renderSession.resolution) + renderSession.shakeX;
var ty = (childTransform.ty * renderSession.resolution) + renderSession.shakeY;
// allow for trimming
if (renderSession.roundPixels)
{
context.setTransform(childTransform.a, childTransform.b, childTransform.c, childTransform.d, tx | 0, ty | 0);
}
else
{
context.setTransform(childTransform.a, childTransform.b, childTransform.c, childTransform.d, tx, ty);
}
context.drawImage(
texture.baseTexture.source,
frame.x,
frame.y,
frame.width,
frame.height,
((child.anchor.x) * (-frame.width) + 0.5) | 0,
((child.anchor.y) * (-frame.height) + 0.5) | 0,
frame.width,
frame.height);
}
}
};
Phaser.TileSprite = function () {};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The CanvasPool is a global static object, that allows Phaser to recycle and pool Canvas DOM elements.
*
* @class Phaser.CanvasPool
* @static
*/
Phaser.CanvasPool = {
/**
* Creates a new Canvas DOM element, or pulls one from the pool if free.
*
* @method Phaser.CanvasPool.create
* @static
* @param {any} parent - The parent of the canvas element.
* @param {number} width - The width of the canvas element.
* @param {number} height - The height of the canvas element.
* @return {HTMLCanvasElement} The canvas element.
*/
create: function (parent, width, height) {
var idx = Phaser.CanvasPool.getFirst();
var canvas;
if (idx === -1)
{
var container = {
parent: parent,
canvas: document.createElement('canvas')
};
Phaser.CanvasPool.pool.push(container);
canvas = container.canvas;
}
else
{
Phaser.CanvasPool.pool[idx].parent = parent;
canvas = Phaser.CanvasPool.pool[idx].canvas;
}
if (width !== undefined)
{
canvas.width = width;
canvas.height = height;
}
return canvas;
},
/**
* Gets the first free canvas index from the pool.
*
* @static
* @method Phaser.CanvasPool.getFirst
* @return {number}
*/
getFirst: function () {
var pool = Phaser.CanvasPool.pool;
for (var i = 0; i < pool.length; i++)
{
if (!pool[i].parent)
{
return i;
}
}
return -1;
},
/**
* Looks up a canvas based on its parent, and if found puts it back in the pool, freeing it up for re-use.
* The canvas has its width and height set to 1, and its parent attribute nulled.
*
* @static
* @method Phaser.CanvasPool.remove
* @param {any} parent - The parent of the canvas element.
*/
remove: function (parent) {
var pool = Phaser.CanvasPool.pool;
for (var i = 0; i < pool.length; i++)
{
if (pool[i].parent === parent)
{
pool[i].parent = null;
pool[i].canvas.width = 1;
pool[i].canvas.height = 1;
}
}
},
/**
* Looks up a canvas based on its type, and if found puts it back in the pool, freeing it up for re-use.
* The canvas has its width and height set to 1, and its parent attribute nulled.
*
* @static
* @method Phaser.CanvasPool.removeByCanvas
* @param {HTMLCanvasElement} canvas - The canvas element to remove.
*/
removeByCanvas: function (canvas) {
var pool = Phaser.CanvasPool.pool;
for (var i = 0; i < pool.length; i++)
{
if (pool[i].canvas === canvas)
{
pool[i].parent = null;
pool[i].canvas.width = 1;
pool[i].canvas.height = 1;
}
}
},
/**
* Gets the total number of used canvas elements in the pool.
*
* @static
* @method Phaser.CanvasPool.getTotal
* @return {number} The number of in-use (parented) canvas elements in the pool.
*/
getTotal: function () {
var pool = Phaser.CanvasPool.pool;
var c = 0;
for (var i = 0; i < pool.length; i++)
{
if (pool[i].parent)
{
c++;
}
}
return c;
},
/**
* Gets the total number of free canvas elements in the pool.
*
* @static
* @method Phaser.CanvasPool.getFree
* @return {number} The number of free (un-parented) canvas elements in the pool.
*/
getFree: function () {
var pool = Phaser.CanvasPool.pool;
var c = 0;
for (var i = 0; i < pool.length; i++)
{
if (!pool[i].parent)
{
c++;
}
}
return c;
}
};
/**
* The pool into which the canvas elements are placed.
*
* @property pool
* @type Array
* @static
*/
Phaser.CanvasPool.pool = [];
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* @classdesc
* Detects device support capabilities and is responsible for device initialization - see {@link Phaser.Device.whenReady whenReady}.
*
* This class represents a singleton object that can be accessed directly as `game.device`
* (or, as a fallback, `Phaser.Device` when a game instance is not available) without the need to instantiate it.
*
* Unless otherwise noted the device capabilities are only guaranteed after initialization. Initialization
* occurs automatically and is guaranteed complete before {@link Phaser.Game} begins its "boot" phase.
* Feature detection can be modified in the {@link Phaser.Device.onInitialized onInitialized} signal.
*
* When checking features using the exposed properties only the *truth-iness* of the value should be relied upon
* unless the documentation states otherwise: properties may return `false`, `''`, `null`, or even `undefined`
* when indicating the lack of a feature.
*
* Uses elements from System.js by MrDoob and Modernizr
*
* @description
* It is not possible to instantiate the Device class manually.
*
* @class
* @protected
*/
Phaser.Device = function () {
/**
* The time the device became ready.
* @property {integer} deviceReadyAt
* @protected
*/
this.deviceReadyAt = 0;
/**
* The time as which initialization has completed.
* @property {boolean} initialized
* @protected
*/
this.initialized = false;
// Browser / Host / Operating System
/**
* @property {boolean} desktop - Is running on a desktop?
* @default
*/
this.desktop = false;
/**
* @property {boolean} iOS - Is running on iOS?
* @default
*/
this.iOS = false;
/**
* @property {number} iOSVersion - If running in iOS this will contain the major version number.
* @default
*/
this.iOSVersion = 0;
/**
* @property {boolean} cocoonJS - Is the game running under CocoonJS?
* @default
*/
this.cocoonJS = false;
/**
* @property {boolean} cocoonJSApp - Is this game running with CocoonJS.App?
* @default
*/
this.cocoonJSApp = false;
/**
* @property {boolean} cordova - Is the game running under Apache Cordova?
* @default
*/
this.cordova = false;
/**
* @property {boolean} node - Is the game running under Node.js?
* @default
*/
this.node = false;
/**
* @property {boolean} nodeWebkit - Is the game running under Node-Webkit?
* @default
*/
this.nodeWebkit = false;
/**
* @property {boolean} electron - Is the game running under GitHub Electron?
* @default
*/
this.electron = false;
/**
* @property {boolean} ejecta - Is the game running under Ejecta?
* @default
*/
this.ejecta = false;
/**
* @property {boolean} crosswalk - Is the game running under the Intel Crosswalk XDK?
* @default
*/
this.crosswalk = false;
/**
* @property {boolean} android - Is running on android?
* @default
*/
this.android = false;
/**
* @property {boolean} chromeOS - Is running on chromeOS?
* @default
*/
this.chromeOS = false;
/**
* @property {boolean} linux - Is running on linux?
* @default
*/
this.linux = false;
/**
* @property {boolean} macOS - Is running on macOS?
* @default
*/
this.macOS = false;
/**
* @property {boolean} windows - Is running on windows?
* @default
*/
this.windows = false;
/**
* @property {boolean} windowsPhone - Is running on a Windows Phone?
* @default
*/
this.windowsPhone = false;
// Features
/**
* @property {boolean} canvas - Is canvas available?
* @default
*/
this.canvas = false;
/**
* @property {?boolean} canvasBitBltShift - True if canvas supports a 'copy' bitblt onto itself when the source and destination regions overlap.
* @default
*/
this.canvasBitBltShift = null;
/**
* If the browser isn't capable of handling tinting with alpha this will be false.
* @property {boolean} canHandleAlpha
* @default
*/
this.canHandleAlpha = false;
/**
* Whether or not the Canvas BlendModes are supported, consequently the ability to tint using the multiply method.
* @property {boolean} canUseMultiply
* @default
*/
this.canUseMultiply = false;
/**
* @property {boolean} webGL - Is webGL available?
* @default
*/
this.webGL = false;
/**
* @property {boolean} file - Is file available?
* @default
*/
this.file = false;
/**
* @property {boolean} fileSystem - Is fileSystem available?
* @default
*/
this.fileSystem = false;
/**
* @property {boolean} localStorage - Is localStorage available?
* @default
*/
this.localStorage = false;
/**
* @property {boolean} worker - Is worker available?
* @default
*/
this.worker = false;
/**
* @property {boolean} css3D - Is css3D available?
* @default
*/
this.css3D = false;
/**
* @property {boolean} pointerLock - Is Pointer Lock available?
* @default
*/
this.pointerLock = false;
/**
* @property {boolean} typedArray - Does the browser support TypedArrays?
* @default
*/
this.typedArray = false;
/**
* @property {boolean} vibration - Does the device support the Vibration API?
* @default
*/
this.vibration = false;
/**
* @property {boolean} getUserMedia - Does the device support the getUserMedia API?
* @default
*/
this.getUserMedia = true;
/**
* @property {boolean} quirksMode - Is the browser running in strict mode (false) or quirks mode? (true)
* @default
*/
this.quirksMode = false;
// Input
/**
* @property {boolean} touch - Is touch available?
* @default
*/
this.touch = false;
/**
* @property {boolean} mspointer - Is mspointer available?
* @default
*/
this.mspointer = false;
/**
* @property {?string} wheelType - The newest type of Wheel/Scroll event supported: 'wheel', 'mousewheel', 'DOMMouseScroll'
* @default
* @protected
*/
this.wheelEvent = null;
// Browser
/**
* @property {boolean} arora - Set to true if running in Arora.
* @default
*/
this.arora = false;
/**
* @property {boolean} chrome - Set to true if running in Chrome.
* @default
*/
this.chrome = false;
/**
* @property {number} chromeVersion - If running in Chrome this will contain the major version number.
* @default
*/
this.chromeVersion = 0;
/**
* @property {boolean} epiphany - Set to true if running in Epiphany.
* @default
*/
this.epiphany = false;
/**
* @property {boolean} firefox - Set to true if running in Firefox.
* @default
*/
this.firefox = false;
/**
* @property {number} firefoxVersion - If running in Firefox this will contain the major version number.
* @default
*/
this.firefoxVersion = 0;
/**
* @property {boolean} ie - Set to true if running in Internet Explorer.
* @default
*/
this.ie = false;
/**
* @property {number} ieVersion - If running in Internet Explorer this will contain the major version number. Beyond IE10 you should use Device.trident and Device.tridentVersion.
* @default
*/
this.ieVersion = 0;
/**
* @property {boolean} trident - Set to true if running a Trident version of Internet Explorer (IE11+)
* @default
*/
this.trident = false;
/**
* @property {number} tridentVersion - If running in Internet Explorer 11 this will contain the major version number. See {@link http://msdn.microsoft.com/en-us/library/ie/ms537503(v=vs.85).aspx}
* @default
*/
this.tridentVersion = 0;
/**
* @property {boolean} edge - Set to true if running in Microsoft Edge browser.
* @default
*/
this.edge = false;
/**
* @property {boolean} mobileSafari - Set to true if running in Mobile Safari.
* @default
*/
this.mobileSafari = false;
/**
* @property {boolean} midori - Set to true if running in Midori.
* @default
*/
this.midori = false;
/**
* @property {boolean} opera - Set to true if running in Opera.
* @default
*/
this.opera = false;
/**
* @property {boolean} safari - Set to true if running in Safari.
* @default
*/
this.safari = false;
/**
* @property {number} safariVersion - If running in Safari this will contain the major version number.
* @default
*/
this.safariVersion = 0;
/**
* @property {boolean} webApp - Set to true if running as a WebApp, i.e. within a WebView
* @default
*/
this.webApp = false;
/**
* @property {boolean} silk - Set to true if running in the Silk browser (as used on the Amazon Kindle)
* @default
*/
this.silk = false;
// Audio
/**
* @property {boolean} audioData - Are Audio tags available?
* @default
*/
this.audioData = false;
/**
* @property {boolean} webAudio - Is the WebAudio API available?
* @default
*/
this.webAudio = false;
/**
* @property {boolean} ogg - Can this device play ogg files?
* @default
*/
this.ogg = false;
/**
* @property {boolean} opus - Can this device play opus files?
* @default
*/
this.opus = false;
/**
* @property {boolean} mp3 - Can this device play mp3 files?
* @default
*/
this.mp3 = false;
/**
* @property {boolean} wav - Can this device play wav files?
* @default
*/
this.wav = false;
/**
* Can this device play m4a files?
* @property {boolean} m4a - True if this device can play m4a files.
* @default
*/
this.m4a = false;
/**
* @property {boolean} webm - Can this device play webm files?
* @default
*/
this.webm = false;
/**
* @property {boolean} dolby - Can this device play EC-3 Dolby Digital Plus files?
* @default
*/
this.dolby = false;
// Video
/**
* @property {boolean} oggVideo - Can this device play ogg video files?
* @default
*/
this.oggVideo = false;
/**
* @property {boolean} h264Video - Can this device play h264 mp4 video files?
* @default
*/
this.h264Video = false;
/**
* @property {boolean} mp4Video - Can this device play h264 mp4 video files?
* @default
*/
this.mp4Video = false;
/**
* @property {boolean} webmVideo - Can this device play webm video files?
* @default
*/
this.webmVideo = false;
/**
* @property {boolean} vp9Video - Can this device play vp9 video files?
* @default
*/
this.vp9Video = false;
/**
* @property {boolean} hlsVideo - Can this device play hls video files?
* @default
*/
this.hlsVideo = false;
// Device
/**
* @property {boolean} iPhone - Is running on iPhone?
* @default
*/
this.iPhone = false;
/**
* @property {boolean} iPhone4 - Is running on iPhone4?
* @default
*/
this.iPhone4 = false;
/**
* @property {boolean} iPad - Is running on iPad?
* @default
*/
this.iPad = false;
// Device features
/**
* @property {number} pixelRatio - PixelRatio of the host device?
* @default
*/
this.pixelRatio = 0;
/**
* @property {boolean} littleEndian - Is the device big or little endian? (only detected if the browser supports TypedArrays)
* @default
*/
this.littleEndian = false;
/**
* @property {boolean} LITTLE_ENDIAN - Same value as `littleEndian`.
* @default
*/
this.LITTLE_ENDIAN = false;
/**
* @property {boolean} support32bit - Does the device context support 32bit pixel manipulation using array buffer views?
* @default
*/
this.support32bit = false;
/**
* @property {boolean} fullscreen - Does the browser support the Full Screen API?
* @default
*/
this.fullscreen = false;
/**
* @property {string} requestFullscreen - If the browser supports the Full Screen API this holds the call you need to use to activate it.
* @default
*/
this.requestFullscreen = '';
/**
* @property {string} cancelFullscreen - If the browser supports the Full Screen API this holds the call you need to use to cancel it.
* @default
*/
this.cancelFullscreen = '';
/**
* @property {boolean} fullscreenKeyboard - Does the browser support access to the Keyboard during Full Screen mode?
* @default
*/
this.fullscreenKeyboard = false;
};
// Device is really a singleton/static entity; instantiate it
// and add new methods directly sans-prototype.
Phaser.Device = new Phaser.Device();
/**
* This signal is dispatched after device initialization occurs but before any of the ready
* callbacks (see {@link Phaser.Device.whenReady whenReady}) have been invoked.
*
* Local "patching" for a particular device can/should be done in this event.
*
* _Note_: This signal is removed after the device has been readied; if a handler has not been
* added _before_ `new Phaser.Game(..)` it is probably too late.
*
* @type {?Phaser.Signal}
* @static
*/
Phaser.Device.onInitialized = new Phaser.Signal();
/**
* Add a device-ready handler and ensure the device ready sequence is started.
*
* Phaser.Device will _not_ activate or initialize until at least one `whenReady` handler is added,
* which is normally done automatically be calling `new Phaser.Game(..)`.
*
* The handler is invoked when the device is considered "ready", which may be immediately
* if the device is already "ready". See {@link Phaser.Device#deviceReadyAt deviceReadyAt}.
*
* @method
* @param {function} handler - Callback to invoke when the device is ready. It is invoked with the given context the Phaser.Device object is supplied as the first argument.
* @param {object} [context] - Context in which to invoke the handler
* @param {boolean} [nonPrimer=false] - If true the device ready check will not be started.
*/
Phaser.Device.whenReady = function (callback, context, nonPrimer) {
var readyCheck = this._readyCheck;
if (this.deviceReadyAt || !readyCheck)
{
callback.call(context, this);
}
else if (readyCheck._monitor || nonPrimer)
{
readyCheck._queue = readyCheck._queue || [];
readyCheck._queue.push([callback, context]);
}
else
{
readyCheck._monitor = readyCheck.bind(this);
readyCheck._queue = readyCheck._queue || [];
readyCheck._queue.push([callback, context]);
var cordova = typeof window.cordova !== 'undefined';
var cocoonJS = navigator['isCocoonJS'];
if (document.readyState === 'complete' || document.readyState === 'interactive')
{
// Why is there an additional timeout here?
window.setTimeout(readyCheck._monitor, 0);
}
else if (cordova && !cocoonJS)
{
// Ref. http://docs.phonegap.com/en/3.5.0/cordova_events_events.md.html#deviceready
// Cordova, but NOT Cocoon?
document.addEventListener('deviceready', readyCheck._monitor, false);
}
else
{
document.addEventListener('DOMContentLoaded', readyCheck._monitor, false);
window.addEventListener('load', readyCheck._monitor, false);
}
}
};
/**
* Internal method used for checking when the device is ready.
* This function is removed from Phaser.Device when the device becomes ready.
*
* @method
* @private
*/
Phaser.Device._readyCheck = function () {
var readyCheck = this._readyCheck;
if (!document.body)
{
window.setTimeout(readyCheck._monitor, 20);
}
else if (!this.deviceReadyAt)
{
this.deviceReadyAt = Date.now();
document.removeEventListener('deviceready', readyCheck._monitor);
document.removeEventListener('DOMContentLoaded', readyCheck._monitor);
window.removeEventListener('load', readyCheck._monitor);
this._initialize();
this.initialized = true;
this.onInitialized.dispatch(this);
var item;
while ((item = readyCheck._queue.shift()))
{
var callback = item[0];
var context = item[1];
callback.call(context, this);
}
// Remove no longer useful methods and properties.
this._readyCheck = null;
this._initialize = null;
this.onInitialized = null;
}
};
/**
* Internal method to initialize the capability checks.
* This function is removed from Phaser.Device once the device is initialized.
*
* @method
* @private
*/
Phaser.Device._initialize = function () {
var device = this;
/**
* Check which OS is game running on.
*/
function _checkOS () {
var ua = navigator.userAgent;
if (/Playstation Vita/.test(ua))
{
device.vita = true;
}
else if (/Kindle/.test(ua) || /\bKF[A-Z][A-Z]+/.test(ua) || /Silk.*Mobile Safari/.test(ua))
{
device.kindle = true;
// This will NOT detect early generations of Kindle Fire, I think there is no reliable way...
// E.g. "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true"
}
else if (/Android/.test(ua))
{
device.android = true;
}
else if (/CrOS/.test(ua))
{
device.chromeOS = true;
}
else if (/iP[ao]d|iPhone/i.test(ua))
{
device.iOS = true;
(navigator.appVersion).match(/OS (\d+)/);
device.iOSVersion = parseInt(RegExp.$1, 10);
}
else if (/Linux/.test(ua))
{
device.linux = true;
}
else if (/Mac OS/.test(ua))
{
device.macOS = true;
}
else if (/Windows/.test(ua))
{
device.windows = true;
}
if (/Windows Phone/i.test(ua) || /IEMobile/i.test(ua))
{
device.android = false;
device.iOS = false;
device.macOS = false;
device.windows = true;
device.windowsPhone = true;
}
var silk = /Silk/.test(ua); // detected in browsers
if (device.windows || device.macOS || (device.linux && !silk) || device.chromeOS)
{
device.desktop = true;
}
// Windows Phone / Table reset
if (device.windowsPhone || ((/Windows NT/i.test(ua)) && (/Touch/i.test(ua))))
{
device.desktop = false;
}
}
/**
* Checks if the browser correctly supports putImageData alpha channels.
* If the browser isn't capable of handling tinting with alpha, `Device.canHandleAlpha` will be false.
* Also checks whether the Canvas BlendModes are supported by the current browser for drawImage.
*/
function _checkCanvasFeatures () {
var canvas = Phaser.CanvasPool.create(this, 6, 1);
var context = canvas.getContext('2d');
context.fillStyle = 'rgba(10, 20, 30, 0.5)';
// Draw a single pixel
context.fillRect(0, 0, 1, 1);
// Get the color values
var s1 = context.getImageData(0, 0, 1, 1);
if (s1)
{
// Plot them to x2
context.putImageData(s1, 1, 0);
// Get those values
var s2 = context.getImageData(1, 0, 1, 1);
// Compare and set
device.canHandleAlpha = (
s2.data[0] === s1.data[0] &&
s2.data[1] === s1.data[1] &&
s2.data[2] === s1.data[2] &&
s2.data[3] === s1.data[3]
);
}
// Checks whether the Canvas BlendModes are supported by the current browser for drawImage.
var pngHead = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAABAQMAAADD8p2OAAAAA1BMVEX/';
var pngEnd = 'AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==';
var magenta = new Image();
magenta.src = pngHead + 'AP804Oa6' + pngEnd;
var yellow = new Image();
yellow.src = pngHead + '/wCKxvRF' + pngEnd;
context.clearRect(0, 0, 6, 1);
context.globalCompositeOperation = 'multiply';
context.drawImage(magenta, 0, 0);
context.drawImage(yellow, 2, 0);
if (context.getImageData(2, 0, 1, 1))
{
var data = context.getImageData(2, 0, 1, 1).data;
device.canUseMultiply = (data[0] === 255 && data[1] === 0 && data[2] === 0);
}
Phaser.CanvasPool.removeByCanvas(canvas);
PIXI.CanvasTinter.tintMethod = (device.canUseMultiply) ? PIXI.CanvasTinter.tintWithMultiply : PIXI.CanvasTinter.tintWithPerPixel;
}
/**
* Check HTML5 features of the host environment.
*/
function _checkFeatures () {
device.canvas = !!window['CanvasRenderingContext2D'] || device.cocoonJS;
try {
device.localStorage = !!localStorage.getItem;
} catch (error) {
device.localStorage = false;
}
device.file = !!window['File'] && !!window['FileReader'] && !!window['FileList'] && !!window['Blob'];
device.fileSystem = !!window['requestFileSystem'];
device.webGL = ( function () { try { var canvas = document.createElement( 'canvas' ); /*Force screencanvas to false*/ canvas.screencanvas = false; return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); } catch( e ) { return false; } } )();
device.webGL = !!device.webGL;
device.worker = !!window['Worker'];
device.pointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document;
device.quirksMode = (document.compatMode === 'CSS1Compat') ? false : true;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
device.getUserMedia = device.getUserMedia && !!navigator.getUserMedia && !!window.URL;
// Older versions of firefox (< 21) apparently claim support but user media does not actually work
if (device.firefox && device.firefoxVersion < 21)
{
device.getUserMedia = false;
}
// TODO: replace canvasBitBltShift detection with actual feature check
// Excludes iOS versions as they generally wrap UIWebView (eg. Safari WebKit) and it
// is safer to not try and use the fast copy-over method.
if (!device.iOS && (device.ie || device.firefox || device.chrome))
{
device.canvasBitBltShift = true;
}
// Known not to work
if (device.safari || device.mobileSafari)
{
device.canvasBitBltShift = false;
}
}
/**
* Checks/configures various input.
*/
function _checkInput () {
if ('ontouchstart' in document.documentElement || (window.navigator.maxTouchPoints && window.navigator.maxTouchPoints >= 1))
{
device.touch = true;
}
if (window.PointerEvent || window.MSPointerEvent || window.navigator.msPointerEnabled || window.navigator.pointerEnabled)
{
device.mspointer = true;
}
if (!device.cocoonJS)
{
// See https://developer.mozilla.org/en-US/docs/Web/Events/wheel
if ('onwheel' in window || (device.ie && 'WheelEvent' in window))
{
// DOM3 Wheel Event: FF 17+, IE 9+, Chrome 31+, Safari 7+
device.wheelEvent = 'wheel';
}
else if ('onmousewheel' in window)
{
// Non-FF legacy: IE 6-9, Chrome 1-31, Safari 5-7.
device.wheelEvent = 'mousewheel';
}
else if (device.firefox && 'MouseScrollEvent' in window)
{
// FF prior to 17. This should probably be scrubbed.
device.wheelEvent = 'DOMMouseScroll';
}
}
}
/**
* Checks for support of the Full Screen API.
*/
function _checkFullScreenSupport () {
var fs = [
'requestFullscreen',
'requestFullScreen',
'webkitRequestFullscreen',
'webkitRequestFullScreen',
'msRequestFullscreen',
'msRequestFullScreen',
'mozRequestFullScreen',
'mozRequestFullscreen'
];
var element = document.createElement('div');
for (var i = 0; i < fs.length; i++)
{
if (element[fs[i]])
{
device.fullscreen = true;
device.requestFullscreen = fs[i];
break;
}
}
var cfs = [
'cancelFullScreen',
'exitFullscreen',
'webkitCancelFullScreen',
'webkitExitFullscreen',
'msCancelFullScreen',
'msExitFullscreen',
'mozCancelFullScreen',
'mozExitFullscreen'
];
if (device.fullscreen)
{
for (var i = 0; i < cfs.length; i++)
{
if (document[cfs[i]])
{
device.cancelFullscreen = cfs[i];
break;
}
}
}
// Keyboard Input?
if (window['Element'] && Element['ALLOW_KEYBOARD_INPUT'])
{
device.fullscreenKeyboard = true;
}
}
/**
* Check what browser is game running in.
*/
function _checkBrowser () {
var ua = navigator.userAgent;
if (/Arora/.test(ua))
{
device.arora = true;
}
else if (/Edge\/\d+/.test(ua))
{
device.edge = true;
}
else if (/Chrome\/(\d+)/.test(ua) && !device.windowsPhone)
{
device.chrome = true;
device.chromeVersion = parseInt(RegExp.$1, 10);
}
else if (/Epiphany/.test(ua))
{
device.epiphany = true;
}
else if (/Firefox\D+(\d+)/.test(ua))
{
device.firefox = true;
device.firefoxVersion = parseInt(RegExp.$1, 10);
}
else if (/AppleWebKit/.test(ua) && device.iOS)
{
device.mobileSafari = true;
}
else if (/MSIE (\d+\.\d+);/.test(ua))
{
device.ie = true;
device.ieVersion = parseInt(RegExp.$1, 10);
}
else if (/Midori/.test(ua))
{
device.midori = true;
}
else if (/Opera/.test(ua))
{
device.opera = true;
}
else if (/Safari\/(\d+)/.test(ua) && !device.windowsPhone)
{
device.safari = true;
if (/Version\/(\d+)\./.test(ua))
{
device.safariVersion = parseInt(RegExp.$1, 10);
}
}
else if (/Trident\/(\d+\.\d+)(.*)rv:(\d+\.\d+)/.test(ua))
{
device.ie = true;
device.trident = true;
device.tridentVersion = parseInt(RegExp.$1, 10);
device.ieVersion = parseInt(RegExp.$3, 10);
}
// Silk gets its own if clause because its ua also contains 'Safari'
if (/Silk/.test(ua))
{
device.silk = true;
}
// WebApp mode in iOS
if (navigator['standalone'])
{
device.webApp = true;
}
if (typeof window.cordova !== 'undefined')
{
device.cordova = true;
}
if (typeof process !== 'undefined' && typeof require !== 'undefined')
{
device.node = true;
}
if (device.node && typeof process.versions === 'object')
{
device.nodeWebkit = !!process.versions['node-webkit'];
device.electron = !!process.versions.electron;
}
if (navigator['isCocoonJS'])
{
device.cocoonJS = true;
}
if (device.cocoonJS)
{
try {
device.cocoonJSApp = (typeof CocoonJS !== 'undefined');
}
catch(error)
{
device.cocoonJSApp = false;
}
}
if (typeof window.ejecta !== 'undefined')
{
device.ejecta = true;
}
if (/Crosswalk/.test(ua))
{
device.crosswalk = true;
}
}
/**
* Check video support.
*/
function _checkVideo () {
var videoElement = document.createElement("video");
var result = false;
try {
if (result = !!videoElement.canPlayType)
{
if (videoElement.canPlayType('video/ogg; codecs="theora"').replace(/^no$/, ''))
{
device.oggVideo = true;
}
if (videoElement.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/, ''))
{
// Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
device.h264Video = true;
device.mp4Video = true;
}
if (videoElement.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/, ''))
{
device.webmVideo = true;
}
if (videoElement.canPlayType('video/webm; codecs="vp9"').replace(/^no$/, ''))
{
device.vp9Video = true;
}
if (videoElement.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/, ''))
{
device.hlsVideo = true;
}
}
} catch (e) {}
}
/**
* Check audio support.
*/
function _checkAudio () {
device.audioData = !!(window['Audio']);
device.webAudio = !!(window['AudioContext'] || window['webkitAudioContext']);
var audioElement = document.createElement('audio');
var result = false;
try {
if (result = !!audioElement.canPlayType)
{
if (audioElement.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''))
{
device.ogg = true;
}
if (audioElement.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, '') || audioElement.canPlayType('audio/opus;').replace(/^no$/, ''))
{
device.opus = true;
}
if (audioElement.canPlayType('audio/mpeg;').replace(/^no$/, ''))
{
device.mp3 = true;
}
// Mimetypes accepted:
// developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
// bit.ly/iphoneoscodecs
if (audioElement.canPlayType('audio/wav; codecs="1"').replace(/^no$/, ''))
{
device.wav = true;
}
if (audioElement.canPlayType('audio/x-m4a;') || audioElement.canPlayType('audio/aac;').replace(/^no$/, ''))
{
device.m4a = true;
}
if (audioElement.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, ''))
{
device.webm = true;
}
if (audioElement.canPlayType('audio/mp4;codecs="ec-3"') !== '')
{
if (device.edge)
{
device.dolby = true;
}
else if (device.safari && device.safariVersion >= 9)
{
if (/Mac OS X (\d+)_(\d+)/.test(navigator.userAgent))
{
var major = parseInt(RegExp.$1, 10);
var minor = parseInt(RegExp.$2, 10);
if ((major === 10 && minor >= 11) || major > 10)
{
device.dolby = true;
}
}
}
}
}
} catch (e) {
}
}
/**
* Check Little or Big Endian system.
*
* @author Matt DesLauriers (@mattdesl)
*/
function _checkIsLittleEndian () {
var a = new ArrayBuffer(4);
var b = new Uint8Array(a);
var c = new Uint32Array(a);
b[0] = 0xa1;
b[1] = 0xb2;
b[2] = 0xc3;
b[3] = 0xd4;
if (c[0] === 0xd4c3b2a1)
{
return true;
}
if (c[0] === 0xa1b2c3d4)
{
return false;
}
else
{
// Could not determine endianness
return null;
}
}
/**
* Test to see if ImageData uses CanvasPixelArray or Uint8ClampedArray.
*
* @author Matt DesLauriers (@mattdesl)
*/
function _checkIsUint8ClampedImageData () {
if (Uint8ClampedArray === undefined)
{
return false;
}
var elem = Phaser.CanvasPool.create(this, 1, 1);
var ctx = elem.getContext('2d');
if (!ctx)
{
return false;
}
var image = ctx.createImageData(1, 1);
Phaser.CanvasPool.remove(this);
return image.data instanceof Uint8ClampedArray;
}
/**
* Check PixelRatio, iOS device, Vibration API, ArrayBuffers and endianess.
*/
function _checkDevice () {
device.pixelRatio = window['devicePixelRatio'] || 1;
device.iPhone = navigator.userAgent.toLowerCase().indexOf('iphone') !== -1;
device.iPhone4 = (device.pixelRatio === 2 && device.iPhone);
device.iPad = navigator.userAgent.toLowerCase().indexOf('ipad') !== -1;
if (typeof Int8Array !== 'undefined')
{
device.typedArray = true;
}
else
{
device.typedArray = false;
}
if (typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined' && typeof Uint32Array !== 'undefined')
{
device.littleEndian = _checkIsLittleEndian();
device.LITTLE_ENDIAN = device.littleEndian;
}
device.support32bit = (typeof ArrayBuffer !== 'undefined' && typeof Uint8ClampedArray !== 'undefined' && typeof Int32Array !== 'undefined' && device.littleEndian !== null && _checkIsUint8ClampedImageData());
navigator.vibrate = navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate;
if (navigator.vibrate)
{
device.vibration = true;
}
}
/**
* Check whether the host environment support 3D CSS.
*/
function _checkCSS3D () {
var el = document.createElement('p');
var has3d;
var transforms = {
'webkitTransform': '-webkit-transform',
'OTransform': '-o-transform',
'msTransform': '-ms-transform',
'MozTransform': '-moz-transform',
'transform': 'transform'
};
// Add it to the body to get the computed style.
document.body.insertBefore(el, null);
for (var t in transforms)
{
if (el.style[t] !== undefined)
{
el.style[t] = "translate3d(1px,1px,1px)";
has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]);
}
}
document.body.removeChild(el);
device.css3D = (has3d !== undefined && has3d.length > 0 && has3d !== "none");
}
// Run the checks
_checkOS();
_checkBrowser();
_checkAudio();
_checkVideo();
_checkCSS3D();
_checkDevice();
_checkFeatures();
_checkCanvasFeatures();
_checkFullScreenSupport();
_checkInput();
};
/**
* Check whether the host environment can play audio.
*
* @method canPlayAudio
* @memberof Phaser.Device.prototype
* @param {string} type - One of 'mp3, 'ogg', 'm4a', 'wav', 'webm' or 'opus'.
* @return {boolean} True if the given file type is supported by the browser, otherwise false.
*/
Phaser.Device.canPlayAudio = function (type) {
if (type === 'mp3' && this.mp3)
{
return true;
}
else if (type === 'ogg' && (this.ogg || this.opus))
{
return true;
}
else if (type === 'm4a' && this.m4a)
{
return true;
}
else if (type === 'opus' && this.opus)
{
return true;
}
else if (type === 'wav' && this.wav)
{
return true;
}
else if (type === 'webm' && this.webm)
{
return true;
}
else if (type === 'mp4' && this.dolby)
{
return true;
}
return false;
};
/**
* Check whether the host environment can play video files.
*
* @method canPlayVideo
* @memberof Phaser.Device.prototype
* @param {string} type - One of 'mp4, 'ogg', 'webm' or 'mpeg'.
* @return {boolean} True if the given file type is supported by the browser, otherwise false.
*/
Phaser.Device.canPlayVideo = function (type) {
if (type === 'webm' && (this.webmVideo || this.vp9Video))
{
return true;
}
else if (type === 'mp4' && (this.mp4Video || this.h264Video))
{
return true;
}
else if ((type === 'ogg' || type === 'ogv') && this.oggVideo)
{
return true;
}
else if (type === 'mpeg' && this.hlsVideo)
{
return true;
}
return false;
};
/**
* Check whether the console is open.
* Note that this only works in Firefox with Firebug and earlier versions of Chrome.
* It used to work in Chrome, but then they removed the ability: {@link http://src.chromium.org/viewvc/blink?view=revision&revision=151136}
*
* @method isConsoleOpen
* @memberof Phaser.Device.prototype
*/
Phaser.Device.isConsoleOpen = function () {
if (window.console && window.console['firebug'])
{
return true;
}
if (window.console)
{
console.profile();
console.profileEnd();
if (console.clear)
{
console.clear();
}
if (console['profiles'])
{
return console['profiles'].length > 0;
}
}
return false;
};
/**
* Detect if the host is a an Android Stock browser.
* This is available before the device "ready" event.
*
* Authors might want to scale down on effects and switch to the CANVAS rendering method on those devices.
*
* @example
* var defaultRenderingMode = Phaser.Device.isAndroidStockBrowser() ? Phaser.CANVAS : Phaser.AUTO;
*
* @method isAndroidStockBrowser
* @memberof Phaser.Device.prototype
*/
Phaser.Device.isAndroidStockBrowser = function () {
var matches = window.navigator.userAgent.match(/Android.*AppleWebKit\/([\d.]+)/);
return matches && matches[1] < 537;
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Canvas class handles everything related to creating the `canvas` DOM tag that Phaser will use,
* including styles, offset and aspect ratio.
*
* @class Phaser.Canvas
* @static
*/
Phaser.Canvas = {
/**
* Creates a `canvas` DOM element. The element is not automatically added to the document.
*
* @method Phaser.Canvas.create
* @param {object} parent - The object that will own the canvas that is created.
* @param {number} [width=256] - The width of the canvas element.
* @param {number} [height=256] - The height of the canvas element..
* @param {string} [id=(none)] - If specified, and not the empty string, this will be set as the ID of the canvas element. Otherwise no ID will be set.
* @param {boolean} [skipPool=false] - If `true` the canvas will not be placed in the CanvasPool global.
* @return {HTMLCanvasElement} The newly created canvas element.
*/
create: function (parent, width, height, id, skipPool) {
width = width || 256;
height = height || 256;
var canvas = (skipPool) ? document.createElement('canvas') : Phaser.CanvasPool.create(parent, width, height);
if (typeof id === 'string' && id !== '')
{
canvas.id = id;
}
canvas.width = width;
canvas.height = height;
canvas.style.display = 'block';
return canvas;
},
/**
* Sets the background color behind the canvas. This changes the canvas style property.
*
* @method Phaser.Canvas.setBackgroundColor
* @param {HTMLCanvasElement} canvas - The canvas to set the background color on.
* @param {string} [color='rgb(0,0,0)'] - The color to set. Can be in the format 'rgb(r,g,b)', or '#RRGGBB' or any valid CSS color.
* @return {HTMLCanvasElement} Returns the source canvas.
*/
setBackgroundColor: function (canvas, color) {
color = color || 'rgb(0,0,0)';
canvas.style.backgroundColor = color;
return canvas;
},
/**
* Sets the touch-action property on the canvas style. Can be used to disable default browser touch actions.
*
* @method Phaser.Canvas.setTouchAction
* @param {HTMLCanvasElement} canvas - The canvas to set the touch action on.
* @param {string} [value] - The touch action to set. Defaults to 'none'.
* @return {HTMLCanvasElement} The source canvas.
*/
setTouchAction: function (canvas, value) {
value = value || 'none';
canvas.style.msTouchAction = value;
canvas.style['ms-touch-action'] = value;
canvas.style['touch-action'] = value;
return canvas;
},
/**
* Sets the user-select property on the canvas style. Can be used to disable default browser selection actions.
*
* @method Phaser.Canvas.setUserSelect
* @param {HTMLCanvasElement} canvas - The canvas to set the touch action on.
* @param {string} [value] - The touch action to set. Defaults to 'none'.
* @return {HTMLCanvasElement} The source canvas.
*/
setUserSelect: function (canvas, value) {
value = value || 'none';
canvas.style['-webkit-touch-callout'] = value;
canvas.style['-webkit-user-select'] = value;
canvas.style['-khtml-user-select'] = value;
canvas.style['-moz-user-select'] = value;
canvas.style['-ms-user-select'] = value;
canvas.style['user-select'] = value;
canvas.style['-webkit-tap-highlight-color'] = 'rgba(0, 0, 0, 0)';
return canvas;
},
/**
* Adds the given canvas element to the DOM. The canvas will be added as a child of the given parent.
* If no parent is given it will be added as a child of the document.body.
*
* @method Phaser.Canvas.addToDOM
* @param {HTMLCanvasElement} canvas - The canvas to be added to the DOM.
* @param {string|HTMLElement} parent - The DOM element to add the canvas to.
* @param {boolean} [overflowHidden=true] - If set to true it will add the overflow='hidden' style to the parent DOM element.
* @return {HTMLCanvasElement} Returns the source canvas.
*/
addToDOM: function (canvas, parent, overflowHidden) {
var target;
if (overflowHidden === undefined) { overflowHidden = true; }
if (parent)
{
if (typeof parent === 'string')
{
// hopefully an element ID
target = document.getElementById(parent);
}
else if (typeof parent === 'object' && parent.nodeType === 1)
{
// quick test for a HTMLelement
target = parent;
}
}
// Fallback, covers an invalid ID and a non HTMLelement object
if (!target)
{
target = document.body;
}
if (overflowHidden && target.style)
{
target.style.overflow = 'hidden';
}
target.appendChild(canvas);
return canvas;
},
/**
* Removes the given canvas element from the DOM.
*
* @method Phaser.Canvas.removeFromDOM
* @param {HTMLCanvasElement} canvas - The canvas to be removed from the DOM.
*/
removeFromDOM: function (canvas) {
if (canvas.parentNode)
{
canvas.parentNode.removeChild(canvas);
}
},
/**
* Sets the transform of the given canvas to the matrix values provided.
*
* @method Phaser.Canvas.setTransform
* @param {CanvasRenderingContext2D} context - The context to set the transform on.
* @param {number} translateX - The value to translate horizontally by.
* @param {number} translateY - The value to translate vertically by.
* @param {number} scaleX - The value to scale horizontally by.
* @param {number} scaleY - The value to scale vertically by.
* @param {number} skewX - The value to skew horizontaly by.
* @param {number} skewY - The value to skew vertically by.
* @return {CanvasRenderingContext2D} Returns the source context.
*/
setTransform: function (context, translateX, translateY, scaleX, scaleY, skewX, skewY) {
context.setTransform(scaleX, skewX, skewY, scaleY, translateX, translateY);
return context;
},
/**
* Sets the Image Smoothing property on the given context. Set to false to disable image smoothing.
* By default browsers have image smoothing enabled, which isn't always what you visually want, especially
* when using pixel art in a game. Note that this sets the property on the context itself, so that any image
* drawn to the context will be affected. This sets the property across all current browsers but support is
* patchy on earlier browsers, especially on mobile.
*
* @method Phaser.Canvas.setSmoothingEnabled
* @param {CanvasRenderingContext2D} context - The context to enable or disable the image smoothing on.
* @param {boolean} value - If set to true it will enable image smoothing, false will disable it.
* @return {CanvasRenderingContext2D} Returns the source context.
*/
setSmoothingEnabled: function (context, value) {
var s = Phaser.Canvas.getSmoothingPrefix(context);
if (s)
{
context[s] = value;
}
return context;
},
/**
* Gets the Smoothing Enabled vendor prefix being used on the given context, or null if not set.
*
* @method Phaser.Canvas.getSmoothingPrefix
* @param {CanvasRenderingContext2D} context - The context to enable or disable the image smoothing on.
* @return {string|null} Returns the smoothingEnabled vendor prefix, or null if not set on the context.
*/
getSmoothingPrefix: function (context) {
var vendor = [ 'i', 'webkitI', 'msI', 'mozI', 'oI' ];
for (var prefix in vendor)
{
var s = vendor[prefix] + 'mageSmoothingEnabled';
if (s in context)
{
return s;
}
}
return null;
},
/**
* Returns `true` if the given context has image smoothing enabled, otherwise returns `false`.
*
* @method Phaser.Canvas.getSmoothingEnabled
* @param {CanvasRenderingContext2D} context - The context to check for smoothing on.
* @return {boolean} True if the given context has image smoothing enabled, otherwise false.
*/
getSmoothingEnabled: function (context) {
var s = Phaser.Canvas.getSmoothingPrefix(context);
if (s)
{
return context[s];
}
},
/**
* Sets the CSS image-rendering property on the given canvas to be 'crisp' (aka 'optimize contrast' on webkit).
* Note that if this doesn't given the desired result then see the setSmoothingEnabled.
*
* @method Phaser.Canvas.setImageRenderingCrisp
* @param {HTMLCanvasElement} canvas - The canvas to set image-rendering crisp on.
* @return {HTMLCanvasElement} Returns the source canvas.
*/
setImageRenderingCrisp: function (canvas) {
var types = [ 'optimizeSpeed', 'crisp-edges', '-moz-crisp-edges', '-webkit-optimize-contrast', 'optimize-contrast', 'pixelated' ];
for (var i = 0; i < types.length; i++)
{
canvas.style['image-rendering'] = types[i];
}
canvas.style.msInterpolationMode = 'nearest-neighbor';
return canvas;
},
/**
* Sets the CSS image-rendering property on the given canvas to be 'bicubic' (aka 'auto').
* Note that if this doesn't given the desired result then see the CanvasUtils.setSmoothingEnabled method.
*
* @method Phaser.Canvas.setImageRenderingBicubic
* @param {HTMLCanvasElement} canvas The canvas to set image-rendering bicubic on.
* @return {HTMLCanvasElement} Returns the source canvas.
*/
setImageRenderingBicubic: function (canvas) {
canvas.style['image-rendering'] = 'auto';
canvas.style.msInterpolationMode = 'bicubic';
return canvas;
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Abstracts away the use of RAF or setTimeOut for the core game update loop.
*
* @class Phaser.RequestAnimationFrame
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {boolean} [forceSetTimeOut=false] - Tell Phaser to use setTimeOut even if raf is available.
*/
Phaser.RequestAnimationFrame = function(game, forceSetTimeOut) {
if (forceSetTimeOut === undefined) { forceSetTimeOut = false; }
/**
* @property {Phaser.Game} game - The currently running game.
*/
this.game = game;
/**
* @property {boolean} isRunning - true if RequestAnimationFrame is running, otherwise false.
* @default
*/
this.isRunning = false;
/**
* @property {boolean} forceSetTimeOut - Tell Phaser to use setTimeOut even if raf is available.
*/
this.forceSetTimeOut = forceSetTimeOut;
var vendors = [
'ms',
'moz',
'webkit',
'o'
];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; x++)
{
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'];
}
/**
* @property {boolean} _isSetTimeOut - true if the browser is using setTimeout instead of raf.
* @private
*/
this._isSetTimeOut = false;
/**
* @property {function} _onLoop - The function called by the update.
* @private
*/
this._onLoop = null;
/**
* @property {number} _timeOutID - The callback ID used when calling cancel.
* @private
*/
this._timeOutID = null;
};
Phaser.RequestAnimationFrame.prototype = {
/**
* Starts the requestAnimationFrame running or setTimeout if unavailable in browser
* @method Phaser.RequestAnimationFrame#start
*/
start: function () {
this.isRunning = true;
var _this = this;
if (!window.requestAnimationFrame || this.forceSetTimeOut)
{
this._isSetTimeOut = true;
this._onLoop = function () {
return _this.updateSetTimeout();
};
this._timeOutID = window.setTimeout(this._onLoop, 0);
}
else
{
this._isSetTimeOut = false;
this._onLoop = function (time) {
return _this.updateRAF(time);
};
this._timeOutID = window.requestAnimationFrame(this._onLoop);
}
},
/**
* The update method for the requestAnimationFrame
* @method Phaser.RequestAnimationFrame#updateRAF
*/
updateRAF: function (rafTime) {
if (this.isRunning)
{
// floor the rafTime to make it equivalent to the Date.now() provided by updateSetTimeout (just below)
this.game.update(Math.floor(rafTime));
this._timeOutID = window.requestAnimationFrame(this._onLoop);
}
},
/**
* The update method for the setTimeout.
* @method Phaser.RequestAnimationFrame#updateSetTimeout
*/
updateSetTimeout: function () {
if (this.isRunning)
{
this.game.update(Date.now());
this._timeOutID = window.setTimeout(this._onLoop, this.game.time.timeToCall);
}
},
/**
* Stops the requestAnimationFrame from running.
* @method Phaser.RequestAnimationFrame#stop
*/
stop: function () {
if (this._isSetTimeOut)
{
clearTimeout(this._timeOutID);
}
else
{
window.cancelAnimationFrame(this._timeOutID);
}
this.isRunning = false;
},
/**
* Is the browser using setTimeout?
* @method Phaser.RequestAnimationFrame#isSetTimeOut
* @return {boolean}
*/
isSetTimeOut: function () {
return this._isSetTimeOut;
},
/**
* Is the browser using requestAnimationFrame?
* @method Phaser.RequestAnimationFrame#isRAF
* @return {boolean}
*/
isRAF: function () {
return (this._isSetTimeOut === false);
}
};
Phaser.RequestAnimationFrame.prototype.constructor = Phaser.RequestAnimationFrame;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A collection of useful mathematical functions.
*
* These are normally accessed through `game.math`.
*
* @class Phaser.Math
* @static
* @see {@link Phaser.Utils}
* @see {@link Phaser.ArrayUtils}
*/
Phaser.Math = {
/**
* Twice PI.
* @property {number} Phaser.Math#PI2
* @default ~6.283
*/
PI2: Math.PI * 2,
/**
* Degrees to Radians factor.
* @property {number} Phaser.Math#DEG_TO_RAD
*/
DEG_TO_RAD: Math.PI / 180,
/**
* Degrees to Radians factor.
* @property {number} Phaser.Math#RAD_TO_DEG
*/
RAD_TO_DEG: 180 / Math.PI,
/**
* Convert degrees to radians.
*
* @method Phaser.Math#degToRad
* @param {number} degrees - Angle in degrees.
* @return {number} Angle in radians.
*/
degToRad: function (degrees) {
return degrees * Phaser.Math.DEG_TO_RAD;
},
/**
* Convert radians to degrees.
*
* @method Phaser.Math#radToDeg
* @param {number} radians - Angle in radians.
* @return {number} Angle in degrees
*/
radToDeg: function (radians) {
return radians * Phaser.Math.RAD_TO_DEG;
},
/**
* Given a number, this function returns the closest number that is a power of two.
* This function is from the Starling Framework.
*
* @method Phaser.Math#getNextPowerOfTwo
* @param {number} value - The value to get the closest power of two from.
* @return {number} The closest number that is a power of two.
*/
getNextPowerOfTwo: function (value) {
if (value > 0 && (value & (value - 1)) === 0)
{
// http://goo.gl/D9kPj
return value;
}
else
{
var result = 1;
while (result < value)
{
result <<= 1;
}
return result;
}
},
/**
* Checks if the given dimensions make a power of two texture.
*
* @method Phaser.Math#isPowerOfTwo
* @param {number} width - The width to check.
* @param {number} height - The height to check.
* @return {boolean} True if the width and height are a power of two.
*/
isPowerOfTwo: function (width, height) {
return (width > 0 && (width & (width - 1)) === 0 && height > 0 && (height & (height - 1)) === 0);
},
/**
* Returns a random float in the range `[min, max)`. If these parameters are not in order than they will be put in order.
* Default is 0 for `min` and 1 for `max`.
*
* @method Phaser.Math#random
* @param {number} min - The minimum value. Must be a Number.
* @param {number} max - The maximum value. Must be a Number.
* @return {number} A floating point number between min (inclusive) and max (exclusive).
*/
random: function (min, max) {
if (min === undefined) { min = 0; }
if (max === undefined) { max = 1; }
if (min === max)
{
return min;
}
if (min < max)
{
var temp = min;
min = max;
max = temp;
}
return (Math.random() * (max - min) + min);
},
/**
* Returns a random integer in the range `[min, max]`. If these parameters are not in order than they will be put in order.
* Default is 0 for `min` and 1 for `max`.
*
* @method Phaser.Math#between
* @param {number} min - The minimum value. Must be a Number.
* @param {number} max - The maximum value. Must be a Number.
* @return {number} An integer between min (inclusive) and max (inclusive).
*/
between: function (min, max) {
if (min === undefined) { min = 0; }
if (max === undefined) { max = 1; }
if (min === max)
{
return min;
}
if (min < max)
{
var temp = min;
min = max;
max = temp;
}
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
},
/**
* Two number are fuzzyEqual if their difference is less than epsilon.
*
* @method Phaser.Math#fuzzyEqual
* @param {number} a - The first number to compare.
* @param {number} b - The second number to compare.
* @param {number} [epsilon=0.0001] - The epsilon (a small value used in the calculation)
* @return {boolean} True if |a-b|<epsilon
*/
fuzzyEqual: function (a, b, epsilon) {
if (epsilon === undefined) { epsilon = 0.0001; }
return Math.abs(a - b) < epsilon;
},
/**
* `a` is fuzzyLessThan `b` if it is less than b + epsilon.
*
* @method Phaser.Math#fuzzyLessThan
* @param {number} a - The first number to compare.
* @param {number} b - The second number to compare.
* @param {number} [epsilon=0.0001] - The epsilon (a small value used in the calculation)
* @return {boolean} True if a<b+epsilon
*/
fuzzyLessThan: function (a, b, epsilon) {
if (epsilon === undefined) { epsilon = 0.0001; }
return a < b + epsilon;
},
/**
* `a` is fuzzyGreaterThan `b` if it is more than b - epsilon.
*
* @method Phaser.Math#fuzzyGreaterThan
* @param {number} a - The first number to compare.
* @param {number} b - The second number to compare.
* @param {number} [epsilon=0.0001] - The epsilon (a small value used in the calculation)
* @return {boolean} True if a>b+epsilon
*/
fuzzyGreaterThan: function (a, b, epsilon) {
if (epsilon === undefined) { epsilon = 0.0001; }
return a > b - epsilon;
},
/**
* Applies a fuzzy ceil to the given value.
*
* @method Phaser.Math#fuzzyCeil
* @param {number} val - The value to ceil.
* @param {number} [epsilon=0.0001] - The epsilon (a small value used in the calculation)
* @return {number} ceiling(val-epsilon)
*/
fuzzyCeil: function (val, epsilon) {
if (epsilon === undefined) { epsilon = 0.0001; }
return Math.ceil(val - epsilon);
},
/**
* Applies a fuzzy floor to the given value.
*
* @method Phaser.Math#fuzzyFloor
* @param {number} val - The value to floor.
* @param {number} [epsilon=0.0001] - The epsilon (a small value used in the calculation)
* @return {number} floor(val+epsilon)
*/
fuzzyFloor: function (val, epsilon) {
if (epsilon === undefined) { epsilon = 0.0001; }
return Math.floor(val + epsilon);
},
/**
* Averages all values passed to the function and returns the result.
*
* @method Phaser.Math#average
* @params {...number} The numbers to average
* @return {number} The average of all given values.
*/
average: function () {
var sum = 0;
var len = arguments.length;
for (var i = 0; i < len; i++)
{
sum += (+arguments[i]);
}
return sum / len;
},
/**
* @method Phaser.Math#shear
* @param {number} n
* @return {number} n mod 1
*/
shear: function (n) {
return n % 1;
},
/**
* Snap a value to nearest grid slice, using rounding.
*
* Example: if you have an interval gap of 5 and a position of 12... you will snap to 10 whereas 14 will snap to 15.
*
* @method Phaser.Math#snapTo
* @param {number} input - The value to snap.
* @param {number} gap - The interval gap of the grid.
* @param {number} [start=0] - Optional starting offset for gap.
* @return {number} The snapped value.
*/
snapTo: function (input, gap, start) {
if (start === undefined) { start = 0; }
if (gap === 0) {
return input;
}
input -= start;
input = gap * Math.round(input / gap);
return start + input;
},
/**
* Snap a value to nearest grid slice, using floor.
*
* Example: if you have an interval gap of 5 and a position of 12... you will snap to 10.
* As will 14 snap to 10... but 16 will snap to 15.
*
* @method Phaser.Math#snapToFloor
* @param {number} input - The value to snap.
* @param {number} gap - The interval gap of the grid.
* @param {number} [start=0] - Optional starting offset for gap.
* @return {number} The snapped value.
*/
snapToFloor: function (input, gap, start) {
if (start === undefined) { start = 0; }
if (gap === 0) {
return input;
}
input -= start;
input = gap * Math.floor(input / gap);
return start + input;
},
/**
* Snap a value to nearest grid slice, using ceil.
*
* Example: if you have an interval gap of 5 and a position of 12... you will snap to 15.
* As will 14 will snap to 15... but 16 will snap to 20.
*
* @method Phaser.Math#snapToCeil
* @param {number} input - The value to snap.
* @param {number} gap - The interval gap of the grid.
* @param {number} [start=0] - Optional starting offset for gap.
* @return {number} The snapped value.
*/
snapToCeil: function (input, gap, start) {
if (start === undefined) { start = 0; }
if (gap === 0) {
return input;
}
input -= start;
input = gap * Math.ceil(input / gap);
return start + input;
},
/**
* Round to some place comparative to a `base`, default is 10 for decimal place.
* The `place` is represented by the power applied to `base` to get that place.
*
* e.g. 2000/7 ~= 285.714285714285714285714 ~= (bin)100011101.1011011011011011
*
* roundTo(2000/7,3) === 0
* roundTo(2000/7,2) == 300
* roundTo(2000/7,1) == 290
* roundTo(2000/7,0) == 286
* roundTo(2000/7,-1) == 285.7
* roundTo(2000/7,-2) == 285.71
* roundTo(2000/7,-3) == 285.714
* roundTo(2000/7,-4) == 285.7143
* roundTo(2000/7,-5) == 285.71429
*
* roundTo(2000/7,3,2) == 288 -- 100100000
* roundTo(2000/7,2,2) == 284 -- 100011100
* roundTo(2000/7,1,2) == 286 -- 100011110
* roundTo(2000/7,0,2) == 286 -- 100011110
* roundTo(2000/7,-1,2) == 285.5 -- 100011101.1
* roundTo(2000/7,-2,2) == 285.75 -- 100011101.11
* roundTo(2000/7,-3,2) == 285.75 -- 100011101.11
* roundTo(2000/7,-4,2) == 285.6875 -- 100011101.1011
* roundTo(2000/7,-5,2) == 285.71875 -- 100011101.10111
*
* Note what occurs when we round to the 3rd space (8ths place), 100100000, this is to be assumed
* because we are rounding 100011.1011011011011011 which rounds up.
*
* @method Phaser.Math#roundTo
* @param {number} value - The value to round.
* @param {number} [place=0] - The place to round to.
* @param {number} [base=10] - The base to round in. Default is 10 for decimal.
* @return {number} The rounded value.
*/
roundTo: function (value, place, base) {
if (place === undefined) { place = 0; }
if (base === undefined) { base = 10; }
var p = Math.pow(base, -place);
return Math.round(value * p) / p;
},
/**
* Floors to some place comparative to a `base`, default is 10 for decimal place.
* The `place` is represented by the power applied to `base` to get that place.
*
* @method Phaser.Math#floorTo
* @param {number} value - The value to round.
* @param {number} [place=0] - The place to round to.
* @param {number} [base=10] - The base to round in. Default is 10 for decimal.
* @return {number} The rounded value.
*/
floorTo: function (value, place, base) {
if (place === undefined) { place = 0; }
if (base === undefined) { base = 10; }
var p = Math.pow(base, -place);
return Math.floor(value * p) / p;
},
/**
* Ceils to some place comparative to a `base`, default is 10 for decimal place.
* The `place` is represented by the power applied to `base` to get that place.
*
* @method Phaser.Math#ceilTo
* @param {number} value - The value to round.
* @param {number} [place=0] - The place to round to.
* @param {number} [base=10] - The base to round in. Default is 10 for decimal.
* @return {number} The rounded value.
*/
ceilTo: function (value, place, base) {
if (place === undefined) { place = 0; }
if (base === undefined) { base = 10; }
var p = Math.pow(base, -place);
return Math.ceil(value * p) / p;
},
/**
* Rotates currentAngle towards targetAngle, taking the shortest rotation distance.
* The lerp argument is the amount to rotate by in this call.
*
* @method Phaser.Math#rotateToAngle
* @param {number} currentAngle - The current angle, in radians.
* @param {number} targetAngle - The target angle to rotate to, in radians.
* @param {number} [lerp=0.05] - The lerp value to add to the current angle.
* @return {number} The adjusted angle.
*/
rotateToAngle: function (currentAngle, targetAngle, lerp) {
if (lerp === undefined) { lerp = 0.05; }
if (currentAngle === targetAngle)
{
return currentAngle;
}
if (Math.abs(targetAngle - currentAngle) <= lerp || Math.abs(targetAngle - currentAngle) >= (Phaser.Math.PI2 - lerp))
{
currentAngle = targetAngle;
}
else
{
if (Math.abs(targetAngle - currentAngle) > Math.PI)
{
if (targetAngle < currentAngle)
{
targetAngle += Phaser.Math.PI2;
}
else
{
targetAngle -= Phaser.Math.PI2;
}
}
if (targetAngle > currentAngle)
{
currentAngle += lerp;
}
else if (targetAngle < currentAngle)
{
currentAngle -= lerp;
}
}
return currentAngle;
},
/**
* Gets the shortest angle between `angle1` and `angle2`.
* Both angles must be in the range -180 to 180, which is the same clamped
* range that `sprite.angle` uses, so you can pass in two sprite angles to
* this method, and get the shortest angle back between the two of them.
*
* The angle returned will be in the same range. If the returned angle is
* greater than 0 then it's a counter-clockwise rotation, if < 0 then it's
* a clockwise rotation.
*
* @method Phaser.Math#getShortestAngle
* @param {number} angle1 - The first angle. In the range -180 to 180.
* @param {number} angle2 - The second angle. In the range -180 to 180.
* @return {number} The shortest angle, in degrees. If greater than zero it's a counter-clockwise rotation.
*/
getShortestAngle: function (angle1, angle2) {
var difference = angle2 - angle1;
if (difference === 0)
{
return 0;
}
var times = Math.floor((difference - (-180)) / 360);
return difference - (times * 360);
},
/**
* Find the angle of a segment from (x1, y1) -> (x2, y2).
*
* @method Phaser.Math#angleBetween
* @param {number} x1 - The x coordinate of the first value.
* @param {number} y1 - The y coordinate of the first value.
* @param {number} x2 - The x coordinate of the second value.
* @param {number} y2 - The y coordinate of the second value.
* @return {number} The angle, in radians.
*/
angleBetween: function (x1, y1, x2, y2) {
return Math.atan2(y2 - y1, x2 - x1);
},
/**
* Find the angle of a segment from (x1, y1) -> (x2, y2).
*
* The difference between this method and Math.angleBetween is that this assumes the y coordinate travels
* down the screen.
*
* @method Phaser.Math#angleBetweenY
* @param {number} x1 - The x coordinate of the first value.
* @param {number} y1 - The y coordinate of the first value.
* @param {number} x2 - The x coordinate of the second value.
* @param {number} y2 - The y coordinate of the second value.
* @return {number} The angle, in radians.
*/
angleBetweenY: function (x1, y1, x2, y2) {
return Math.atan2(x2 - x1, y2 - y1);
},
/**
* Find the angle of a segment from (point1.x, point1.y) -> (point2.x, point2.y).
*
* @method Phaser.Math#angleBetweenPoints
* @param {Phaser.Point} point1 - The first point.
* @param {Phaser.Point} point2 - The second point.
* @return {number} The angle between the two points, in radians.
*/
angleBetweenPoints: function (point1, point2) {
return Math.atan2(point2.y - point1.y, point2.x - point1.x);
},
/**
* Find the angle of a segment from (point1.x, point1.y) -> (point2.x, point2.y).
* @method Phaser.Math#angleBetweenPointsY
* @param {Phaser.Point} point1
* @param {Phaser.Point} point2
* @return {number} The angle, in radians.
*/
angleBetweenPointsY: function (point1, point2) {
return Math.atan2(point2.x - point1.x, point2.y - point1.y);
},
/**
* Reverses an angle.
* @method Phaser.Math#reverseAngle
* @param {number} angleRad - The angle to reverse, in radians.
* @return {number} The reverse angle, in radians.
*/
reverseAngle: function (angleRad) {
return this.normalizeAngle(angleRad + Math.PI, true);
},
/**
* Normalizes an angle to the [0,2pi) range.
* @method Phaser.Math#normalizeAngle
* @param {number} angleRad - The angle to normalize, in radians.
* @return {number} The angle, fit within the [0,2pi] range, in radians.
*/
normalizeAngle: function (angleRad) {
angleRad = angleRad % (2 * Math.PI);
return angleRad >= 0 ? angleRad : angleRad + 2 * Math.PI;
},
/**
* Adds the given amount to the value, but never lets the value go over the specified maximum.
*
* @method Phaser.Math#maxAdd
* @param {number} value - The value to add the amount to.
* @param {number} amount - The amount to add to the value.
* @param {number} max - The maximum the value is allowed to be.
* @return {number} The new value.
*/
maxAdd: function (value, amount, max) {
return Math.min(value + amount, max);
},
/**
* Subtracts the given amount from the value, but never lets the value go below the specified minimum.
*
* @method Phaser.Math#minSub
* @param {number} value - The base value.
* @param {number} amount - The amount to subtract from the base value.
* @param {number} min - The minimum the value is allowed to be.
* @return {number} The new value.
*/
minSub: function (value, amount, min) {
return Math.max(value - amount, min);
},
/**
* Ensures that the value always stays between min and max, by wrapping the value around.
*
* If `max` is not larger than `min` the result is 0.
*
* @method Phaser.Math#wrap
* @param {number} value - The value to wrap.
* @param {number} min - The minimum the value is allowed to be.
* @param {number} max - The maximum the value is allowed to be, should be larger than `min`.
* @return {number} The wrapped value.
*/
wrap: function (value, min, max) {
var range = max - min;
if (range <= 0)
{
return 0;
}
var result = (value - min) % range;
if (result < 0)
{
result += range;
}
return result + min;
},
/**
* Adds value to amount and ensures that the result always stays between 0 and max, by wrapping the value around.
*
* Values _must_ be positive integers, and are passed through Math.abs. See {@link Phaser.Math#wrap} for an alternative.
*
* @method Phaser.Math#wrapValue
* @param {number} value - The value to add the amount to.
* @param {number} amount - The amount to add to the value.
* @param {number} max - The maximum the value is allowed to be.
* @return {number} The wrapped value.
*/
wrapValue: function (value, amount, max) {
var diff;
value = Math.abs(value);
amount = Math.abs(amount);
max = Math.abs(max);
diff = (value + amount) % max;
return diff;
},
/**
* Returns true if the number given is odd.
*
* @method Phaser.Math#isOdd
* @param {integer} n - The number to check.
* @return {boolean} True if the given number is odd. False if the given number is even.
*/
isOdd: function (n) {
// Does not work with extremely large values
return !!(n & 1);
},
/**
* Returns true if the number given is even.
*
* @method Phaser.Math#isEven
* @param {integer} n - The number to check.
* @return {boolean} True if the given number is even. False if the given number is odd.
*/
isEven: function (n) {
// Does not work with extremely large values
return !(n & 1);
},
/**
* Variation of Math.min that can be passed either an array of numbers or the numbers as parameters.
*
* Prefer the standard `Math.min` function when appropriate.
*
* @method Phaser.Math#min
* @return {number} The lowest value from those given.
* @see {@link http://jsperf.com/math-s-min-max-vs-homemade}
*/
min: function () {
if (arguments.length === 1 && typeof arguments[0] === 'object')
{
var data = arguments[0];
}
else
{
var data = arguments;
}
for (var i = 1, min = 0, len = data.length; i < len; i++)
{
if (data[i] < data[min])
{
min = i;
}
}
return data[min];
},
/**
* Variation of Math.max that can be passed either an array of numbers or the numbers as parameters.
*
* Prefer the standard `Math.max` function when appropriate.
*
* @method Phaser.Math#max
* @return {number} The largest value from those given.
* @see {@link http://jsperf.com/math-s-min-max-vs-homemade}
*/
max: function () {
if (arguments.length === 1 && typeof arguments[0] === 'object')
{
var data = arguments[0];
}
else
{
var data = arguments;
}
for (var i = 1, max = 0, len = data.length; i < len; i++)
{
if (data[i] > data[max])
{
max = i;
}
}
return data[max];
},
/**
* Variation of Math.min that can be passed a property and either an array of objects or the objects as parameters.
* It will find the lowest matching property value from the given objects.
*
* @method Phaser.Math#minProperty
* @return {number} The lowest value from those given.
*/
minProperty: function (property) {
if (arguments.length === 2 && typeof arguments[1] === 'object')
{
var data = arguments[1];
}
else
{
var data = arguments.slice(1);
}
for (var i = 1, min = 0, len = data.length; i < len; i++)
{
if (data[i][property] < data[min][property])
{
min = i;
}
}
return data[min][property];
},
/**
* Variation of Math.max that can be passed a property and either an array of objects or the objects as parameters.
* It will find the largest matching property value from the given objects.
*
* @method Phaser.Math#maxProperty
* @return {number} The largest value from those given.
*/
maxProperty: function (property) {
if (arguments.length === 2 && typeof arguments[1] === 'object')
{
var data = arguments[1];
}
else
{
var data = arguments.slice(1);
}
for (var i = 1, max = 0, len = data.length; i < len; i++)
{
if (data[i][property] > data[max][property])
{
max = i;
}
}
return data[max][property];
},
/**
* Keeps an angle value between -180 and +180; or -PI and PI if radians.
*
* @method Phaser.Math#wrapAngle
* @param {number} angle - The angle value to wrap
* @param {boolean} [radians=false] - Set to `true` if the angle is given in radians, otherwise degrees is expected.
* @return {number} The new angle value; will be the same as the input angle if it was within bounds.
*/
wrapAngle: function (angle, radians) {
return radians ? this.wrap(angle, -Math.PI, Math.PI) : this.wrap(angle, -180, 180);
},
/**
* A Linear Interpolation Method, mostly used by Phaser.Tween.
*
* @method Phaser.Math#linearInterpolation
* @param {Array} v - The input array of values to interpolate between.
* @param {number} k - The percentage of interpolation, between 0 and 1.
* @return {number} The interpolated value
*/
linearInterpolation: function (v, k) {
var m = v.length - 1;
var f = m * k;
var i = Math.floor(f);
if (k < 0)
{
return this.linear(v[0], v[1], f);
}
if (k > 1)
{
return this.linear(v[m], v[m - 1], m - f);
}
return this.linear(v[i], v[i + 1 > m ? m : i + 1], f - i);
},
/**
* A Bezier Interpolation Method, mostly used by Phaser.Tween.
*
* @method Phaser.Math#bezierInterpolation
* @param {Array} v - The input array of values to interpolate between.
* @param {number} k - The percentage of interpolation, between 0 and 1.
* @return {number} The interpolated value
*/
bezierInterpolation: function (v, k) {
var b = 0;
var n = v.length - 1;
for (var i = 0; i <= n; i++)
{
b += Math.pow(1 - k, n - i) * Math.pow(k, i) * v[i] * this.bernstein(n, i);
}
return b;
},
/**
* A Catmull Rom Interpolation Method, mostly used by Phaser.Tween.
*
* @method Phaser.Math#catmullRomInterpolation
* @param {Array} v - The input array of values to interpolate between.
* @param {number} k - The percentage of interpolation, between 0 and 1.
* @return {number} The interpolated value
*/
catmullRomInterpolation: function (v, k) {
var m = v.length - 1;
var f = m * k;
var i = Math.floor(f);
if (v[0] === v[m])
{
if (k < 0)
{
i = Math.floor(f = m * (1 + k));
}
return this.catmullRom(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i);
}
else
{
if (k < 0)
{
return v[0] - (this.catmullRom(v[0], v[0], v[1], v[1], -f) - v[0]);
}
if (k > 1)
{
return v[m] - (this.catmullRom(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]);
}
return this.catmullRom(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i);
}
},
/**
* Calculates a linear (interpolation) value over t.
*
* @method Phaser.Math#linear
* @param {number} p0
* @param {number} p1
* @param {number} t - A value between 0 and 1.
* @return {number}
*/
linear: function (p0, p1, t) {
return (p1 - p0) * t + p0;
},
/**
* @method Phaser.Math#bernstein
* @protected
* @param {number} n
* @param {number} i
* @return {number}
*/
bernstein: function (n, i) {
return this.factorial(n) / this.factorial(i) / this.factorial(n - i);
},
/**
* @method Phaser.Math#factorial
* @param {number} value - the number you want to evaluate
* @return {number}
*/
factorial: function (value) {
if (value === 0)
{
return 1;
}
var res = value;
while(--value)
{
res *= value;
}
return res;
},
/**
* Calculates a catmum rom value.
*
* @method Phaser.Math#catmullRom
* @protected
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {number} t
* @return {number}
*/
catmullRom: function (p0, p1, p2, p3, t) {
var v0 = (p2 - p0) * 0.5, v1 = (p3 - p1) * 0.5, t2 = t * t, t3 = t * t2;
return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
},
/**
* The absolute difference between two values.
*
* @method Phaser.Math#difference
* @param {number} a - The first value to check.
* @param {number} b - The second value to check.
* @return {number} The absolute difference between the two values.
*/
difference: function (a, b) {
return Math.abs(a - b);
},
/**
* Round to the next whole number _away_ from zero.
*
* @method Phaser.Math#roundAwayFromZero
* @param {number} value - Any number.
* @return {integer} The rounded value of that number.
*/
roundAwayFromZero: function (value) {
// "Opposite" of truncate.
return (value > 0) ? Math.ceil(value) : Math.floor(value);
},
/**
* Generate a sine and cosine table simultaneously and extremely quickly.
* The parameters allow you to specify the length, amplitude and frequency of the wave.
* This generator is fast enough to be used in real-time.
* Code based on research by Franky of scene.at
*
* @method Phaser.Math#sinCosGenerator
* @param {number} length - The length of the wave
* @param {number} sinAmplitude - The amplitude to apply to the sine table (default 1.0) if you need values between say -+ 125 then give 125 as the value
* @param {number} cosAmplitude - The amplitude to apply to the cosine table (default 1.0) if you need values between say -+ 125 then give 125 as the value
* @param {number} frequency - The frequency of the sine and cosine table data
* @return {{sin:number[], cos:number[]}} Returns the table data.
*/
sinCosGenerator: function (length, sinAmplitude, cosAmplitude, frequency) {
if (sinAmplitude === undefined) { sinAmplitude = 1.0; }
if (cosAmplitude === undefined) { cosAmplitude = 1.0; }
if (frequency === undefined) { frequency = 1.0; }
var sin = sinAmplitude;
var cos = cosAmplitude;
var frq = frequency * Math.PI / length;
var cosTable = [];
var sinTable = [];
for (var c = 0; c < length; c++) {
cos -= sin * frq;
sin += cos * frq;
cosTable[c] = cos;
sinTable[c] = sin;
}
return { sin: sinTable, cos: cosTable, length: length };
},
/**
* Returns the euclidian distance between the two given set of coordinates.
*
* @method Phaser.Math#distance
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @return {number} The distance between the two sets of coordinates.
*/
distance: function (x1, y1, x2, y2) {
var dx = x1 - x2;
var dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
},
/**
* Returns the euclidean distance squared between the two given set of
* coordinates (cuts out a square root operation before returning).
*
* @method Phaser.Math#distanceSq
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @return {number} The distance squared between the two sets of coordinates.
*/
distanceSq: function (x1, y1, x2, y2) {
var dx = x1 - x2;
var dy = y1 - y2;
return dx * dx + dy * dy;
},
/**
* Returns the distance between the two given set of coordinates at the power given.
*
* @method Phaser.Math#distancePow
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} [pow=2]
* @return {number} The distance between the two sets of coordinates.
*/
distancePow: function (x1, y1, x2, y2, pow) {
if (pow === undefined) { pow = 2; }
return Math.sqrt(Math.pow(x2 - x1, pow) + Math.pow(y2 - y1, pow));
},
/**
* Force a value within the boundaries by clamping it to the range `min`, `max`.
*
* @method Phaser.Math#clamp
* @param {float} v - The value to be clamped.
* @param {float} min - The minimum bounds.
* @param {float} max - The maximum bounds.
* @return {number} The clamped value.
*/
clamp: function (v, min, max) {
if (v < min)
{
return min;
}
else if (max < v)
{
return max;
}
else
{
return v;
}
},
/**
* Clamp `x` to the range `[a, Infinity)`.
* Roughly the same as `Math.max(x, a)`, except for NaN handling.
*
* @method Phaser.Math#clampBottom
* @param {number} x
* @param {number} a
* @return {number}
*/
clampBottom: function (x, a) {
return x < a ? a : x;
},
/**
* Checks if two values are within the given tolerance of each other.
*
* @method Phaser.Math#within
* @param {number} a - The first number to check
* @param {number} b - The second number to check
* @param {number} tolerance - The tolerance. Anything equal to or less than this is considered within the range.
* @return {boolean} True if a is <= tolerance of b.
* @see {@link Phaser.Math.fuzzyEqual}
*/
within: function (a, b, tolerance) {
return (Math.abs(a - b) <= tolerance);
},
/**
* Linear mapping from range <a1, a2> to range <b1, b2>
*
* @method Phaser.Math#mapLinear
* @param {number} x - The value to map
* @param {number} a1 - First endpoint of the range <a1, a2>
* @param {number} a2 - Final endpoint of the range <a1, a2>
* @param {number} b1 - First endpoint of the range <b1, b2>
* @param {number} b2 - Final endpoint of the range <b1, b2>
* @return {number}
*/
mapLinear: function (x, a1, a2, b1, b2) {
return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
},
/**
* Smoothstep function as detailed at http://en.wikipedia.org/wiki/Smoothstep
*
* @method Phaser.Math#smoothstep
* @param {float} x - The input value.
* @param {float} min - The left edge. Should be smaller than the right edge.
* @param {float} max - The right edge.
* @return {float} A value between 0 and 1.
*/
smoothstep: function (x, min, max) {
// Scale, bias and saturate x to 0..1 range
x = Math.max(0, Math.min(1, (x - min) / (max - min)));
// Evaluate polynomial
return x * x * (3 - 2 * x);
},
/**
* Smootherstep function as detailed at http://en.wikipedia.org/wiki/Smoothstep
*
* @method Phaser.Math#smootherstep
* @param {float} x - The input value.
* @param {float} min - The left edge. Should be smaller than the right edge.
* @param {float} max - The right edge.
* @return {float} A value between 0 and 1.
*/
smootherstep: function (x, min, max) {
x = Math.max(0, Math.min(1, (x - min) / (max - min)));
return x * x * x * (x * (x * 6 - 15) + 10);
},
/**
* A value representing the sign of the value: -1 for negative, +1 for positive, 0 if value is 0.
*
* This works differently from `Math.sign` for values of NaN and -0, etc.
*
* @method Phaser.Math#sign
* @param {number} x
* @return {integer} An integer in {-1, 0, 1}
*/
sign: function (x) {
return ( x < 0 ) ? -1 : ( ( x > 0 ) ? 1 : 0 );
},
/**
* Work out what percentage value `a` is of value `b` using the given base.
*
* @method Phaser.Math#percent
* @param {number} a - The value to work out the percentage for.
* @param {number} b - The value you wish to get the percentage of.
* @param {number} [base=0] - The base value.
* @return {number} The percentage a is of b, between 0 and 1.
*/
percent: function (a, b, base) {
if (base === undefined) { base = 0; }
if (a > b || base > b)
{
return 1;
}
else if (a < base || base > a)
{
return 0;
}
else
{
return (a - base) / b;
}
}
};
/* jshint noempty: false */
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* An extremely useful repeatable random data generator.
*
* Based on Nonsense by Josh Faul https://github.com/jocafa/Nonsense.
*
* The random number genererator is based on the Alea PRNG, but is modified.
* - https://github.com/coverslide/node-alea
* - https://github.com/nquinlan/better-random-numbers-for-javascript-mirror
* - http://baagoe.org/en/wiki/Better_random_numbers_for_javascript (original, perm. 404)
*
* @class Phaser.RandomDataGenerator
* @constructor
* @param {any[]|string} [seeds] - An array of values to use as the seed, or a generator state (from {#state}).
*/
Phaser.RandomDataGenerator = function (seeds) {
if (seeds === undefined) { seeds = []; }
/**
* @property {number} c - Internal var.
* @private
*/
this.c = 1;
/**
* @property {number} s0 - Internal var.
* @private
*/
this.s0 = 0;
/**
* @property {number} s1 - Internal var.
* @private
*/
this.s1 = 0;
/**
* @property {number} s2 - Internal var.
* @private
*/
this.s2 = 0;
if (typeof seeds === 'string')
{
this.state(seeds);
}
else
{
this.sow(seeds);
}
};
Phaser.RandomDataGenerator.prototype = {
/**
* Private random helper.
*
* @method Phaser.RandomDataGenerator#rnd
* @private
* @return {number}
*/
rnd: function () {
var t = 2091639 * this.s0 + this.c * 2.3283064365386963e-10; // 2^-32
this.c = t | 0;
this.s0 = this.s1;
this.s1 = this.s2;
this.s2 = t - this.c;
return this.s2;
},
/**
* Reset the seed of the random data generator.
*
* _Note_: the seed array is only processed up to the first `undefined` (or `null`) value, should such be present.
*
* @method Phaser.RandomDataGenerator#sow
* @param {any[]} seeds - The array of seeds: the `toString()` of each value is used.
*/
sow: function (seeds) {
// Always reset to default seed
this.s0 = this.hash(' ');
this.s1 = this.hash(this.s0);
this.s2 = this.hash(this.s1);
this.c = 1;
if (!seeds)
{
return;
}
// Apply any seeds
for (var i = 0; i < seeds.length && (seeds[i] != null); i++)
{
var seed = seeds[i];
this.s0 -= this.hash(seed);
this.s0 += ~~(this.s0 < 0);
this.s1 -= this.hash(seed);
this.s1 += ~~(this.s1 < 0);
this.s2 -= this.hash(seed);
this.s2 += ~~(this.s2 < 0);
}
},
/**
* Internal method that creates a seed hash.
*
* @method Phaser.RandomDataGenerator#hash
* @private
* @param {any} data
* @return {number} hashed value.
*/
hash: function (data) {
var h, i, n;
n = 0xefc8249d;
data = data.toString();
for (i = 0; i < data.length; i++) {
n += data.charCodeAt(i);
h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000;// 2^32
}
return (n >>> 0) * 2.3283064365386963e-10;// 2^-32
},
/**
* Returns a random integer between 0 and 2^32.
*
* @method Phaser.RandomDataGenerator#integer
* @return {number} A random integer between 0 and 2^32.
*/
integer: function() {
return this.rnd.apply(this) * 0x100000000;// 2^32
},
/**
* Returns a random real number between 0 and 1.
*
* @method Phaser.RandomDataGenerator#frac
* @return {number} A random real number between 0 and 1.
*/
frac: function() {
return this.rnd.apply(this) + (this.rnd.apply(this) * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53
},
/**
* Returns a random real number between 0 and 2^32.
*
* @method Phaser.RandomDataGenerator#real
* @return {number} A random real number between 0 and 2^32.
*/
real: function() {
return this.integer() + this.frac();
},
/**
* Returns a random integer between and including min and max.
*
* @method Phaser.RandomDataGenerator#integerInRange
* @param {number} min - The minimum value in the range.
* @param {number} max - The maximum value in the range.
* @return {number} A random number between min and max.
*/
integerInRange: function (min, max) {
return Math.floor(this.realInRange(0, max - min + 1) + min);
},
/**
* Returns a random integer between and including min and max.
* This method is an alias for RandomDataGenerator.integerInRange.
*
* @method Phaser.RandomDataGenerator#between
* @param {number} min - The minimum value in the range.
* @param {number} max - The maximum value in the range.
* @return {number} A random number between min and max.
*/
between: function (min, max) {
return this.integerInRange(min, max);
},
/**
* Returns a random real number between min and max.
*
* @method Phaser.RandomDataGenerator#realInRange
* @param {number} min - The minimum value in the range.
* @param {number} max - The maximum value in the range.
* @return {number} A random number between min and max.
*/
realInRange: function (min, max) {
return this.frac() * (max - min) + min;
},
/**
* Returns a random real number between -1 and 1.
*
* @method Phaser.RandomDataGenerator#normal
* @return {number} A random real number between -1 and 1.
*/
normal: function () {
return 1 - 2 * this.frac();
},
/**
* Returns a valid RFC4122 version4 ID hex string from https://gist.github.com/1308368
*
* @method Phaser.RandomDataGenerator#uuid
* @return {string} A valid RFC4122 version4 ID hex string
*/
uuid: function () {
var a = '';
var b = '';
for (b = a = ''; a++ < 36; b +=~a % 5 | a * 3&4 ? (a^15 ? 8^this.frac() * (a^20 ? 16 : 4) : 4).toString(16) : '-')
{
}
return b;
},
/**
* Returns a random member of `array`.
*
* @method Phaser.RandomDataGenerator#pick
* @param {Array} ary - An Array to pick a random member of.
* @return {any} A random member of the array.
*/
pick: function (ary) {
return ary[this.integerInRange(0, ary.length - 1)];
},
/**
* Returns a sign to be used with multiplication operator.
*
* @method Phaser.RandomDataGenerator#sign
* @return {number} -1 or +1.
*/
sign: function () {
return this.pick([-1, 1]);
},
/**
* Returns a random member of `array`, favoring the earlier entries.
*
* @method Phaser.RandomDataGenerator#weightedPick
* @param {Array} ary - An Array to pick a random member of.
* @return {any} A random member of the array.
*/
weightedPick: function (ary) {
return ary[~~(Math.pow(this.frac(), 2) * (ary.length - 1) + 0.5)];
},
/**
* Returns a random timestamp between min and max, or between the beginning of 2000 and the end of 2020 if min and max aren't specified.
*
* @method Phaser.RandomDataGenerator#timestamp
* @param {number} min - The minimum value in the range.
* @param {number} max - The maximum value in the range.
* @return {number} A random timestamp between min and max.
*/
timestamp: function (min, max) {
return this.realInRange(min || 946684800000, max || 1577862000000);
},
/**
* Returns a random angle between -180 and 180.
*
* @method Phaser.RandomDataGenerator#angle
* @return {number} A random number between -180 and 180.
*/
angle: function() {
return this.integerInRange(-180, 180);
},
/**
* Gets or Sets the state of the generator. This allows you to retain the values
* that the generator is using between games, i.e. in a game save file.
*
* To seed this generator with a previously saved state you can pass it as the
* `seed` value in your game config, or call this method directly after Phaser has booted.
*
* Call this method with no parameters to return the current state.
*
* If providing a state it should match the same format that this method
* returns, which is a string with a header `!rnd` followed by the `c`,
* `s0`, `s1` and `s2` values respectively, each comma-delimited.
*
* @method Phaser.RandomDataGenerator#state
* @param {string} [state] - Generator state to be set.
* @return {string} The current state of the generator.
*/
state: function (state) {
if (typeof state === 'string' && state.match(/^!rnd/))
{
state = state.split(',');
this.c = parseFloat(state[1]);
this.s0 = parseFloat(state[2]);
this.s1 = parseFloat(state[3]);
this.s2 = parseFloat(state[4]);
}
return ['!rnd', this.c, this.s0, this.s1, this.s2].join(',');
}
};
Phaser.RandomDataGenerator.prototype.constructor = Phaser.RandomDataGenerator;
/**
* @author Timo Hausmann
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A QuadTree implementation. The original code was a conversion of the Java code posted to GameDevTuts.
* However I've tweaked it massively to add node indexing, removed lots of temp. var creation and significantly increased performance as a result.
* Original version at https://github.com/timohausmann/quadtree-js/
*
* @class Phaser.QuadTree
* @constructor
* @param {number} x - The top left coordinate of the quadtree.
* @param {number} y - The top left coordinate of the quadtree.
* @param {number} width - The width of the quadtree in pixels.
* @param {number} height - The height of the quadtree in pixels.
* @param {number} [maxObjects=10] - The maximum number of objects per node.
* @param {number} [maxLevels=4] - The maximum number of levels to iterate to.
* @param {number} [level=0] - Which level is this?
*/
Phaser.QuadTree = function(x, y, width, height, maxObjects, maxLevels, level) {
/**
* @property {number} maxObjects - The maximum number of objects per node.
* @default
*/
this.maxObjects = 10;
/**
* @property {number} maxLevels - The maximum number of levels to break down to.
* @default
*/
this.maxLevels = 4;
/**
* @property {number} level - The current level.
*/
this.level = 0;
/**
* @property {object} bounds - Object that contains the quadtree bounds.
*/
this.bounds = {};
/**
* @property {array} objects - Array of quadtree children.
*/
this.objects = [];
/**
* @property {array} nodes - Array of associated child nodes.
*/
this.nodes = [];
/**
* @property {array} _empty - Internal empty array.
* @private
*/
this._empty = [];
this.reset(x, y, width, height, maxObjects, maxLevels, level);
};
Phaser.QuadTree.prototype = {
/**
* Resets the QuadTree.
*
* @method Phaser.QuadTree#reset
* @param {number} x - The top left coordinate of the quadtree.
* @param {number} y - The top left coordinate of the quadtree.
* @param {number} width - The width of the quadtree in pixels.
* @param {number} height - The height of the quadtree in pixels.
* @param {number} [maxObjects=10] - The maximum number of objects per node.
* @param {number} [maxLevels=4] - The maximum number of levels to iterate to.
* @param {number} [level=0] - Which level is this?
*/
reset: function (x, y, width, height, maxObjects, maxLevels, level) {
this.maxObjects = maxObjects || 10;
this.maxLevels = maxLevels || 4;
this.level = level || 0;
this.bounds = {
x: Math.round(x),
y: Math.round(y),
width: width,
height: height,
subWidth: Math.floor(width / 2),
subHeight: Math.floor(height / 2),
right: Math.round(x) + Math.floor(width / 2),
bottom: Math.round(y) + Math.floor(height / 2)
};
this.objects.length = 0;
this.nodes.length = 0;
},
/**
* Populates this quadtree with the children of the given Group. In order to be added the child must exist and have a body property.
*
* @method Phaser.QuadTree#populate
* @param {Phaser.Group} group - The Group to add to the quadtree.
*/
populate: function (group) {
group.forEach(this.populateHandler, this, true);
},
/**
* Handler for the populate method.
*
* @method Phaser.QuadTree#populateHandler
* @param {Phaser.Sprite|object} sprite - The Sprite to check.
*/
populateHandler: function (sprite) {
if (sprite.body && sprite.exists)
{
this.insert(sprite.body);
}
},
/**
* Split the node into 4 subnodes
*
* @method Phaser.QuadTree#split
*/
split: function () {
// top right node
this.nodes[0] = new Phaser.QuadTree(this.bounds.right, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, (this.level + 1));
// top left node
this.nodes[1] = new Phaser.QuadTree(this.bounds.x, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, (this.level + 1));
// bottom left node
this.nodes[2] = new Phaser.QuadTree(this.bounds.x, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, (this.level + 1));
// bottom right node
this.nodes[3] = new Phaser.QuadTree(this.bounds.right, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, (this.level + 1));
},
/**
* Insert the object into the node. If the node exceeds the capacity, it will split and add all objects to their corresponding subnodes.
*
* @method Phaser.QuadTree#insert
* @param {Phaser.Physics.Arcade.Body|object} body - The Body object to insert into the quadtree. Can be any object so long as it exposes x, y, right and bottom properties.
*/
insert: function (body) {
var i = 0;
var index;
// if we have subnodes ...
if (this.nodes[0] != null)
{
index = this.getIndex(body);
if (index !== -1)
{
this.nodes[index].insert(body);
return;
}
}
this.objects.push(body);
if (this.objects.length > this.maxObjects && this.level < this.maxLevels)
{
// Split if we don't already have subnodes
if (this.nodes[0] == null)
{
this.split();
}
// Add objects to subnodes
while (i < this.objects.length)
{
index = this.getIndex(this.objects[i]);
if (index !== -1)
{
// this is expensive - see what we can do about it
this.nodes[index].insert(this.objects.splice(i, 1)[0]);
}
else
{
i++;
}
}
}
},
/**
* Determine which node the object belongs to.
*
* @method Phaser.QuadTree#getIndex
* @param {Phaser.Rectangle|object} rect - The bounds in which to check.
* @return {number} index - Index of the subnode (0-3), or -1 if rect cannot completely fit within a subnode and is part of the parent node.
*/
getIndex: function (rect) {
// default is that rect doesn't fit, i.e. it straddles the internal quadrants
var index = -1;
if (rect.x < this.bounds.right && rect.right < this.bounds.right)
{
if (rect.y < this.bounds.bottom && rect.bottom < this.bounds.bottom)
{
// rect fits within the top-left quadrant of this quadtree
index = 1;
}
else if (rect.y > this.bounds.bottom)
{
// rect fits within the bottom-left quadrant of this quadtree
index = 2;
}
}
else if (rect.x > this.bounds.right)
{
// rect can completely fit within the right quadrants
if (rect.y < this.bounds.bottom && rect.bottom < this.bounds.bottom)
{
// rect fits within the top-right quadrant of this quadtree
index = 0;
}
else if (rect.y > this.bounds.bottom)
{
// rect fits within the bottom-right quadrant of this quadtree
index = 3;
}
}
return index;
},
/**
* Return all objects that could collide with the given Sprite or Rectangle.
*
* @method Phaser.QuadTree#retrieve
* @param {Phaser.Sprite|Phaser.Rectangle} source - The source object to check the QuadTree against. Either a Sprite or Rectangle.
* @return {array} - Array with all detected objects.
*/
retrieve: function (source) {
if (source instanceof Phaser.Rectangle)
{
var returnObjects = this.objects;
var index = this.getIndex(source);
}
else
{
if (!source.body)
{
return this._empty;
}
var returnObjects = this.objects;
var index = this.getIndex(source.body);
}
if (this.nodes[0])
{
// If rect fits into a subnode ..
if (index !== -1)
{
returnObjects = returnObjects.concat(this.nodes[index].retrieve(source));
}
else
{
// If rect does not fit into a subnode, check it against all subnodes (unrolled for speed)
returnObjects = returnObjects.concat(this.nodes[0].retrieve(source));
returnObjects = returnObjects.concat(this.nodes[1].retrieve(source));
returnObjects = returnObjects.concat(this.nodes[2].retrieve(source));
returnObjects = returnObjects.concat(this.nodes[3].retrieve(source));
}
}
return returnObjects;
},
/**
* Clear the quadtree.
* @method Phaser.QuadTree#clear
*/
clear: function () {
this.objects.length = 0;
var i = this.nodes.length;
while (i--)
{
this.nodes[i].clear();
this.nodes.splice(i, 1);
}
this.nodes.length = 0;
}
};
Phaser.QuadTree.prototype.constructor = Phaser.QuadTree;
/**
* Javascript QuadTree
* @version 1.0
*
* @version 1.3, March 11th 2014
* @author Richard Davey
* The original code was a conversion of the Java code posted to GameDevTuts. However I've tweaked
* it massively to add node indexing, removed lots of temp. var creation and significantly
* increased performance as a result.
*
* Original version at https://github.com/timohausmann/quadtree-js/
*/
/**
* @copyright © 2012 Timo Hausmann
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @author Steven Rogers <soldoutactivist@gmail.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is a stub for the Phaser Net Class.
* It allows you to exclude the default Net from your build, without making Game crash.
*/
var netNoop = function () {};
Phaser.Net = netNoop;
Phaser.Net.prototype = {
isDisabled: true,
getHostName: netNoop,
checkDomainName: netNoop,
updateQueryString: netNoop,
getQueryString: netNoop,
decodeURI: netNoop
};
Phaser.Net.prototype.constructor = Phaser.Net;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is a stub for the Phaser TweenManager.
* It allows you to exclude the default Tween Manager from your build, without making Game crash.
*/
Phaser.TweenManager = function () {};
Phaser.TweenManager.prototype.update = function () {};
Phaser.TweenManager.prototype.removeAll = function () {};
Phaser.TweenManager.prototype.removeFrom = function () {};
Phaser.TweenManager.prototype.constructor = Phaser.TweenManager;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is the core internal game clock.
*
* It manages the elapsed time and calculation of elapsed values, used for game object motion and tweens,
* and also handles the standard Timer pool.
*
* To create a general timed event, use the master {@link Phaser.Timer} accessible through {@link Phaser.Time.events events}.
*
* There are different *types* of time in Phaser:
*
* - ***Game time*** always runs at the speed of time in real life.
*
* Unlike wall-clock time, *game time stops when Phaser is paused*.
*
* Game time is used for {@link Phaser.Timer timer events}.
*
* - ***Physics time*** represents the amount of time given to physics calculations.
*
* *When {@link #slowMotion} is in effect physics time runs slower than game time.*
* Like game time, physics time stops when Phaser is paused.
*
* Physics time is used for physics calculations and {@link Phaser.Tween tweens}.
*
* - {@link https://en.wikipedia.org/wiki/Wall-clock_time ***Wall-clock time***} represents the duration between two events in real life time.
*
* This time is independent of Phaser and always progresses, regardless of if Phaser is paused.
*
* @class Phaser.Time
* @constructor
* @param {Phaser.Game} game A reference to the currently running game.
*/
Phaser.Time = function (game) {
/**
* @property {Phaser.Game} game - Local reference to game.
* @protected
*/
this.game = game;
/**
* The `Date.now()` value when the time was last updated.
* @property {integer} time
* @protected
*/
this.time = 0;
/**
* The `now` when the previous update occurred.
* @property {number} prevTime
* @protected
*/
this.prevTime = 0;
/**
* An increasing value representing cumulative milliseconds since an undisclosed epoch.
*
* While this value is in milliseconds and can be used to compute time deltas,
* it must must _not_ be used with `Date.now()` as it may not use the same epoch / starting reference.
*
* The source may either be from a high-res source (eg. if RAF is available) or the standard Date.now;
* the value can only be relied upon within a particular game instance.
*
* @property {number} now
* @protected
*/
this.now = 0;
/**
* Elapsed time since the last time update, in milliseconds, based on `now`.
*
* This value _may_ include time that the game is paused/inactive.
*
* _Note:_ This is updated only once per game loop - even if multiple logic update steps are done.
* Use {@link Phaser.Timer#physicsTime physicsTime} as a basis of game/logic calculations instead.
*
* @property {number} elapsed
* @see Phaser.Time.time
* @protected
*/
this.elapsed = 0;
/**
* The time in ms since the last time update, in milliseconds, based on `time`.
*
* This value is corrected for game pauses and will be "about zero" after a game is resumed.
*
* _Note:_ This is updated once per game loop - even if multiple logic update steps are done.
* Use {@link Phaser.Timer#physicsTime physicsTime} as a basis of game/logic calculations instead.
*
* @property {integer} elapsedMS
* @protected
*/
this.elapsedMS = 0;
/**
* The physics update delta, in fractional seconds.
*
* This should be used as an applicable multiplier by all logic update steps (eg. `preUpdate/postUpdate/update`)
* to ensure consistent game timing. Game/logic timing can drift from real-world time if the system
* is unable to consistently maintain the desired FPS.
*
* With fixed-step updates this is normally equivalent to `1.0 / desiredFps`.
*
* @property {number} physicsElapsed
*/
this.physicsElapsed = 1 / 60;
/**
* The physics update delta, in milliseconds - equivalent to `physicsElapsed * 1000`.
*
* @property {number} physicsElapsedMS
*/
this.physicsElapsedMS = (1 / 60) * 1000;
/**
* The desiredFps multiplier as used by Game.update.
* @property {integer} desiredFpsMult
* @protected
*/
this.desiredFpsMult = 1.0 / 60;
/**
* The desired frame rate of the game.
*
* This is used is used to calculate the physic/logic multiplier and how to apply catch-up logic updates.
*
* @property {number} _desiredFps
* @private
* @default
*/
this._desiredFps = 60;
/**
* The suggested frame rate for your game, based on an averaged real frame rate.
* This value is only populated if `Time.advancedTiming` is enabled.
*
* _Note:_ This is not available until after a few frames have passed; until then
* it's set to the same value as desiredFps.
*
* @property {number} suggestedFps
* @default
*/
this.suggestedFps = this.desiredFps;
/**
* Scaling factor to make the game move smoothly in slow motion
* - 1.0 = normal speed
* - 2.0 = half speed
* @property {number} slowMotion
* @default
*/
this.slowMotion = 1.0;
/**
* If true then advanced profiling, including the fps rate, fps min/max, suggestedFps and msMin/msMax are updated.
* @property {boolean} advancedTiming
* @default
*/
this.advancedTiming = false;
/**
* Advanced timing result: The number of render frames record in the last second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {integer} frames
* @readonly
*/
this.frames = 0;
/**
* Advanced timing result: Frames per second.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* @property {number} fps
* @readonly
*/
this.fps = 0;
/**
* Advanced timing result: The lowest rate the fps has dropped to.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} fpsMin
*/
this.fpsMin = 1000;
/**
* Advanced timing result: The highest rate the fps has reached (usually no higher than 60fps).
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} fpsMax
*/
this.fpsMax = 0;
/**
* Advanced timing result: The minimum amount of time the game has taken between consecutive frames.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} msMin
* @default
*/
this.msMin = 1000;
/**
* Advanced timing result: The maximum amount of time the game has taken between consecutive frames.
*
* Only calculated if {@link Phaser.Time#advancedTiming advancedTiming} is enabled.
* This value can be manually reset.
* @property {number} msMax
*/
this.msMax = 0;
/**
* Records how long the game was last paused, in milliseconds.
* (This is not updated until the game is resumed.)
* @property {number} pauseDuration
*/
this.pauseDuration = 0;
/**
* @property {number} timeToCall - The value that setTimeout needs to work out when to next update
* @protected
*/
this.timeToCall = 0;
/**
* @property {number} timeExpected - The time when the next call is expected when using setTimer to control the update loop
* @protected
*/
this.timeExpected = 0;
/**
* A {@link Phaser.Timer} object bound to the master clock (this Time object) which events can be added to.
* @property {Phaser.Timer} events
*/
this.events = new Phaser.Timer(this.game, false);
/**
* @property {number} _frameCount - count the number of calls to time.update since the last suggestedFps was calculated
* @private
*/
this._frameCount = 0;
/**
* @property {number} _elapsedAcumulator - sum of the elapsed time since the last suggestedFps was calculated
* @private
*/
this._elapsedAccumulator = 0;
/**
* @property {number} _started - The time at which the Game instance started.
* @private
*/
this._started = 0;
/**
* @property {number} _timeLastSecond - The time (in ms) that the last second counter ticked over.
* @private
*/
this._timeLastSecond = 0;
/**
* @property {number} _pauseStarted - The time the game started being paused.
* @private
*/
this._pauseStarted = 0;
/**
* @property {boolean} _justResumed - Internal value used to recover from the game pause state.
* @private
*/
this._justResumed = false;
/**
* @property {Phaser.Timer[]} _timers - Internal store of Phaser.Timer objects.
* @private
*/
this._timers = [];
};
Phaser.Time.prototype = {
/**
* Called automatically by Phaser.Game after boot. Should not be called directly.
*
* @method Phaser.Time#boot
* @protected
*/
boot: function () {
this._started = Date.now();
this.time = Date.now();
this.events.start();
this.timeExpected = this.time;
},
/**
* Adds an existing Phaser.Timer object to the Timer pool.
*
* @method Phaser.Time#add
* @param {Phaser.Timer} timer - An existing Phaser.Timer object.
* @return {Phaser.Timer} The given Phaser.Timer object.
*/
add: function (timer) {
this._timers.push(timer);
return timer;
},
/**
* Creates a new stand-alone Phaser.Timer object.
*
* @method Phaser.Time#create
* @param {boolean} [autoDestroy=true] - A Timer that is set to automatically destroy itself will do so after all of its events have been dispatched (assuming no looping events).
* @return {Phaser.Timer} The Timer object that was created.
*/
create: function (autoDestroy) {
if (autoDestroy === undefined) { autoDestroy = true; }
var timer = new Phaser.Timer(this.game, autoDestroy);
this._timers.push(timer);
return timer;
},
/**
* Remove all Timer objects, regardless of their state and clears all Timers from the {@link Phaser.Time#events events} timer.
*
* @method Phaser.Time#removeAll
*/
removeAll: function () {
for (var i = 0; i < this._timers.length; i++)
{
this._timers[i].destroy();
}
this._timers = [];
this.events.removeAll();
},
/**
* Refreshes the Time.time and Time.elapsedMS properties from the system clock.
*
* @method Phaser.Time#refresh
*/
refresh: function () {
// Set to the old Date.now value
var previousDateNow = this.time;
// this.time always holds a Date.now value
this.time = Date.now();
// Adjust accordingly.
this.elapsedMS = this.time - previousDateNow;
},
/**
* Updates the game clock and if enabled the advanced timing data. This is called automatically by Phaser.Game.
*
* @method Phaser.Time#update
* @protected
* @param {number} time - The current relative timestamp; see {@link Phaser.Time#now now}.
*/
update: function (time) {
// Set to the old Date.now value
var previousDateNow = this.time;
// this.time always holds a Date.now value
this.time = Date.now();
// Adjust accordingly.
this.elapsedMS = this.time - previousDateNow;
// 'now' is currently still holding the time of the last call, move it into prevTime
this.prevTime = this.now;
// update 'now' to hold the current time
// this.now may hold the RAF high resolution time value if RAF is available (otherwise it also holds Date.now)
this.now = time;
// elapsed time between previous call and now - this could be a high resolution value
this.elapsed = this.now - this.prevTime;
if (this.game.raf._isSetTimeOut)
{
// console.log('Time isSet', this._desiredFps, 'te', this.timeExpected, 'time', time);
// time to call this function again in ms in case we're using timers instead of RequestAnimationFrame to update the game
this.timeToCall = Math.floor(Math.max(0, (1000.0 / this._desiredFps) - (this.timeExpected - time)));
// time when the next call is expected if using timers
this.timeExpected = time + this.timeToCall;
// console.log('Time expect', this.timeExpected);
}
if (this.advancedTiming)
{
this.updateAdvancedTiming();
}
// Paused but still running?
if (!this.game.paused)
{
// Our internal Phaser.Timer
this.events.update(this.time);
if (this._timers.length)
{
this.updateTimers();
}
}
},
/**
* Handles the updating of the Phaser.Timers (if any)
* Called automatically by Time.update.
*
* @method Phaser.Time#updateTimers
* @private
*/
updateTimers: function () {
// Any game level timers
var i = 0;
var len = this._timers.length;
while (i < len)
{
if (this._timers[i].update(this.time))
{
i++;
}
else
{
// Timer requests to be removed
this._timers.splice(i, 1);
len--;
}
}
},
/**
* Handles the updating of the advanced timing values (if enabled)
* Called automatically by Time.update.
*
* @method Phaser.Time#updateAdvancedTiming
* @private
*/
updateAdvancedTiming: function () {
// count the number of time.update calls
this._frameCount++;
this._elapsedAccumulator += this.elapsed;
// occasionally recalculate the suggestedFps based on the accumulated elapsed time
if (this._frameCount >= this._desiredFps * 2)
{
// this formula calculates suggestedFps in multiples of 5 fps
this.suggestedFps = Math.floor(200 / (this._elapsedAccumulator / this._frameCount)) * 5;
this._frameCount = 0;
this._elapsedAccumulator = 0;
}
this.msMin = Math.min(this.msMin, this.elapsed);
this.msMax = Math.max(this.msMax, this.elapsed);
this.frames++;
if (this.now > this._timeLastSecond + 1000)
{
this.fps = Math.round((this.frames * 1000) / (this.now - this._timeLastSecond));
this.fpsMin = Math.min(this.fpsMin, this.fps);
this.fpsMax = Math.max(this.fpsMax, this.fps);
this._timeLastSecond = this.now;
this.frames = 0;
}
},
/**
* Called when the game enters a paused state.
*
* @method Phaser.Time#gamePaused
* @private
*/
gamePaused: function () {
this._pauseStarted = Date.now();
this.events.pause();
var i = this._timers.length;
while (i--)
{
this._timers[i]._pause();
}
},
/**
* Called when the game resumes from a paused state.
*
* @method Phaser.Time#gameResumed
* @private
*/
gameResumed: function () {
// Set the parameter which stores Date.now() to make sure it's correct on resume
this.time = Date.now();
this.pauseDuration = this.time - this._pauseStarted;
this.events.resume();
var i = this._timers.length;
while (i--)
{
this._timers[i]._resume();
}
},
/**
* The number of seconds that have elapsed since the game was started.
*
* @method Phaser.Time#totalElapsedSeconds
* @return {number} The number of seconds that have elapsed since the game was started.
*/
totalElapsedSeconds: function() {
return (this.time - this._started) * 0.001;
},
/**
* How long has passed since the given time.
*
* @method Phaser.Time#elapsedSince
* @param {number} since - The time you want to measure against.
* @return {number} The difference between the given time and now.
*/
elapsedSince: function (since) {
return this.time - since;
},
/**
* How long has passed since the given time (in seconds).
*
* @method Phaser.Time#elapsedSecondsSince
* @param {number} since - The time you want to measure (in seconds).
* @return {number} Duration between given time and now (in seconds).
*/
elapsedSecondsSince: function (since) {
return (this.time - since) * 0.001;
},
/**
* Resets the private _started value to now and removes all currently running Timers.
*
* @method Phaser.Time#reset
*/
reset: function () {
this._started = this.time;
this.removeAll();
}
};
/**
* The desired frame rate of the game.
*
* This is used is used to calculate the physic / logic multiplier and how to apply catch-up logic updates.
*
* @name Phaser.Time#desiredFps
* @property {integer} desiredFps - The desired frame rate of the game. Defaults to 60.
*/
Object.defineProperty(Phaser.Time.prototype, "desiredFps", {
get: function () {
return this._desiredFps;
},
set: function (value) {
this._desiredFps = value;
// Set the physics elapsed time... this will always be 1 / this.desiredFps
// because we're using fixed time steps in game.update
this.physicsElapsed = 1 / value;
this.physicsElapsedMS = this.physicsElapsed * 1000;
this.desiredFpsMult = 1.0 / value;
}
});
Phaser.Time.prototype.constructor = Phaser.Time;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Timer is a way to create and manage {@link Phaser.TimerEvent timer events} that wait for a specific duration and then run a callback.
* Many different timer events, with individual delays, can be added to the same Timer.
*
* All Timer delays are in milliseconds (there are 1000 ms in 1 second); so a delay value of 250 represents a quarter of a second.
*
* Timers are based on real life time, adjusted for game pause durations.
* That is, *timer events are based on elapsed {@link Phaser.Time game time}* and do *not* take physics time or slow motion into account.
*
* @class Phaser.Timer
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {boolean} [autoDestroy=true] - If true, the timer will automatically destroy itself after all the events have been dispatched (assuming no looping events).
*/
Phaser.Timer = function (game, autoDestroy) {
if (autoDestroy === undefined) { autoDestroy = true; }
/**
* @property {Phaser.Game} game - Local reference to game.
* @protected
*/
this.game = game;
/**
* True if the Timer is actively running.
*
* Do not modify this boolean - use {@link Phaser.Timer#pause pause} (and {@link Phaser.Timer#resume resume}) to pause the timer.
* @property {boolean} running
* @default
* @readonly
*/
this.running = false;
/**
* If true, the timer will automatically destroy itself after all the events have been dispatched (assuming no looping events).
* @property {boolean} autoDestroy
*/
this.autoDestroy = autoDestroy;
/**
* @property {boolean} expired - An expired Timer is one in which all of its events have been dispatched and none are pending.
* @readonly
* @default
*/
this.expired = false;
/**
* @property {number} elapsed - Elapsed time since the last frame (in ms).
* @protected
*/
this.elapsed = 0;
/**
* @property {Phaser.TimerEvent[]} events - An array holding all of this timers Phaser.TimerEvent objects. Use the methods add, repeat and loop to populate it.
*/
this.events = [];
/**
* This signal will be dispatched when this Timer has completed which means that there are no more events in the queue.
*
* The signal is supplied with one argument, `timer`, which is this Timer object.
*
* @property {Phaser.Signal} onComplete
*/
this.onComplete = new Phaser.Signal();
/**
* @property {number} nextTick - The time the next tick will occur.
* @readonly
* @protected
*/
this.nextTick = 0;
/**
* @property {number} timeCap - If the difference in time between two frame updates exceeds this value, the event times are reset to avoid catch-up situations.
*/
this.timeCap = 1000;
/**
* @property {boolean} paused - The paused state of the Timer. You can pause the timer by calling Timer.pause() and Timer.resume() or by the game pausing.
* @readonly
* @default
*/
this.paused = false;
/**
* @property {boolean} _codePaused - Was the Timer paused by code or by Game focus loss?
* @private
*/
this._codePaused = false;
/**
* @property {number} _started - The time at which this Timer instance started running.
* @private
* @default
*/
this._started = 0;
/**
* @property {number} _pauseStarted - The time the game started being paused.
* @private
*/
this._pauseStarted = 0;
/**
* @property {number} _pauseTotal - Total paused time.
* @private
*/
this._pauseTotal = 0;
/**
* @property {number} _now - The current start-time adjusted time.
* @private
*/
this._now = Date.now();
/**
* @property {number} _len - Temp. array length variable.
* @private
*/
this._len = 0;
/**
* @property {number} _marked - Temp. counter variable.
* @private
*/
this._marked = 0;
/**
* @property {number} _i - Temp. array counter variable.
* @private
*/
this._i = 0;
/**
* @property {number} _diff - Internal cache var.
* @private
*/
this._diff = 0;
/**
* @property {number} _newTick - Internal cache var.
* @private
*/
this._newTick = 0;
};
/**
* Number of milliseconds in a minute.
* @constant
* @type {integer}
*/
Phaser.Timer.MINUTE = 60000;
/**
* Number of milliseconds in a second.
* @constant
* @type {integer}
*/
Phaser.Timer.SECOND = 1000;
/**
* Number of milliseconds in half a second.
* @constant
* @type {integer}
*/
Phaser.Timer.HALF = 500;
/**
* Number of milliseconds in a quarter of a second.
* @constant
* @type {integer}
*/
Phaser.Timer.QUARTER = 250;
Phaser.Timer.prototype = {
/**
* Creates a new TimerEvent on this Timer.
*
* Use {@link Phaser.Timer#add}, {@link Phaser.Timer#repeat}, or {@link Phaser.Timer#loop} methods to create a new event.
*
* @method Phaser.Timer#create
* @private
* @param {integer} delay - The number of milliseconds, in {@link Phaser.Time game time}, before the timer event occurs.
* @param {boolean} loop - Should the event loop or not?
* @param {number} repeatCount - The number of times the event will repeat.
* @param {function} callback - The callback that will be called when the timer event occurs.
* @param {object} callbackContext - The context in which the callback will be called.
* @param {any[]} arguments - The values to be sent to your callback function when it is called.
* @return {Phaser.TimerEvent} The Phaser.TimerEvent object that was created.
*/
create: function (delay, loop, repeatCount, callback, callbackContext, args) {
delay = Math.round(delay);
var tick = delay;
if (this._now === 0)
{
tick += this.game.time.time;
}
else
{
tick += this._now;
}
var event = new Phaser.TimerEvent(this, delay, tick, repeatCount, loop, callback, callbackContext, args);
this.events.push(event);
this.order();
this.expired = false;
return event;
},
/**
* Adds a new Event to this Timer.
*
* The event will fire after the given amount of `delay` in milliseconds has passed, once the Timer has started running.
* The delay is in relation to when the Timer starts, not the time it was added. If the Timer is already running the delay will be calculated based on the timers current time.
*
* Make sure to call {@link Phaser.Timer#start start} after adding all of the Events you require for this Timer.
*
* @method Phaser.Timer#add
* @param {integer} delay - The number of milliseconds, in {@link Phaser.Time game time}, before the timer event occurs.
* @param {function} callback - The callback that will be called when the timer event occurs.
* @param {object} callbackContext - The context in which the callback will be called.
* @param {...*} arguments - Additional arguments that will be supplied to the callback.
* @return {Phaser.TimerEvent} The Phaser.TimerEvent object that was created.
*/
add: function (delay, callback, callbackContext) {
return this.create(delay, false, 0, callback, callbackContext, Array.prototype.slice.call(arguments, 3));
},
/**
* Adds a new TimerEvent that will always play through once and then repeat for the given number of iterations.
*
* The event will fire after the given amount of `delay` in milliseconds has passed, once the Timer has started running.
* The delay is in relation to when the Timer starts, not the time it was added.
* If the Timer is already running the delay will be calculated based on the timers current time.
*
* Make sure to call {@link Phaser.Timer#start start} after adding all of the Events you require for this Timer.
*
* @method Phaser.Timer#repeat
* @param {integer} delay - The number of milliseconds, in {@link Phaser.Time game time}, before the timer event occurs.
* @param {number} repeatCount - The number of times the event will repeat once is has finished playback. A repeatCount of 1 means it will repeat itself once, playing the event twice in total.
* @param {function} callback - The callback that will be called when the timer event occurs.
* @param {object} callbackContext - The context in which the callback will be called.
* @param {...*} arguments - Additional arguments that will be supplied to the callback.
* @return {Phaser.TimerEvent} The Phaser.TimerEvent object that was created.
*/
repeat: function (delay, repeatCount, callback, callbackContext) {
return this.create(delay, false, repeatCount, callback, callbackContext, Array.prototype.slice.call(arguments, 4));
},
/**
* Adds a new looped Event to this Timer that will repeat forever or until the Timer is stopped.
*
* The event will fire after the given amount of `delay` in milliseconds has passed, once the Timer has started running.
* The delay is in relation to when the Timer starts, not the time it was added. If the Timer is already running the delay will be calculated based on the timers current time.
*
* Make sure to call {@link Phaser.Timer#start start} after adding all of the Events you require for this Timer.
*
* @method Phaser.Timer#loop
* @param {integer} delay - The number of milliseconds, in {@link Phaser.Time game time}, before the timer event occurs.
* @param {function} callback - The callback that will be called when the timer event occurs.
* @param {object} callbackContext - The context in which the callback will be called.
* @param {...*} arguments - Additional arguments that will be supplied to the callback.
* @return {Phaser.TimerEvent} The Phaser.TimerEvent object that was created.
*/
loop: function (delay, callback, callbackContext) {
return this.create(delay, true, 0, callback, callbackContext, Array.prototype.slice.call(arguments, 3));
},
/**
* Starts this Timer running.
* @method Phaser.Timer#start
* @param {integer} [delay=0] - The number of milliseconds, in {@link Phaser.Time game time}, that should elapse before the Timer will start.
*/
start: function (delay) {
if (this.running)
{
return;
}
this._started = this.game.time.time + (delay || 0);
this.running = true;
for (var i = 0; i < this.events.length; i++)
{
this.events[i].tick = this.events[i].delay + this._started;
}
},
/**
* Stops this Timer from running. Does not cause it to be destroyed if autoDestroy is set to true.
* @method Phaser.Timer#stop
* @param {boolean} [clearEvents=true] - If true all the events in Timer will be cleared, otherwise they will remain.
*/
stop: function (clearEvents) {
this.running = false;
if (clearEvents === undefined) { clearEvents = true; }
if (clearEvents)
{
this.events.length = 0;
}
},
/**
* Removes a pending TimerEvent from the queue.
* @param {Phaser.TimerEvent} event - The event to remove from the queue.
* @method Phaser.Timer#remove
*/
remove: function (event) {
for (var i = 0; i < this.events.length; i++)
{
if (this.events[i] === event)
{
this.events[i].pendingDelete = true;
return true;
}
}
return false;
},
/**
* Orders the events on this Timer so they are in tick order.
* This is called automatically when new events are created.
* @method Phaser.Timer#order
* @protected
*/
order: function () {
if (this.events.length > 0)
{
// Sort the events so the one with the lowest tick is first
this.events.sort(this.sortHandler);
this.nextTick = this.events[0].tick;
}
},
/**
* Sort handler used by Phaser.Timer.order.
* @method Phaser.Timer#sortHandler
* @private
*/
sortHandler: function (a, b) {
if (a.tick < b.tick)
{
return -1;
}
else if (a.tick > b.tick)
{
return 1;
}
return 0;
},
/**
* Clears any events from the Timer which have pendingDelete set to true and then resets the private _len and _i values.
*
* @method Phaser.Timer#clearPendingEvents
* @protected
*/
clearPendingEvents: function () {
this._i = this.events.length;
while (this._i--)
{
if (this.events[this._i].pendingDelete)
{
this.events.splice(this._i, 1);
}
}
this._len = this.events.length;
this._i = 0;
},
/**
* The main Timer update event, called automatically by Phaser.Time.update.
*
* @method Phaser.Timer#update
* @protected
* @param {number} time - The time from the core game clock.
* @return {boolean} True if there are still events waiting to be dispatched, otherwise false if this Timer can be destroyed.
*/
update: function (time) {
if (this.paused)
{
return true;
}
this.elapsed = time - this._now;
this._now = time;
// spike-dislike
if (this.elapsed > this.timeCap)
{
// For some reason the time between now and the last time the game was updated was larger than our timeCap.
// This can happen if the Stage.disableVisibilityChange is true and you swap tabs, which makes the raf pause.
// In this case we need to adjust the TimerEvents and nextTick.
this.adjustEvents(time - this.elapsed);
}
this._marked = 0;
// Clears events marked for deletion and resets _len and _i to 0.
this.clearPendingEvents();
if (this.running && this._now >= this.nextTick && this._len > 0)
{
while (this._i < this._len && this.running)
{
if (this._now >= this.events[this._i].tick && !this.events[this._i].pendingDelete)
{
// (now + delay) - (time difference from last tick to now)
this._newTick = (this._now + this.events[this._i].delay) - (this._now - this.events[this._i].tick);
if (this._newTick < 0)
{
this._newTick = this._now + this.events[this._i].delay;
}
if (this.events[this._i].loop === true)
{
this.events[this._i].tick = this._newTick;
this.events[this._i].callback.apply(this.events[this._i].callbackContext, this.events[this._i].args);
}
else if (this.events[this._i].repeatCount > 0)
{
this.events[this._i].repeatCount--;
this.events[this._i].tick = this._newTick;
this.events[this._i].callback.apply(this.events[this._i].callbackContext, this.events[this._i].args);
}
else
{
this._marked++;
this.events[this._i].pendingDelete = true;
this.events[this._i].callback.apply(this.events[this._i].callbackContext, this.events[this._i].args);
}
this._i++;
}
else
{
break;
}
}
// Are there any events left?
if (this.events.length > this._marked)
{
this.order();
}
else
{
this.expired = true;
this.onComplete.dispatch(this);
}
}
if (this.expired && this.autoDestroy)
{
return false;
}
else
{
return true;
}
},
/**
* Pauses the Timer and all events in the queue.
* @method Phaser.Timer#pause
*/
pause: function () {
if (!this.running)
{
return;
}
this._codePaused = true;
if (this.paused)
{
return;
}
this._pauseStarted = this.game.time.time;
this.paused = true;
},
/**
* Internal pause/resume control - user code should use Timer.pause instead.
* @method Phaser.Timer#_pause
* @private
*/
_pause: function () {
if (this.paused || !this.running)
{
return;
}
this._pauseStarted = this.game.time.time;
this.paused = true;
},
/**
* Adjusts the time of all pending events and the nextTick by the given baseTime.
*
* @method Phaser.Timer#adjustEvents
* @protected
*/
adjustEvents: function (baseTime) {
for (var i = 0; i < this.events.length; i++)
{
if (!this.events[i].pendingDelete)
{
// Work out how long there would have been from when the game paused until the events next tick
var t = this.events[i].tick - baseTime;
if (t < 0)
{
t = 0;
}
// Add the difference on to the time now
this.events[i].tick = this._now + t;
}
}
var d = this.nextTick - baseTime;
if (d < 0)
{
this.nextTick = this._now;
}
else
{
this.nextTick = this._now + d;
}
},
/**
* Resumes the Timer and updates all pending events.
*
* @method Phaser.Timer#resume
*/
resume: function () {
if (!this.paused)
{
return;
}
var now = this.game.time.time;
this._pauseTotal += now - this._now;
this._now = now;
this.adjustEvents(this._pauseStarted);
this.paused = false;
this._codePaused = false;
},
/**
* Internal pause/resume control - user code should use Timer.resume instead.
* @method Phaser.Timer#_resume
* @private
*/
_resume: function () {
if (this._codePaused)
{
return;
}
else
{
this.resume();
}
},
/**
* Removes all Events from this Timer and all callbacks linked to onComplete, but leaves the Timer running.
* The onComplete callbacks won't be called.
*
* @method Phaser.Timer#removeAll
*/
removeAll: function () {
this.onComplete.removeAll();
this.events.length = 0;
this._len = 0;
this._i = 0;
},
/**
* Destroys this Timer. Any pending Events are not dispatched.
* The onComplete callbacks won't be called.
*
* @method Phaser.Timer#destroy
*/
destroy: function () {
this.onComplete.removeAll();
this.running = false;
this.events = [];
this._len = 0;
this._i = 0;
}
};
/**
* @name Phaser.Timer#next
* @property {number} next - The time at which the next event will occur.
* @readonly
*/
Object.defineProperty(Phaser.Timer.prototype, "next", {
get: function () {
return this.nextTick;
}
});
/**
* @name Phaser.Timer#duration
* @property {number} duration - The duration in ms remaining until the next event will occur.
* @readonly
*/
Object.defineProperty(Phaser.Timer.prototype, "duration", {
get: function () {
if (this.running && this.nextTick > this._now)
{
return this.nextTick - this._now;
}
else
{
return 0;
}
}
});
/**
* @name Phaser.Timer#length
* @property {number} length - The number of pending events in the queue.
* @readonly
*/
Object.defineProperty(Phaser.Timer.prototype, "length", {
get: function () {
return this.events.length;
}
});
/**
* @name Phaser.Timer#ms
* @property {number} ms - The duration in milliseconds that this Timer has been running for.
* @readonly
*/
Object.defineProperty(Phaser.Timer.prototype, "ms", {
get: function () {
if (this.running)
{
return this._now - this._started - this._pauseTotal;
}
else
{
return 0;
}
}
});
/**
* @name Phaser.Timer#seconds
* @property {number} seconds - The duration in seconds that this Timer has been running for.
* @readonly
*/
Object.defineProperty(Phaser.Timer.prototype, "seconds", {
get: function () {
if (this.running)
{
return this.ms * 0.001;
}
else
{
return 0;
}
}
});
Phaser.Timer.prototype.constructor = Phaser.Timer;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A TimerEvent is a single event that is processed by a Phaser.Timer.
*
* It consists of a delay, which is a value in milliseconds after which the event will fire.
* When the event fires it calls a specific callback with the specified arguments.
*
* TimerEvents are removed by their parent timer once finished firing or repeating.
*
* Use {@link Phaser.Timer#add}, {@link Phaser.Timer#repeat}, or {@link Phaser.Timer#loop} methods to create a new event.
*
* @class Phaser.TimerEvent
* @constructor
* @param {Phaser.Timer} timer - The Timer object that this TimerEvent belongs to.
* @param {number} delay - The delay in ms at which this TimerEvent fires.
* @param {number} tick - The tick is the next game clock time that this event will fire at.
* @param {number} repeatCount - If this TimerEvent repeats it will do so this many times.
* @param {boolean} loop - True if this TimerEvent loops, otherwise false.
* @param {function} callback - The callback that will be called when the TimerEvent occurs.
* @param {object} callbackContext - The context in which the callback will be called.
* @param {any[]} arguments - Additional arguments to be passed to the callback.
*/
Phaser.TimerEvent = function (timer, delay, tick, repeatCount, loop, callback, callbackContext, args) {
/**
* @property {Phaser.Timer} timer - The Timer object that this TimerEvent belongs to.
* @protected
* @readonly
*/
this.timer = timer;
/**
* @property {number} delay - The delay in ms at which this TimerEvent fires.
*/
this.delay = delay;
/**
* @property {number} tick - The tick is the next game clock time that this event will fire at.
*/
this.tick = tick;
/**
* @property {number} repeatCount - If this TimerEvent repeats it will do so this many times.
*/
this.repeatCount = repeatCount - 1;
/**
* @property {boolean} loop - True if this TimerEvent loops, otherwise false.
*/
this.loop = loop;
/**
* @property {function} callback - The callback that will be called when the TimerEvent occurs.
*/
this.callback = callback;
/**
* @property {object} callbackContext - The context in which the callback will be called.
*/
this.callbackContext = callbackContext;
/**
* @property {any[]} arguments - Additional arguments to be passed to the callback.
*/
this.args = args;
/**
* @property {boolean} pendingDelete - A flag that controls if the TimerEvent is pending deletion.
* @protected
*/
this.pendingDelete = false;
};
Phaser.TimerEvent.prototype.constructor = Phaser.TimerEvent;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Animation Manager is used to add, play and update Phaser Animations.
* Any Game Object such as Phaser.Sprite that supports animation contains a single AnimationManager instance.
*
* @class Phaser.AnimationManager
* @constructor
* @param {Phaser.Sprite} sprite - A reference to the Game Object that owns this AnimationManager.
*/
Phaser.AnimationManager = function (sprite) {
/**
* @property {Phaser.Sprite} sprite - A reference to the parent Sprite that owns this AnimationManager.
*/
this.sprite = sprite;
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = sprite.game;
/**
* The currently displayed Frame of animation, if any.
* This property is only set once an Animation starts playing. Until that point it remains set as `null`.
*
* @property {Phaser.Frame} currentFrame
* @default
*/
this.currentFrame = null;
/**
* @property {Phaser.Animation} currentAnim - The currently displayed animation, if any.
* @default
*/
this.currentAnim = null;
/**
* @property {boolean} updateIfVisible - Should the animation data continue to update even if the Sprite.visible is set to false.
* @default
*/
this.updateIfVisible = true;
/**
* @property {boolean} isLoaded - Set to true once animation data has been loaded.
* @default
*/
this.isLoaded = false;
/**
* @property {Phaser.FrameData} _frameData - A temp. var for holding the currently playing Animations FrameData.
* @private
* @default
*/
this._frameData = null;
/**
* @property {object} _anims - An internal object that stores all of the Animation instances.
* @private
*/
this._anims = {};
/**
* @property {object} _outputFrames - An internal object to help avoid gc.
* @private
*/
this._outputFrames = [];
};
Phaser.AnimationManager.prototype = {
/**
* Loads FrameData into the internal temporary vars and resets the frame index to zero.
* This is called automatically when a new Sprite is created.
*
* @method Phaser.AnimationManager#loadFrameData
* @private
* @param {Phaser.FrameData} frameData - The FrameData set to load.
* @param {string|number} frame - The frame to default to.
* @return {boolean} Returns `true` if the frame data was loaded successfully, otherwise `false`
*/
loadFrameData: function (frameData, frame) {
if (frameData === undefined)
{
return false;
}
if (this.isLoaded)
{
// We need to update the frameData that the animations are using
for (var anim in this._anims)
{
this._anims[anim].updateFrameData(frameData);
}
}
this._frameData = frameData;
if (frame === undefined || frame === null)
{
this.frame = 0;
}
else
{
if (typeof frame === 'string')
{
this.frameName = frame;
}
else
{
this.frame = frame;
}
}
this.isLoaded = true;
return true;
},
/**
* Loads FrameData into the internal temporary vars and resets the frame index to zero.
* This is called automatically when a new Sprite is created.
*
* @method Phaser.AnimationManager#copyFrameData
* @private
* @param {Phaser.FrameData} frameData - The FrameData set to load.
* @param {string|number} frame - The frame to default to.
* @return {boolean} Returns `true` if the frame data was loaded successfully, otherwise `false`
*/
copyFrameData: function (frameData, frame) {
this._frameData = frameData.clone();
if (this.isLoaded)
{
// We need to update the frameData that the animations are using
for (var anim in this._anims)
{
this._anims[anim].updateFrameData(this._frameData);
}
}
if (frame === undefined || frame === null)
{
this.frame = 0;
}
else
{
if (typeof frame === 'string')
{
this.frameName = frame;
}
else
{
this.frame = frame;
}
}
this.isLoaded = true;
return true;
},
/**
* Adds a new animation under the given key. Optionally set the frames, frame rate and loop.
* Animations added in this way are played back with the play function.
*
* @method Phaser.AnimationManager#add
* @param {string} name - The unique (within this Sprite) name for the animation, i.e. "run", "fire", "walk".
* @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.Animation} The Animation object that was created.
*/
add: function (name, frames, frameRate, loop, useNumericIndex) {
frames = frames || [];
frameRate = frameRate || 60;
if (loop === undefined) { loop = false; }
// If they didn't set the useNumericIndex then let's at least try and guess it
if (useNumericIndex === undefined)
{
if (frames && typeof frames[0] === 'number')
{
useNumericIndex = true;
}
else
{
useNumericIndex = false;
}
}
this._outputFrames = [];
this._frameData.getFrameIndexes(frames, useNumericIndex, this._outputFrames);
this._anims[name] = new Phaser.Animation(this.game, this.sprite, name, this._frameData, this._outputFrames, frameRate, loop);
this.currentAnim = this._anims[name];
if (this.sprite.tilingTexture)
{
this.sprite.refreshTexture = true;
}
return this._anims[name];
},
/**
* Check whether the frames in the given array are valid and exist.
*
* @method Phaser.AnimationManager#validateFrames
* @param {Array} frames - An array of frames to be validated.
* @param {boolean} [useNumericIndex=true] - Validate the frames based on their numeric index (true) or string index (false)
* @return {boolean} True if all given Frames are valid, otherwise false.
*/
validateFrames: function (frames, useNumericIndex) {
if (useNumericIndex === undefined) { useNumericIndex = true; }
for (var i = 0; i < frames.length; i++)
{
if (useNumericIndex === true)
{
if (frames[i] > this._frameData.total)
{
return false;
}
}
else
{
if (this._frameData.checkFrameName(frames[i]) === false)
{
return false;
}
}
}
return true;
},
/**
* Play an animation based on the given key. The animation should previously have been added via `animations.add`
*
* If the requested animation is already playing this request will be ignored.
* If you need to reset an already running animation do so directly on the Animation object itself.
*
* @method Phaser.AnimationManager#play
* @param {string} name - The name of the animation to be played, e.g. "fire", "walk", "jump".
* @param {number} [frameRate=null] - The framerate to play the animation at. The speed is given in frames per second. If not provided the previously set frameRate of the Animation is used.
* @param {boolean} [loop=false] - Should the animation be looped after playback. If not provided the previously set loop value of the Animation is used.
* @param {boolean} [killOnComplete=false] - If set to true when the animation completes (only happens if loop=false) the parent Sprite will be killed.
* @return {Phaser.Animation} A reference to playing Animation instance.
*/
play: function (name, frameRate, loop, killOnComplete) {
if (this._anims[name])
{
if (this.currentAnim === this._anims[name])
{
if (this.currentAnim.isPlaying === false)
{
this.currentAnim.paused = false;
return this.currentAnim.play(frameRate, loop, killOnComplete);
}
return this.currentAnim;
}
else
{
if (this.currentAnim && this.currentAnim.isPlaying)
{
this.currentAnim.stop();
}
this.currentAnim = this._anims[name];
this.currentAnim.paused = false;
this.currentFrame = this.currentAnim.currentFrame;
return this.currentAnim.play(frameRate, loop, killOnComplete);
}
}
},
/**
* Stop playback of an animation. If a name is given that specific animation is stopped, otherwise the current animation is stopped.
* The currentAnim property of the AnimationManager is automatically set to the animation given.
*
* @method Phaser.AnimationManager#stop
* @param {string} [name=null] - The name of the animation to be stopped, e.g. "fire". If none is given the currently running animation is stopped.
* @param {boolean} [resetFrame=false] - When the animation is stopped should the currentFrame be set to the first frame of the animation (true) or paused on the last frame displayed (false)
*/
stop: function (name, resetFrame) {
if (resetFrame === undefined) { resetFrame = false; }
if (this.currentAnim && (typeof name !== 'string' || name === this.currentAnim.name))
{
this.currentAnim.stop(resetFrame);
}
},
/**
* The main update function is called by the Sprites update loop. It's responsible for updating animation frames and firing related events.
*
* @method Phaser.AnimationManager#update
* @protected
* @return {boolean} True if a new animation frame has been set, otherwise false.
*/
update: function () {
if (this.updateIfVisible && !this.sprite.visible)
{
return false;
}
if (this.currentAnim && this.currentAnim.update())
{
this.currentFrame = this.currentAnim.currentFrame;
return true;
}
return false;
},
/**
* Advances by the given number of frames in the current animation, taking the loop value into consideration.
*
* @method Phaser.AnimationManager#next
* @param {number} [quantity=1] - The number of frames to advance.
*/
next: function (quantity) {
if (this.currentAnim)
{
this.currentAnim.next(quantity);
this.currentFrame = this.currentAnim.currentFrame;
}
},
/**
* Moves backwards the given number of frames in the current animation, taking the loop value into consideration.
*
* @method Phaser.AnimationManager#previous
* @param {number} [quantity=1] - The number of frames to move back.
*/
previous: function (quantity) {
if (this.currentAnim)
{
this.currentAnim.previous(quantity);
this.currentFrame = this.currentAnim.currentFrame;
}
},
/**
* Returns an animation that was previously added by name.
*
* @method Phaser.AnimationManager#getAnimation
* @param {string} name - The name of the animation to be returned, e.g. "fire".
* @return {Phaser.Animation} The Animation instance, if found, otherwise null.
*/
getAnimation: function (name) {
if (typeof name === 'string')
{
if (this._anims[name])
{
return this._anims[name];
}
}
return null;
},
/**
* Refreshes the current frame data back to the parent Sprite and also resets the texture data.
*
* @method Phaser.AnimationManager#refreshFrame
*/
refreshFrame: function () {
// TODO
// this.sprite.setTexture(PIXI.TextureCache[this.currentFrame.uuid]);
},
/**
* Destroys all references this AnimationManager contains.
* Iterates through the list of animations stored in this manager and calls destroy on each of them.
*
* @method Phaser.AnimationManager#destroy
*/
destroy: function () {
var anim = null;
for (var anim in this._anims)
{
if (this._anims.hasOwnProperty(anim))
{
this._anims[anim].destroy();
}
}
this._anims = {};
this._outputFrames = [];
this._frameData = null;
this.currentAnim = null;
this.currentFrame = null;
this.sprite = null;
this.game = null;
}
};
Phaser.AnimationManager.prototype.constructor = Phaser.AnimationManager;
/**
* @name Phaser.AnimationManager#frameData
* @property {Phaser.FrameData} frameData - The current animations FrameData.
* @readonly
*/
Object.defineProperty(Phaser.AnimationManager.prototype, 'frameData', {
get: function () {
return this._frameData;
}
});
/**
* @name Phaser.AnimationManager#frameTotal
* @property {number} frameTotal - The total number of frames in the currently loaded FrameData, or -1 if no FrameData is loaded.
* @readonly
*/
Object.defineProperty(Phaser.AnimationManager.prototype, 'frameTotal', {
get: function () {
return this._frameData.total;
}
});
/**
* @name Phaser.AnimationManager#paused
* @property {boolean} paused - Gets and sets the paused state of the current animation.
*/
Object.defineProperty(Phaser.AnimationManager.prototype, 'paused', {
get: function () {
return this.currentAnim.isPaused;
},
set: function (value) {
this.currentAnim.paused = value;
}
});
/**
* @name Phaser.AnimationManager#name
* @property {string} name - Gets the current animation name, if set.
*/
Object.defineProperty(Phaser.AnimationManager.prototype, 'name', {
get: function () {
if (this.currentAnim)
{
return this.currentAnim.name;
}
}
});
/**
* @name Phaser.AnimationManager#frame
* @property {number} frame - Gets or sets the current frame index and updates the Texture Cache for display.
*/
Object.defineProperty(Phaser.AnimationManager.prototype, 'frame', {
get: function () {
if (this.currentFrame)
{
return this.currentFrame.index;
}
},
set: function (value) {
if (typeof value === 'number' && this._frameData && this._frameData.getFrame(value) !== null)
{
this.currentFrame = this._frameData.getFrame(value);
if (this.currentFrame)
{
this.sprite.setFrame(this.currentFrame);
}
}
}
});
/**
* @name Phaser.AnimationManager#frameName
* @property {string} frameName - Gets or sets the current frame name and updates the Texture Cache for display.
*/
Object.defineProperty(Phaser.AnimationManager.prototype, 'frameName', {
get: function () {
if (this.currentFrame)
{
return this.currentFrame.name;
}
},
set: function (value) {
if (typeof value === 'string' && this._frameData && this._frameData.getFrameByName(value) !== null)
{
this.currentFrame = this._frameData.getFrameByName(value);
if (this.currentFrame)
{
this._frameIndex = this.currentFrame.index;
this.sprite.setFrame(this.currentFrame);
}
}
else
{
console.warn('Cannot set frameName: ' + value);
}
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* An Animation instance contains a single animation and the controls to play it.
*
* It is created by the AnimationManager, consists of Animation.Frame objects and belongs to a single Game Object such as a Sprite.
*
* @class Phaser.Animation
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {Phaser.Sprite} parent - A reference to the owner of this Animation.
* @param {string} name - The unique name for this animation, used in playback commands.
* @param {Phaser.FrameData} frameData - The FrameData object that contains all frames used by this Animation.
* @param {number[]|string[]} frames - An array of numbers or strings indicating which frames to play in which order.
* @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.
*/
Phaser.Animation = function (game, parent, name, frameData, frames, frameRate, loop) {
if (loop === undefined) { loop = false; }
/**
* @property {Phaser.Game} game - A reference to the currently running Game.
*/
this.game = game;
/**
* @property {Phaser.Sprite} _parent - A reference to the parent Sprite that owns this Animation.
* @private
*/
this._parent = parent;
/**
* @property {Phaser.FrameData} _frameData - The FrameData the Animation uses.
* @private
*/
this._frameData = frameData;
/**
* @property {string} name - The user defined name given to this Animation.
*/
this.name = name;
/**
* @property {array} _frames
* @private
*/
this._frames = [];
this._frames = this._frames.concat(frames);
/**
* @property {number} delay - The delay in ms between each frame of the Animation, based on the given frameRate.
*/
this.delay = 1000 / frameRate;
/**
* @property {boolean} loop - The loop state of the Animation.
*/
this.loop = loop;
/**
* @property {number} loopCount - The number of times the animation has looped since it was last started.
*/
this.loopCount = 0;
/**
* @property {boolean} killOnComplete - Should the parent of this Animation be killed when the animation completes?
* @default
*/
this.killOnComplete = false;
/**
* @property {boolean} isFinished - The finished state of the Animation. Set to true once playback completes, false during playback.
* @default
*/
this.isFinished = false;
/**
* @property {boolean} isPlaying - The playing state of the Animation. Set to false once playback completes, true during playback.
* @default
*/
this.isPlaying = false;
/**
* @property {boolean} isPaused - The paused state of the Animation.
* @default
*/
this.isPaused = false;
/**
* @property {boolean} _pauseStartTime - The time the animation paused.
* @private
* @default
*/
this._pauseStartTime = 0;
/**
* @property {number} _frameIndex
* @private
* @default
*/
this._frameIndex = 0;
/**
* @property {number} _frameDiff
* @private
* @default
*/
this._frameDiff = 0;
/**
* @property {number} _frameSkip
* @private
* @default
*/
this._frameSkip = 1;
/**
* @property {Phaser.Frame} currentFrame - The currently displayed frame of the Animation.
*/
this.currentFrame = this._frameData.getFrame(this._frames[this._frameIndex]);
/**
* @property {Phaser.Signal} onStart - This event is dispatched when this Animation starts playback.
*/
this.onStart = new Phaser.Signal();
/**
* This event is dispatched when the Animation changes frame.
* By default this event is disabled due to its intensive nature. Enable it with: `Animation.enableUpdate = true`.
* Note that the event is only dispatched with the current frame. In a low-FPS environment Animations
* will automatically frame-skip to try and claw back time, so do not base your code on expecting to
* receive a perfectly sequential set of frames from this event.
* @property {Phaser.Signal|null} onUpdate
* @default
*/
this.onUpdate = null;
/**
* @property {Phaser.Signal} onComplete - This event is dispatched when this Animation completes playback. If the animation is set to loop this is never fired, listen for onLoop instead.
*/
this.onComplete = new Phaser.Signal();
/**
* @property {Phaser.Signal} onLoop - This event is dispatched when this Animation loops.
*/
this.onLoop = new Phaser.Signal();
/**
* @property {boolean} isReversed - Indicates if the animation will play backwards.
* @default
*/
this.isReversed = false;
// Set-up some event listeners
this.game.onPause.add(this.onPause, this);
this.game.onResume.add(this.onResume, this);
};
Phaser.Animation.prototype = {
/**
* Plays this animation.
*
* @method Phaser.Animation#play
* @param {number} [frameRate=null] - The framerate to play the animation at. The speed is given in frames per second. If not provided the previously set frameRate of the Animation is used.
* @param {boolean} [loop=false] - Should the animation be looped after playback. If not provided the previously set loop value of the Animation is used.
* @param {boolean} [killOnComplete=false] - If set to true when the animation completes (only happens if loop=false) the parent Sprite will be killed.
* @return {Phaser.Animation} - A reference to this Animation instance.
*/
play: function (frameRate, loop, killOnComplete) {
if (typeof frameRate === 'number')
{
// If they set a new frame rate then use it, otherwise use the one set on creation
this.delay = 1000 / frameRate;
}
if (typeof loop === 'boolean')
{
// If they set a new loop value then use it, otherwise use the one set on creation
this.loop = loop;
}
if (typeof killOnComplete !== 'undefined')
{
// Remove the parent sprite once the animation has finished?
this.killOnComplete = killOnComplete;
}
this.isPlaying = true;
this.isFinished = false;
this.paused = false;
this.loopCount = 0;
this._timeLastFrame = this.game.time.time;
this._timeNextFrame = this.game.time.time + this.delay;
this._frameIndex = this.isReversed ? this._frames.length - 1 : 0;
this.updateCurrentFrame(false, true);
this._parent.events.onAnimationStart$dispatch(this._parent, this);
this.onStart.dispatch(this._parent, this);
this._parent.animations.currentAnim = this;
this._parent.animations.currentFrame = this.currentFrame;
return this;
},
/**
* Sets this animation back to the first frame and restarts the animation.
*
* @method Phaser.Animation#restart
*/
restart: function () {
this.isPlaying = true;
this.isFinished = false;
this.paused = false;
this.loopCount = 0;
this._timeLastFrame = this.game.time.time;
this._timeNextFrame = this.game.time.time + this.delay;
this._frameIndex = 0;
this.currentFrame = this._frameData.getFrame(this._frames[this._frameIndex]);
this._parent.setFrame(this.currentFrame);
this._parent.animations.currentAnim = this;
this._parent.animations.currentFrame = this.currentFrame;
this.onStart.dispatch(this._parent, this);
},
/**
* Reverses the animation direction.
*
* @method Phaser.Animation#reverse
* @return {Phaser.Animation} The animation instance.
*/
reverse: function () {
this.reversed = !this.reversed;
return this;
},
/**
* Reverses the animation direction for the current/next animation only
* Once the onComplete event is called this method will be called again and revert
* the reversed state.
*
* @method Phaser.Animation#reverseOnce
* @return {Phaser.Animation} The animation instance.
*/
reverseOnce: function () {
this.onComplete.addOnce(this.reverse, this);
return this.reverse();
},
/**
* Sets this animations playback to a given frame with the given ID.
*
* @method Phaser.Animation#setFrame
* @param {string|number} [frameId] - The identifier of the frame to set. Can be the name of the frame, the sprite index of the frame, or the animation-local frame index.
* @param {boolean} [useLocalFrameIndex=false] - If you provide a number for frameId, should it use the numeric indexes of the frameData, or the 0-indexed frame index local to the animation.
*/
setFrame: function(frameId, useLocalFrameIndex) {
var frameIndex;
if (useLocalFrameIndex === undefined)
{
useLocalFrameIndex = false;
}
// Find the index to the desired frame.
if (typeof frameId === "string")
{
for (var i = 0; i < this._frames.length; i++)
{
if (this._frameData.getFrame(this._frames[i]).name === frameId)
{
frameIndex = i;
}
}
}
else if (typeof frameId === "number")
{
if (useLocalFrameIndex)
{
frameIndex = frameId;
}
else
{
for (var i = 0; i < this._frames.length; i++)
{
if (this._frames[i] === frameId)
{
frameIndex = i;
}
}
}
}
if (frameIndex)
{
// Set the current frame index to the found index. Subtract 1 so that it animates to the desired frame on update.
this._frameIndex = frameIndex - 1;
// Make the animation update at next update
this._timeNextFrame = this.game.time.time;
this.update();
}
},
/**
* Stops playback of this animation and set it to a finished state. If a resetFrame is provided it will stop playback and set frame to the first in the animation.
* If `dispatchComplete` is true it will dispatch the complete events, otherwise they'll be ignored.
*
* @method Phaser.Animation#stop
* @param {boolean} [resetFrame=false] - If true after the animation stops the currentFrame value will be set to the first frame in this animation.
* @param {boolean} [dispatchComplete=false] - Dispatch the Animation.onComplete and parent.onAnimationComplete events?
*/
stop: function (resetFrame, dispatchComplete) {
if (resetFrame === undefined) { resetFrame = false; }
if (dispatchComplete === undefined) { dispatchComplete = false; }
this.isPlaying = false;
this.isFinished = true;
this.paused = false;
if (resetFrame)
{
this.currentFrame = this._frameData.getFrame(this._frames[0]);
this._parent.setFrame(this.currentFrame);
}
if (dispatchComplete)
{
this._parent.events.onAnimationComplete$dispatch(this._parent, this);
this.onComplete.dispatch(this._parent, this);
}
},
/**
* Called when the Game enters a paused state.
*
* @method Phaser.Animation#onPause
*/
onPause: function () {
if (this.isPlaying)
{
this._frameDiff = this._timeNextFrame - this.game.time.time;
}
},
/**
* Called when the Game resumes from a paused state.
*
* @method Phaser.Animation#onResume
*/
onResume: function () {
if (this.isPlaying)
{
this._timeNextFrame = this.game.time.time + this._frameDiff;
}
},
/**
* Updates this animation. Called automatically by the AnimationManager.
*
* @method Phaser.Animation#update
*/
update: function () {
if (this.isPaused)
{
return false;
}
if (this.isPlaying && this.game.time.time >= this._timeNextFrame)
{
this._frameSkip = 1;
// Lagging?
this._frameDiff = this.game.time.time - this._timeNextFrame;
this._timeLastFrame = this.game.time.time;
if (this._frameDiff > this.delay)
{
// We need to skip a frame, work out how many
this._frameSkip = Math.floor(this._frameDiff / this.delay);
this._frameDiff -= (this._frameSkip * this.delay);
}
// And what's left now?
this._timeNextFrame = this.game.time.time + (this.delay - this._frameDiff);
if (this.isReversed)
{
this._frameIndex -= this._frameSkip;
}
else
{
this._frameIndex += this._frameSkip;
}
if (!this.isReversed && this._frameIndex >= this._frames.length || this.isReversed && this._frameIndex <= -1)
{
if (this.loop)
{
// Update current state before event callback
this._frameIndex = Math.abs(this._frameIndex) % this._frames.length;
if (this.isReversed)
{
this._frameIndex = this._frames.length - 1 - this._frameIndex;
}
this.currentFrame = this._frameData.getFrame(this._frames[this._frameIndex]);
// Instead of calling updateCurrentFrame we do it here instead
if (this.currentFrame)
{
this._parent.setFrame(this.currentFrame);
}
this.loopCount++;
this._parent.events.onAnimationLoop$dispatch(this._parent, this);
this.onLoop.dispatch(this._parent, this);
if (this.onUpdate)
{
this.onUpdate.dispatch(this, this.currentFrame);
// False if the animation was destroyed from within a callback
return !!this._frameData;
}
else
{
return true;
}
}
else
{
this.complete();
return false;
}
}
else
{
return this.updateCurrentFrame(true);
}
}
return false;
},
/**
* Changes the currentFrame per the _frameIndex, updates the display state,
* and triggers the update signal.
*
* Returns true if the current frame update was 'successful', false otherwise.
*
* @method Phaser.Animation#updateCurrentFrame
* @private
* @param {boolean} signalUpdate - If true the `Animation.onUpdate` signal will be dispatched.
* @param {boolean} fromPlay - Was this call made from the playing of a new animation?
* @return {boolean} True if the current frame was updated, otherwise false.
*/
updateCurrentFrame: function (signalUpdate, fromPlay) {
if (fromPlay === undefined) { fromPlay = false; }
if (!this._frameData)
{
// The animation is already destroyed, probably from a callback
return false;
}
// Previous index
var idx = this.currentFrame.index;
this.currentFrame = this._frameData.getFrame(this._frames[this._frameIndex]);
if (this.currentFrame && (fromPlay || (!fromPlay && idx !== this.currentFrame.index)))
{
this._parent.setFrame(this.currentFrame);
}
if (this.onUpdate && signalUpdate)
{
this.onUpdate.dispatch(this, this.currentFrame);
// False if the animation was destroyed from within a callback
return !!this._frameData;
}
else
{
return true;
}
},
/**
* Advances by the given number of frames in the Animation, taking the loop value into consideration.
*
* @method Phaser.Animation#next
* @param {number} [quantity=1] - The number of frames to advance.
*/
next: function (quantity) {
if (quantity === undefined) { quantity = 1; }
var frame = this._frameIndex + quantity;
if (frame >= this._frames.length)
{
if (this.loop)
{
frame %= this._frames.length;
}
else
{
frame = this._frames.length - 1;
}
}
if (frame !== this._frameIndex)
{
this._frameIndex = frame;
this.updateCurrentFrame(true);
}
},
/**
* Moves backwards the given number of frames in the Animation, taking the loop value into consideration.
*
* @method Phaser.Animation#previous
* @param {number} [quantity=1] - The number of frames to move back.
*/
previous: function (quantity) {
if (quantity === undefined) { quantity = 1; }
var frame = this._frameIndex - quantity;
if (frame < 0)
{
if (this.loop)
{
frame = this._frames.length + frame;
}
else
{
frame++;
}
}
if (frame !== this._frameIndex)
{
this._frameIndex = frame;
this.updateCurrentFrame(true);
}
},
/**
* Changes the FrameData object this Animation is using.
*
* @method Phaser.Animation#updateFrameData
* @param {Phaser.FrameData} frameData - The FrameData object that contains all frames used by this Animation.
*/
updateFrameData: function (frameData) {
this._frameData = frameData;
this.currentFrame = this._frameData ? this._frameData.getFrame(this._frames[this._frameIndex % this._frames.length]) : null;
},
/**
* Cleans up this animation ready for deletion. Nulls all values and references.
*
* @method Phaser.Animation#destroy
*/
destroy: function () {
if (!this._frameData)
{
// Already destroyed
return;
}
this.game.onPause.remove(this.onPause, this);
this.game.onResume.remove(this.onResume, this);
this.game = null;
this._parent = null;
this._frames = null;
this._frameData = null;
this.currentFrame = null;
this.isPlaying = false;
this.onStart.dispose();
this.onLoop.dispose();
this.onComplete.dispose();
if (this.onUpdate)
{
this.onUpdate.dispose();
}
},
/**
* Called internally when the animation finishes playback.
* Sets the isPlaying and isFinished states and dispatches the onAnimationComplete event if it exists on the parent and local onComplete event.
*
* @method Phaser.Animation#complete
*/
complete: function () {
this._frameIndex = this._frames.length - 1;
this.currentFrame = this._frameData.getFrame(this._frames[this._frameIndex]);
this.isPlaying = false;
this.isFinished = true;
this.paused = false;
this._parent.events.onAnimationComplete$dispatch(this._parent, this);
this.onComplete.dispatch(this._parent, this);
if (this.killOnComplete)
{
this._parent.kill();
}
}
};
Phaser.Animation.prototype.constructor = Phaser.Animation;
/**
* @name Phaser.Animation#paused
* @property {boolean} paused - Gets and sets the paused state of this Animation.
*/
Object.defineProperty(Phaser.Animation.prototype, 'paused', {
get: function () {
return this.isPaused;
},
set: function (value) {
this.isPaused = value;
if (value)
{
// Paused
this._pauseStartTime = this.game.time.time;
}
else
{
// Un-paused
if (this.isPlaying)
{
this._timeNextFrame = this.game.time.time + this.delay;
}
}
}
});
/**
* @name Phaser.Animation#reversed
* @property {boolean} reversed - Gets and sets the isReversed state of this Animation.
*/
Object.defineProperty(Phaser.Animation.prototype, 'reversed', {
get: function () {
return this.isReversed;
},
set: function (value) {
this.isReversed = value;
}
});
/**
* @name Phaser.Animation#frameTotal
* @property {number} frameTotal - The total number of frames in the currently loaded FrameData, or -1 if no FrameData is loaded.
* @readonly
*/
Object.defineProperty(Phaser.Animation.prototype, 'frameTotal', {
get: function () {
return this._frames.length;
}
});
/**
* @name Phaser.Animation#frame
* @property {number} frame - Gets or sets the current frame index and updates the Texture Cache for display.
*/
Object.defineProperty(Phaser.Animation.prototype, 'frame', {
get: function () {
if (this.currentFrame !== null)
{
return this.currentFrame.index;
}
else
{
return this._frameIndex;
}
},
set: function (value) {
this.currentFrame = this._frameData.getFrame(this._frames[value]);
if (this.currentFrame !== null)
{
this._frameIndex = value;
this._parent.setFrame(this.currentFrame);
if (this.onUpdate)
{
this.onUpdate.dispatch(this, this.currentFrame);
}
}
}
});
/**
* @name Phaser.Animation#speed
* @property {number} speed - Gets or sets the current speed of the animation in frames per second. Changing this in a playing animation will take effect from the next frame. Value must be greater than 0.
*/
Object.defineProperty(Phaser.Animation.prototype, 'speed', {
get: function () {
return 1000 / this.delay;
},
set: function (value) {
if (value > 0)
{
this.delay = 1000 / value;
}
}
});
/**
* @name Phaser.Animation#enableUpdate
* @property {boolean} enableUpdate - Gets or sets if this animation will dispatch the onUpdate events upon changing frame.
*/
Object.defineProperty(Phaser.Animation.prototype, 'enableUpdate', {
get: function () {
return (this.onUpdate !== null);
},
set: function (value) {
if (value && this.onUpdate === null)
{
this.onUpdate = new Phaser.Signal();
}
else if (!value && this.onUpdate !== null)
{
this.onUpdate.dispose();
this.onUpdate = null;
}
}
});
/**
* Really handy function for when you are creating arrays of animation data but it's using frame names and not numbers.
* For example imagine you've got 30 frames named: 'explosion_0001-large' to 'explosion_0030-large'
* You could use this function to generate those by doing: Phaser.Animation.generateFrameNames('explosion_', 1, 30, '-large', 4);
*
* @method Phaser.Animation.generateFrameNames
* @static
* @param {string} prefix - The start of the filename. If the filename was 'explosion_0001-large' the prefix would be 'explosion_'.
* @param {number} start - The number to start sequentially counting from. If your frames are named 'explosion_0001' to 'explosion_0034' the start is 1.
* @param {number} stop - The number to count to. If your frames are named 'explosion_0001' to 'explosion_0034' the stop value is 34.
* @param {string} [suffix=''] - The end of the filename. If the filename was 'explosion_0001-large' the prefix would be '-large'.
* @param {number} [zeroPad=0] - The number of zeros to pad the min and max values with. If your frames are named 'explosion_0001' to 'explosion_0034' then the zeroPad is 4.
* @return {string[]} An array of framenames.
*/
Phaser.Animation.generateFrameNames = function (prefix, start, stop, suffix, zeroPad) {
if (suffix === undefined) { suffix = ''; }
var output = [];
var frame = '';
if (start < stop)
{
for (var i = start; i <= stop; i++)
{
if (typeof zeroPad === 'number')
{
// str, len, pad, dir
frame = Phaser.Utils.pad(i.toString(), zeroPad, '0', 1);
}
else
{
frame = i.toString();
}
frame = prefix + frame + suffix;
output.push(frame);
}
}
else
{
for (var i = start; i >= stop; i--)
{
if (typeof zeroPad === 'number')
{
// str, len, pad, dir
frame = Phaser.Utils.pad(i.toString(), zeroPad, '0', 1);
}
else
{
frame = i.toString();
}
frame = prefix + frame + suffix;
output.push(frame);
}
}
return output;
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Frame is a single frame of an animation and is part of a FrameData collection.
*
* @class Phaser.Frame
* @constructor
* @param {number} index - The index of this Frame within the FrameData set it is being added to.
* @param {number} x - X position of the frame within the texture image.
* @param {number} y - Y position of the frame within the texture image.
* @param {number} width - Width of the frame within the texture image.
* @param {number} height - Height of the frame within the texture image.
* @param {string} name - The name of the frame. In Texture Atlas data this is usually set to the filename.
*/
Phaser.Frame = function (index, x, y, width, height, name) {
/**
* @property {number} index - The index of this Frame within the FrameData set it is being added to.
*/
this.index = index;
/**
* @property {number} x - X position within the image to cut from.
*/
this.x = x;
/**
* @property {number} y - Y position within the image to cut from.
*/
this.y = y;
/**
* @property {number} width - Width of the frame.
*/
this.width = width;
/**
* @property {number} height - Height of the frame.
*/
this.height = height;
/**
* @property {string} name - Useful for Texture Atlas files (is set to the filename value).
*/
this.name = name;
/**
* @property {number} centerX - Center X position within the image to cut from.
*/
this.centerX = Math.floor(width / 2);
/**
* @property {number} centerY - Center Y position within the image to cut from.
*/
this.centerY = Math.floor(height / 2);
/**
* @property {number} distance - The distance from the top left to the bottom-right of this Frame.
*/
this.distance = Phaser.Math.distance(0, 0, width, height);
/**
* @property {boolean} rotated - Is the frame rotated in the source texture?
* @default
*/
this.rotated = false;
/**
* @property {boolean} trimmed - Was it trimmed when packed?
* @default
*/
this.trimmed = false;
/**
* @property {number} sourceSizeW - Width of the original sprite before it was trimmed.
*/
this.sourceSizeW = width;
/**
* @property {number} sourceSizeH - Height of the original sprite before it was trimmed.
*/
this.sourceSizeH = height;
/**
* @property {number} spriteSourceSizeX - X position of the trimmed sprite inside original sprite.
* @default
*/
this.spriteSourceSizeX = 0;
/**
* @property {number} spriteSourceSizeY - Y position of the trimmed sprite inside original sprite.
* @default
*/
this.spriteSourceSizeY = 0;
/**
* @property {number} spriteSourceSizeW - Width of the trimmed sprite.
* @default
*/
this.spriteSourceSizeW = 0;
/**
* @property {number} spriteSourceSizeH - Height of the trimmed sprite.
* @default
*/
this.spriteSourceSizeH = 0;
/**
* @property {number} right - The right of the Frame (x + width).
*/
this.right = this.x + this.width;
/**
* @property {number} bottom - The bottom of the frame (y + height).
*/
this.bottom = this.y + this.height;
};
Phaser.Frame.prototype = {
/**
* Adjusts of all the Frame properties based on the given width and height values.
*
* @method Phaser.Frame#resize
* @param {integer} width - The new width of the Frame.
* @param {integer} height - The new height of the Frame.
*/
resize: function (width, height) {
this.width = width;
this.height = height;
this.centerX = Math.floor(width / 2);
this.centerY = Math.floor(height / 2);
this.distance = Phaser.Math.distance(0, 0, width, height);
this.sourceSizeW = width;
this.sourceSizeH = height;
this.right = this.x + width;
this.bottom = this.y + height;
},
/**
* If the frame was trimmed when added to the Texture Atlas this records the trim and source data.
*
* @method Phaser.Frame#setTrim
* @param {boolean} trimmed - If this frame was trimmed or not.
* @param {number} actualWidth - The width of the frame before being trimmed.
* @param {number} actualHeight - The height of the frame before being trimmed.
* @param {number} destX - The destination X position of the trimmed frame for display.
* @param {number} destY - The destination Y position of the trimmed frame for display.
* @param {number} destWidth - The destination width of the trimmed frame for display.
* @param {number} destHeight - The destination height of the trimmed frame for display.
*/
setTrim: function (trimmed, actualWidth, actualHeight, destX, destY, destWidth, destHeight) {
this.trimmed = trimmed;
if (trimmed)
{
this.sourceSizeW = actualWidth;
this.sourceSizeH = actualHeight;
this.centerX = Math.floor(actualWidth / 2);
this.centerY = Math.floor(actualHeight / 2);
this.spriteSourceSizeX = destX;
this.spriteSourceSizeY = destY;
this.spriteSourceSizeW = destWidth;
this.spriteSourceSizeH = destHeight;
}
},
/**
* Clones this Frame into a new Phaser.Frame object and returns it.
* Note that all properties are cloned, including the name and index.
*
* @method Phaser.Frame#clone
* @return {Phaser.Frame} An exact copy of this Frame object.
*/
clone: function () {
var output = new Phaser.Frame(this.index, this.x, this.y, this.width, this.height, this.name);
for (var prop in this)
{
if (this.hasOwnProperty(prop))
{
output[prop] = this[prop];
}
}
return output;
},
/**
* Returns a Rectangle set to the dimensions of this Frame.
*
* @method Phaser.Frame#getRect
* @param {Phaser.Rectangle} [out] - A rectangle to copy the frame dimensions to.
* @return {Phaser.Rectangle} A rectangle.
*/
getRect: function (out) {
if (out === undefined)
{
out = new Phaser.Rectangle(this.x, this.y, this.width, this.height);
}
else
{
out.setTo(this.x, this.y, this.width, this.height);
}
return out;
}
};
Phaser.Frame.prototype.constructor = Phaser.Frame;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* FrameData is a container for Frame objects, which are the internal representation of animation data in Phaser.
*
* @class Phaser.FrameData
* @constructor
*/
Phaser.FrameData = function () {
/**
* @property {Array} _frames - Local array of frames.
* @private
*/
this._frames = [];
/**
* @property {Array} _frameNames - Local array of frame names for name to index conversions.
* @private
*/
this._frameNames = [];
};
Phaser.FrameData.prototype = {
/**
* Adds a new Frame to this FrameData collection. Typically called by the Animation.Parser and not directly.
*
* @method Phaser.FrameData#addFrame
* @param {Phaser.Frame} frame - The frame to add to this FrameData set.
* @return {Phaser.Frame} The frame that was just added.
*/
addFrame: function (frame) {
frame.index = this._frames.length;
this._frames.push(frame);
if (frame.name !== '')
{
this._frameNames[frame.name] = frame.index;
}
return frame;
},
/**
* Get a Frame by its numerical index.
*
* @method Phaser.FrameData#getFrame
* @param {number} index - The index of the frame you want to get.
* @return {Phaser.Frame} The frame, if found.
*/
getFrame: function (index) {
if (index >= this._frames.length)
{
index = 0;
}
return this._frames[index];
},
/**
* Get a Frame by its frame name.
*
* @method Phaser.FrameData#getFrameByName
* @param {string} name - The name of the frame you want to get.
* @return {Phaser.Frame} The frame, if found.
*/
getFrameByName: function (name) {
if (typeof this._frameNames[name] === 'number')
{
return this._frames[this._frameNames[name]];
}
return null;
},
/**
* Check if there is a Frame with the given name.
*
* @method Phaser.FrameData#checkFrameName
* @param {string} name - The name of the frame you want to check.
* @return {boolean} True if the frame is found, otherwise false.
*/
checkFrameName: function (name) {
if (this._frameNames[name] == null)
{
return false;
}
return true;
},
/**
* Makes a copy of this FrameData including copies (not references) to all of the Frames it contains.
*
* @method Phaser.FrameData#clone
* @return {Phaser.FrameData} A clone of this object, including clones of the Frame objects it contains.
*/
clone: function () {
var output = new Phaser.FrameData();
// No input array, so we loop through all frames
for (var i = 0; i < this._frames.length; i++)
{
output._frames.push(this._frames[i].clone());
}
for (var p in this._frameNames)
{
if (this._frameNames.hasOwnProperty(p))
{
output._frameNames.push(this._frameNames[p]);
}
}
return output;
},
/**
* Returns a range of frames based on the given start and end frame indexes and returns them in an Array.
*
* @method Phaser.FrameData#getFrameRange
* @param {number} start - The starting frame index.
* @param {number} end - The ending frame index.
* @param {Array} [output] - If given the results will be appended to the end of this array otherwise a new array will be created.
* @return {Array} An array of Frames between the start and end index values, or an empty array if none were found.
*/
getFrameRange: function (start, end, output) {
if (output === undefined) { output = []; }
for (var i = start; i <= end; i++)
{
output.push(this._frames[i]);
}
return output;
},
/**
* Returns all of the Frames in this FrameData set where the frame index is found in the input array.
* The frames are returned in the output array, or if none is provided in a new Array object.
*
* @method Phaser.FrameData#getFrames
* @param {Array} [frames] - An Array containing the indexes of the frames to retrieve. If the array is empty or undefined then all frames in the FrameData are returned.
* @param {boolean} [useNumericIndex=true] - Are the given frames using numeric indexes (default) or strings? (false)
* @param {Array} [output] - If given the results will be appended to the end of this array otherwise a new array will be created.
* @return {Array} An array of all Frames in this FrameData set matching the given names or IDs.
*/
getFrames: function (frames, useNumericIndex, output) {
if (useNumericIndex === undefined) { useNumericIndex = true; }
if (output === undefined) { output = []; }
if (frames === undefined || frames.length === 0)
{
// No input array, so we loop through all frames
for (var i = 0; i < this._frames.length; i++)
{
// We only need the indexes
output.push(this._frames[i]);
}
}
else
{
// Input array given, loop through that instead
for (var i = 0; i < frames.length; i++)
{
// Does the input array contain names or indexes?
if (useNumericIndex)
{
// The actual frame
output.push(this.getFrame(frames[i]));
}
else
{
// The actual frame
output.push(this.getFrameByName(frames[i]));
}
}
}
return output;
},
/**
* Returns all of the Frame indexes in this FrameData set.
* The frames indexes are returned in the output array, or if none is provided in a new Array object.
*
* @method Phaser.FrameData#getFrameIndexes
* @param {Array} [frames] - An Array containing the indexes of the frames to retrieve. If undefined or the array is empty then all frames in the FrameData are returned.
* @param {boolean} [useNumericIndex=true] - Are the given frames using numeric indexes (default) or strings? (false)
* @param {Array} [output] - If given the results will be appended to the end of this array otherwise a new array will be created.
* @return {Array} An array of all Frame indexes matching the given names or IDs.
*/
getFrameIndexes: function (frames, useNumericIndex, output) {
if (useNumericIndex === undefined) { useNumericIndex = true; }
if (output === undefined) { output = []; }
if (frames === undefined || frames.length === 0)
{
// No frames array, so we loop through all frames
for (var i = 0; i < this._frames.length; i++)
{
output.push(this._frames[i].index);
}
}
else
{
// Input array given, loop through that instead
for (var i = 0; i < frames.length; i++)
{
// Does the frames array contain names or indexes?
if (useNumericIndex && this._frames[frames[i]])
{
output.push(this._frames[frames[i]].index);
}
else
{
if (this.getFrameByName(frames[i]))
{
output.push(this.getFrameByName(frames[i]).index);
}
}
}
}
return output;
},
/**
* Destroys this FrameData collection by nulling the _frames and _frameNames arrays.
*
* @method Phaser.FrameData#destroy
*/
destroy: function () {
this._frames = null;
this._frameNames = null;
}
};
Phaser.FrameData.prototype.constructor = Phaser.FrameData;
/**
* @name Phaser.FrameData#total
* @property {number} total - The total number of frames in this FrameData set.
* @readonly
*/
Object.defineProperty(Phaser.FrameData.prototype, "total", {
get: function () {
return this._frames.length;
}
});
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Responsible for parsing sprite sheet and JSON data into the internal FrameData format that Phaser uses for animations.
*
* @class Phaser.AnimationParser
* @static
*/
Phaser.AnimationParser = {
/**
* Parse a Sprite Sheet and extract the animation frame data from it.
*
* @method Phaser.AnimationParser.spriteSheet
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {string|Image} key - The Game.Cache asset key of the Sprite Sheet image or an actual HTML Image element.
* @param {number} frameWidth - The fixed width of each frame of the animation.
* @param {number} frameHeight - The fixed height of each frame of the animation.
* @param {number} [frameMax=-1] - The total number of animation frames to extract from the Sprite Sheet. The default value of -1 means "extract all frames".
* @param {number} [margin=0] - If the frames have been drawn with a margin, specify the amount here.
* @param {number} [spacing=0] - If the frames have been drawn with spacing between them, specify the amount here.
* @param {number} [skipFrames=0] - Skip a number of frames. Useful when there are multiple sprite sheets in one image.
* @return {Phaser.FrameData} A FrameData object containing the parsed frames.
*/
spriteSheet: function (game, key, frameWidth, frameHeight, frameMax, margin, spacing, skipFrames) {
var img = key;
if (typeof key === 'string')
{
img = game.cache.getImage(key);
}
if (img === null)
{
return null;
}
var width = img.width;
var height = img.height;
if (frameWidth <= 0)
{
frameWidth = Math.floor(-width / Math.min(-1, frameWidth));
}
if (frameHeight <= 0)
{
frameHeight = Math.floor(-height / Math.min(-1, frameHeight));
}
var row = Math.floor((width - margin) / (frameWidth + spacing));
var column = Math.floor((height - margin) / (frameHeight + spacing));
var total = row * column;
if (skipFrames > total || skipFrames < -total)
{
console.warn(
"Phaser.AnimationParser.spriteSheet: skipFrames = " +
skipFrames.toString() + " is larger than total sprite number " +
total.toString());
return null;
}
if (skipFrames < 0)
{
// Allow negative skipframes.
skipFrames = total + skipFrames;
}
if (frameMax !== -1)
{
total = skipFrames + frameMax;
}
// Zero or smaller than frame sizes?
if (width === 0 || height === 0 || width < frameWidth || height < frameHeight || total === 0)
{
console.warn("Phaser.AnimationParser.spriteSheet: '" + key + "'s width/height zero or width/height < given frameWidth/frameHeight");
return null;
}
// Let's create some frames then
var data = new Phaser.FrameData();
var x = margin;
var y = margin;
for (var i = 0; i < total; i++)
{
data.addFrame(new Phaser.Frame(i, x, y, frameWidth, frameHeight, ''));
x += frameWidth + spacing;
if (x + frameWidth > width)
{
x = margin;
y += frameHeight + spacing;
}
}
return data;
},
/**
* Parse the JSON data and extract the animation frame data from it.
*
* @method Phaser.AnimationParser.JSONData
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {object} json - The JSON data from the Texture Atlas. Must be in Array format.
* @return {Phaser.FrameData} A FrameData object containing the parsed frames.
*/
JSONData: function (game, json) {
// Malformed?
if (!json['frames'])
{
console.warn("Phaser.AnimationParser.JSONData: Invalid Texture Atlas JSON given, missing 'frames' array");
console.log(json);
return;
}
// Let's create some frames then
var data = new Phaser.FrameData();
// By this stage frames is a fully parsed array
var frames = json['frames'];
var newFrame;
for (var i = 0; i < frames.length; i++)
{
newFrame = data.addFrame(new Phaser.Frame(
i,
frames[i].frame.x,
frames[i].frame.y,
frames[i].frame.w,
frames[i].frame.h,
frames[i].filename
));
if (frames[i].trimmed)
{
newFrame.setTrim(
frames[i].trimmed,
frames[i].sourceSize.w,
frames[i].sourceSize.h,
frames[i].spriteSourceSize.x,
frames[i].spriteSourceSize.y,
frames[i].spriteSourceSize.w,
frames[i].spriteSourceSize.h
);
}
if (frames[i].rotated)
{
newFrame.rotated = true;
}
}
return data;
},
/**
* Parse the JSON data and extract the animation frame data from it.
*
* @method Phaser.AnimationParser.JSONDataPyxel
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {object} json - The JSON data from the Texture Atlas. Must be in Pyxel JSON format.
* @return {Phaser.FrameData} A FrameData object containing the parsed frames.
*/
JSONDataPyxel: function (game, json) {
// Malformed? There are a few keys to check here.
var signature = ['layers', 'tilewidth','tileheight','tileswide', 'tileshigh'];
signature.forEach( function(key) {
if (!json[key])
{
console.warn('Phaser.AnimationParser.JSONDataPyxel: Invalid Pyxel Tilemap JSON given, missing "' + key + '" key.');
console.log(json);
return;
}
});
// For this purpose, I only care about parsing tilemaps with a single layer.
if (json['layers'].length !== 1)
{
console.warn('Phaser.AnimationParser.JSONDataPyxel: Too many layers, this parser only supports flat Tilemaps.');
console.log(json);
return;
}
var data = new Phaser.FrameData();
var tileheight = json['tileheight'];
var tilewidth = json['tilewidth'];
var frames = json['layers'][0]['tiles'];
var newFrame;
for (var i = 0; i < frames.length; i++)
{
newFrame = data.addFrame(new Phaser.Frame(
i,
frames[i].x,
frames[i].y,
tilewidth,
tileheight,
"frame_" + i // No names are included in pyxel tilemap data.
));
// No trim data is included.
newFrame.setTrim(false);
}
return data;
},
/**
* Parse the JSON data and extract the animation frame data from it.
*
* @method Phaser.AnimationParser.JSONDataHash
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {object} json - The JSON data from the Texture Atlas. Must be in JSON Hash format.
* @return {Phaser.FrameData} A FrameData object containing the parsed frames.
*/
JSONDataHash: function (game, json) {
// Malformed?
if (!json['frames'])
{
console.warn("Phaser.AnimationParser.JSONDataHash: Invalid Texture Atlas JSON given, missing 'frames' object");
console.log(json);
return;
}
// Let's create some frames then
var data = new Phaser.FrameData();
// By this stage frames is a fully parsed array
var frames = json['frames'];
var newFrame;
var i = 0;
for (var key in frames)
{
newFrame = data.addFrame(new Phaser.Frame(
i,
frames[key].frame.x,
frames[key].frame.y,
frames[key].frame.w,
frames[key].frame.h,
key
));
if (frames[key].trimmed)
{
newFrame.setTrim(
frames[key].trimmed,
frames[key].sourceSize.w,
frames[key].sourceSize.h,
frames[key].spriteSourceSize.x,
frames[key].spriteSourceSize.y,
frames[key].spriteSourceSize.w,
frames[key].spriteSourceSize.h
);
}
if (frames[key].rotated)
{
newFrame.rotated = true;
}
i++;
}
return data;
},
/**
* Parse the XML data and extract the animation frame data from it.
*
* @method Phaser.AnimationParser.XMLData
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {object} xml - The XML data from the Texture Atlas. Must be in Starling XML format.
* @return {Phaser.FrameData} A FrameData object containing the parsed frames.
*/
XMLData: function (game, xml) {
// Malformed?
if (!xml.getElementsByTagName('TextureAtlas'))
{
console.warn("Phaser.AnimationParser.XMLData: Invalid Texture Atlas XML given, missing <TextureAtlas> tag");
return;
}
// Let's create some frames then
var data = new Phaser.FrameData();
var frames = xml.getElementsByTagName('SubTexture');
var newFrame;
var name;
var frame;
var x;
var y;
var width;
var height;
var frameX;
var frameY;
var frameWidth;
var frameHeight;
for (var i = 0; i < frames.length; i++)
{
frame = frames[i].attributes;
name = frame.name.value;
x = parseInt(frame.x.value, 10);
y = parseInt(frame.y.value, 10);
width = parseInt(frame.width.value, 10);
height = parseInt(frame.height.value, 10);
frameX = null;
frameY = null;
if (frame.frameX)
{
frameX = Math.abs(parseInt(frame.frameX.value, 10));
frameY = Math.abs(parseInt(frame.frameY.value, 10));
frameWidth = parseInt(frame.frameWidth.value, 10);
frameHeight = parseInt(frame.frameHeight.value, 10);
}
newFrame = data.addFrame(new Phaser.Frame(i, x, y, width, height, name));
// Trimmed?
if (frameX !== null || frameY !== null)
{
newFrame.setTrim(true, width, height, frameX, frameY, frameWidth, frameHeight);
}
}
return data;
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Phaser has one single cache in which it stores all assets.
*
* The cache is split up into sections, such as images, sounds, video, json, etc. All assets are stored using
* a unique string-based key as their identifier. Assets stored in different areas of the cache can have the
* same key, for example 'playerWalking' could be used as the key for both a sprite sheet and an audio file,
* because they are unique data types.
*
* The cache is automatically populated by the Phaser.Loader. When you use the loader to pull in external assets
* such as images they are automatically placed into their respective cache. Most common Game Objects, such as
* Sprites and Videos automatically query the cache to extract the assets they need on instantiation.
*
* You can access the cache from within a State via `this.cache`. From here you can call any public method it has,
* including adding new entries to it, deleting them or querying them.
*
* Understand that almost without exception when you get an item from the cache it will return a reference to the
* item stored in the cache, not a copy of it. Therefore if you retrieve an item and then modify it, the original
* object in the cache will also be updated, even if you don't put it back into the cache again.
*
* By default when you change State the cache is _not_ cleared, although there is an option to clear it should
* your game require it. In a typical game set-up the cache is populated once after the main game has loaded and
* then used as an asset store.
*
* @class Phaser.Cache
* @constructor
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.Cache = function (game) {
/**
* @property {Phaser.Game} game - Local reference to game.
*/
this.game = game;
/**
* Automatically resolve resource URLs to absolute paths for use with the Cache.getURL method.
* @property {boolean} autoResolveURL
*/
this.autoResolveURL = false;
/**
* The main cache object into which all resources are placed.
* @property {object} _cache
* @private
*/
this._cache = {
canvas: {},
image: {},
texture: {},
sound: {},
video: {},
text: {},
json: {},
xml: {},
physics: {},
tilemap: {},
binary: {},
bitmapData: {},
bitmapFont: {},
shader: {},
renderTexture: {},
compressedTexture: {}
};
/**
* @property {object} _urlMap - Maps URLs to resources.
* @private
*/
this._urlMap = {};
/**
* @property {Image} _urlResolver - Used to resolve URLs to the absolute path.
* @private
*/
this._urlResolver = new Image();
/**
* @property {string} _urlTemp - Temporary variable to hold a resolved url.
* @private
*/
this._urlTemp = null;
/**
* @property {Phaser.Signal} onSoundUnlock - This event is dispatched when the sound system is unlocked via a touch event on cellular devices.
*/
this.onSoundUnlock = new Phaser.Signal();
/**
* @property {array} _cacheMap - Const to cache object look-up array.
* @private
*/
this._cacheMap = [];
this._cacheMap[Phaser.Cache.CANVAS] = this._cache.canvas;
this._cacheMap[Phaser.Cache.IMAGE] = this._cache.image;
this._cacheMap[Phaser.Cache.TEXTURE] = this._cache.texture;
this._cacheMap[Phaser.Cache.SOUND] = this._cache.sound;
this._cacheMap[Phaser.Cache.TEXT] = this._cache.text;
this._cacheMap[Phaser.Cache.PHYSICS] = this._cache.physics;
this._cacheMap[Phaser.Cache.TILEMAP] = this._cache.tilemap;
this._cacheMap[Phaser.Cache.BINARY] = this._cache.binary;
this._cacheMap[Phaser.Cache.BITMAPDATA] = this._cache.bitmapData;
this._cacheMap[Phaser.Cache.BITMAPFONT] = this._cache.bitmapFont;
this._cacheMap[Phaser.Cache.JSON] = this._cache.json;
this._cacheMap[Phaser.Cache.XML] = this._cache.xml;
this._cacheMap[Phaser.Cache.VIDEO] = this._cache.video;
this._cacheMap[Phaser.Cache.SHADER] = this._cache.shader;
this._cacheMap[Phaser.Cache.RENDER_TEXTURE] = this._cache.renderTexture;
this.addDefaultImage();
this.addMissingImage();
};
/**
* @constant
* @type {number}
*/
Phaser.Cache.CANVAS = 1;
/**
* @constant
* @type {number}
*/
Phaser.Cache.IMAGE = 2;
/**
* @constant
* @type {number}
*/
Phaser.Cache.TEXTURE = 3;
/**
* @constant
* @type {number}
*/
Phaser.Cache.SOUND = 4;
/**
* @constant
* @type {number}
*/
Phaser.Cache.TEXT = 5;
/**
* @constant
* @type {number}
*/
Phaser.Cache.PHYSICS = 6;
/**
* @constant
* @type {number}
*/
Phaser.Cache.TILEMAP = 7;
/**
* @constant
* @type {number}
*/
Phaser.Cache.BINARY = 8;
/**
* @constant
* @type {number}
*/
Phaser.Cache.BITMAPDATA = 9;
/**
* @constant
* @type {number}
*/
Phaser.Cache.BITMAPFONT = 10;
/**
* @constant
* @type {number}
*/
Phaser.Cache.JSON = 11;
/**
* @constant
* @type {number}
*/
Phaser.Cache.XML = 12;
/**
* @constant
* @type {number}
*/
Phaser.Cache.VIDEO = 13;
/**
* @constant
* @type {number}
*/
Phaser.Cache.SHADER = 14;
/**
* @constant
* @type {number}
*/
Phaser.Cache.RENDER_TEXTURE = 15;
/**
* The default image used for a texture when no other is specified.
* @constant
* @type {PIXI.Texture}
*/
Phaser.Cache.DEFAULT = null;
/**
* The default image used for a texture when the source image is missing.
* @constant
* @type {PIXI.Texture}
*/
Phaser.Cache.MISSING = null;
Phaser.Cache.prototype = {
//////////////////
// Add Methods //
//////////////////
/**
* Add a new canvas object in to the cache.
*
* @method Phaser.Cache#addCompressedTextureMetaData
* @private
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url
* @param {string} extension
* @param {array} arrayBuffer
* @return {object} The compressed texture entry.
*/
addCompressedTextureMetaData: function (key, url, extension, arrayBuffer) {
if (this.checkImageKey(key))
{
this.removeImage(key);
}
var data = (extension in Phaser.LoaderParser) ? Phaser.LoaderParser[extension](arrayBuffer) : arrayBuffer;
var texture = {
key: key,
url: url,
data: data,
base: new PIXI.BaseTexture(data),
frame: new Phaser.Frame(0, 0, 0, data.width, data.height, key),
frameData: new Phaser.FrameData(),
fileFormat: extension
};
texture.frameData.addFrame(new Phaser.Frame(0, 0, 0, data.width, data.height, url));
this._cache.image[key] = texture;
this._resolveURL(url, texture);
return texture;
},
/**
* Add a new canvas object in to the cache.
*
* @method Phaser.Cache#addCanvas
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {HTMLCanvasElement} canvas - The Canvas DOM element.
* @param {CanvasRenderingContext2D} [context] - The context of the canvas element. If not specified it will default go `getContext('2d')`.
*/
addCanvas: function (key, canvas, context) {
if (context === undefined) { context = canvas.getContext('2d'); }
this._cache.canvas[key] = { canvas: canvas, context: context };
},
/**
* Adds an Image file into the Cache. The file must have already been loaded, typically via Phaser.Loader, but can also have been loaded into the DOM.
* If an image already exists in the cache with the same key then it is removed and destroyed, and the new image inserted in its place.
*
* @method Phaser.Cache#addImage
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra image data.
* @return {object} The full image object that was added to the cache.
*/
addImage: function (key, url, data) {
if (this.checkImageKey(key))
{
this.removeImage(key);
}
var img = {
key: key,
url: url,
data: data,
base: new PIXI.BaseTexture(data),
frame: new Phaser.Frame(0, 0, 0, data.width, data.height, key),
frameData: new Phaser.FrameData()
};
img.frameData.addFrame(new Phaser.Frame(0, 0, 0, data.width, data.height, url));
this._cache.image[key] = img;
this._resolveURL(url, img);
if (key === '__default')
{
Phaser.Cache.DEFAULT = new PIXI.Texture(img.base);
}
else if (key === '__missing')
{
Phaser.Cache.MISSING = new PIXI.Texture(img.base);
}
return img;
},
/**
* Adds a default image to be used in special cases such as WebGL Filters.
* It uses the special reserved key of `__default`.
* This method is called automatically when the Cache is created.
* This image is skipped when `Cache.destroy` is called due to its internal requirements.
*
* @method Phaser.Cache#addDefaultImage
* @protected
*/
addDefaultImage: function () {
var img = new Image();
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAQMAAABJtOi3AAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAABVJREFUeF7NwIEAAAAAgKD9qdeocAMAoAABm3DkcAAAAABJRU5ErkJggg==";
var obj = this.addImage('__default', null, img);
// Because we don't want to invalidate the sprite batch for an invisible texture
obj.base.skipRender = true;
// Make it easily available within the rest of Phaser / Pixi
Phaser.Cache.DEFAULT = new PIXI.Texture(obj.base);
},
/**
* Adds an image to be used when a key is wrong / missing.
* It uses the special reserved key of `__missing`.
* This method is called automatically when the Cache is created.
* This image is skipped when `Cache.destroy` is called due to its internal requirements.
*
* @method Phaser.Cache#addMissingImage
* @protected
*/
addMissingImage: function () {
var img = new Image();
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJ9JREFUeNq01ssOwyAMRFG46v//Mt1ESmgh+DFmE2GPOBARKb2NVjo+17PXLD8a1+pl5+A+wSgFygymWYHBb0FtsKhJDdZlncG2IzJ4ayoMDv20wTmSMzClEgbWYNTAkQ0Z+OJ+A/eWnAaR9+oxCF4Os0H8htsMUp+pwcgBBiMNnAwF8GqIgL2hAzaGFFgZauDPKABmowZ4GL369/0rwACp2yA/ttmvsQAAAABJRU5ErkJggg==";
var obj = this.addImage('__missing', null, img);
// Make it easily available within the rest of Phaser / Pixi
Phaser.Cache.MISSING = new PIXI.Texture(obj.base);
},
/**
* Adds a Sound file into the Cache. The file must have already been loaded, typically via Phaser.Loader.
*
* @method Phaser.Cache#addSound
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra sound data.
* @param {boolean} webAudio - True if the file is using web audio.
* @param {boolean} audioTag - True if the file is using legacy HTML audio.
*/
addSound: function (key, url, data, webAudio, audioTag) {
if (webAudio === undefined) { webAudio = true; audioTag = false; }
if (audioTag === undefined) { webAudio = false; audioTag = true; }
var decoded = false;
if (audioTag)
{
decoded = true;
}
this._cache.sound[key] = {
url: url,
data: data,
isDecoding: false,
decoded: decoded,
webAudio: webAudio,
audioTag: audioTag,
locked: this.game.sound.touchLocked
};
this._resolveURL(url, this._cache.sound[key]);
},
/**
* Add a new text data.
*
* @method Phaser.Cache#addText
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra text data.
*/
addText: function (key, url, data) {
this._cache.text[key] = { url: url, data: data };
this._resolveURL(url, this._cache.text[key]);
},
/**
* Add a new physics data object to the Cache.
*
* @method Phaser.Cache#addPhysicsData
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} JSONData - The physics data object (a JSON file).
* @param {number} format - The format of the physics data.
*/
addPhysicsData: function (key, url, JSONData, format) {
this._cache.physics[key] = { url: url, data: JSONData, format: format };
this._resolveURL(url, this._cache.physics[key]);
},
/**
* Add a new tilemap to the Cache.
*
* @method Phaser.Cache#addTilemap
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} mapData - The tilemap data object (either a CSV or JSON file).
* @param {number} format - The format of the tilemap data.
*/
addTilemap: function (key, url, mapData, format) {
this._cache.tilemap[key] = { url: url, data: mapData, format: format };
this._resolveURL(url, this._cache.tilemap[key]);
},
/**
* Add a binary object in to the cache.
*
* @method Phaser.Cache#addBinary
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {object} binaryData - The binary object to be added to the cache.
*/
addBinary: function (key, binaryData) {
this._cache.binary[key] = binaryData;
},
/**
* Add a BitmapData object to the cache.
*
* @method Phaser.Cache#addBitmapData
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {Phaser.BitmapData} bitmapData - The BitmapData object to be addded to the cache.
* @param {Phaser.FrameData|null} [frameData=(auto create)] - Optional FrameData set associated with the given BitmapData. If not specified (or `undefined`) a new FrameData object is created containing the Bitmap's Frame. If `null` is supplied then no FrameData will be created.
* @return {Phaser.BitmapData} The BitmapData object to be addded to the cache.
*/
addBitmapData: function (key, bitmapData, frameData) {
bitmapData.key = key;
if (frameData === undefined)
{
frameData = new Phaser.FrameData();
frameData.addFrame(bitmapData.textureFrame);
}
this._cache.bitmapData[key] = { data: bitmapData, frameData: frameData };
return bitmapData;
},
/**
* Add a new Bitmap Font to the Cache.
*
* @method Phaser.Cache#addBitmapFont
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra font data.
* @param {object} atlasData - The Bitmap Font data.
* @param {string} [atlasType='xml'] - The format of the Bitmap Font data file: `json` or `xml`.
* @param {number} [xSpacing=0] - If you'd like to add additional horizontal spacing between the characters then set the pixel value here.
* @param {number} [ySpacing=0] - If you'd like to add additional vertical spacing between the lines then set the pixel value here.
*/
addBitmapFont: function (key, url, data, atlasData, atlasType, xSpacing, ySpacing) {
var obj = {
url: url,
data: data,
font: null,
base: new PIXI.BaseTexture(data)
};
if (xSpacing === undefined) { xSpacing = 0; }
if (ySpacing === undefined) { ySpacing = 0; }
if (atlasType === 'json')
{
obj.font = Phaser.LoaderParser.jsonBitmapFont(atlasData, obj.base, xSpacing, ySpacing);
}
else
{
obj.font = Phaser.LoaderParser.xmlBitmapFont(atlasData, obj.base, xSpacing, ySpacing);
}
this._cache.bitmapFont[key] = obj;
this._resolveURL(url, obj);
},
/**
* Add a new Bitmap Font to the Cache, where the font texture is part of a Texture Atlas.
*
* The atlas must already exist in the cache, and be available based on the given `atlasKey`.
*
* The `atlasFrame` specifies the name of the frame within the atlas that the Bitmap Font is
* stored in.
*
* The `dataKey` is the key of the XML or JSON Bitmap Font Data, which must already be in
* the Cache.
*
* @method Phaser.Cache#addBitmapFontFromAtlas
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} atlasKey - The key of the Texture Atlas in the Cache.
* @param {string} atlasFrame - The frame of the Texture Atlas that the Bitmap Font is in.
* @param {string} dataKey - The key of the Bitmap Font data in the Cache
* @param {string} [dataType='xml'] - The format of the Bitmap Font data: `json` or `xml`.
* @param {number} [xSpacing=0] - If you'd like to add additional horizontal spacing between the characters then set the pixel value here.
* @param {number} [ySpacing=0] - If you'd like to add additional vertical spacing between the lines then set the pixel value here.
*/
addBitmapFontFromAtlas: function (key, atlasKey, atlasFrame, dataKey, dataType, xSpacing, ySpacing) {
var frame = this.getFrameByName(atlasKey, atlasFrame);
if (!frame)
{
return;
}
var obj = {
font: null,
base: this.getBaseTexture(atlasKey),
frame: frame
};
if (xSpacing === undefined) { xSpacing = 0; }
if (ySpacing === undefined) { ySpacing = 0; }
var fontData;
if (dataType === 'json')
{
fontData = this.getJSON(dataKey);
obj.font = Phaser.LoaderParser.jsonBitmapFont(fontData, obj.base, xSpacing, ySpacing, frame);
}
else
{
fontData = this.getXML(dataKey);
obj.font = Phaser.LoaderParser.xmlBitmapFont(fontData, obj.base, xSpacing, ySpacing, frame);
}
this._cache.bitmapFont[key] = obj;
},
/**
* Add a new json object into the cache.
*
* @method Phaser.Cache#addJSON
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra json data.
*/
addJSON: function (key, url, data) {
this._cache.json[key] = { url: url, data: data };
this._resolveURL(url, this._cache.json[key]);
},
/**
* Add a new xml object into the cache.
*
* @method Phaser.Cache#addXML
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra text data.
*/
addXML: function (key, url, data) {
this._cache.xml[key] = { url: url, data: data };
this._resolveURL(url, this._cache.xml[key]);
},
/**
* Adds a Video file into the Cache. The file must have already been loaded, typically via Phaser.Loader.
*
* @method Phaser.Cache#addVideo
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra video data.
* @param {boolean} isBlob - True if the file was preloaded via xhr and the data parameter is a Blob. false if a Video tag was created instead.
*/
addVideo: function (key, url, data, isBlob) {
this._cache.video[key] = { url: url, data: data, isBlob: isBlob, locked: true };
this._resolveURL(url, this._cache.video[key]);
},
/**
* Adds a Fragment Shader in to the Cache. The file must have already been loaded, typically via Phaser.Loader.
*
* @method Phaser.Cache#addShader
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra shader data.
*/
addShader: function (key, url, data) {
this._cache.shader[key] = { url: url, data: data };
this._resolveURL(url, this._cache.shader[key]);
},
/**
* Add a new Phaser.RenderTexture in to the cache.
*
* @method Phaser.Cache#addRenderTexture
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {Phaser.RenderTexture} texture - The texture to use as the base of the RenderTexture.
*/
addRenderTexture: function (key, texture) {
this._cache.renderTexture[key] = { texture: texture, frame: new Phaser.Frame(0, 0, 0, texture.width, texture.height, '', '') };
},
/**
* Add a new sprite sheet in to the cache.
*
* @method Phaser.Cache#addSpriteSheet
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra sprite sheet data.
* @param {number} frameWidth - Width of the sprite sheet.
* @param {number} frameHeight - Height of the sprite sheet.
* @param {number} [frameMax=-1] - How many frames stored in the sprite sheet. If -1 then it divides the whole sheet evenly.
* @param {number} [margin=0] - If the frames have been drawn with a margin, specify the amount here.
* @param {number} [spacing=0] - If the frames have been drawn with spacing between them, specify the amount here.
* @param {number} [skipFrames=0] - Skip a number of frames. Useful when there are multiple sprite sheets in one image.
*/
addSpriteSheet: function (key, url, data, frameWidth, frameHeight, frameMax, margin, spacing, skipFrames) {
if (frameMax === undefined) { frameMax = -1; }
if (margin === undefined) { margin = 0; }
if (spacing === undefined) { spacing = 0; }
var obj = {
key: key,
url: url,
data: data,
frameWidth: frameWidth,
frameHeight: frameHeight,
margin: margin,
spacing: spacing,
base: new PIXI.BaseTexture(data),
frameData: Phaser.AnimationParser.spriteSheet(this.game, data, frameWidth, frameHeight, frameMax, margin, spacing, skipFrames)
};
this._cache.image[key] = obj;
this._resolveURL(url, obj);
},
/**
* Add a new texture atlas to the Cache.
*
* @method Phaser.Cache#addTextureAtlas
* @param {string} key - The key that this asset will be stored in the cache under. This should be unique within this cache.
* @param {string} url - The URL the asset was loaded from. If the asset was not loaded externally set to `null`.
* @param {object} data - Extra texture atlas data.
* @param {object} atlasData - Texture atlas frames data.
* @param {number} format - The format of the texture atlas.
*/
addTextureAtlas: function (key, url, data, atlasData, format) {
var obj = {
key: key,
url: url,
data: data,
base: new PIXI.BaseTexture(data)
};
if (format === Phaser.Loader.TEXTURE_ATLAS_XML_STARLING)
{
obj.frameData = Phaser.AnimationParser.XMLData(this.game, atlasData, key);
}
else if (format === Phaser.Loader.TEXTURE_ATLAS_JSON_PYXEL)
{
obj.frameData = Phaser.AnimationParser.JSONDataPyxel(this.game, atlasData, key);
}
else
{
// Let's just work it out from the frames array
if (Array.isArray(atlasData.frames))
{
obj.frameData = Phaser.AnimationParser.JSONData(this.game, atlasData, key);
}
else
{
obj.frameData = Phaser.AnimationParser.JSONDataHash(this.game, atlasData, key);
}
}
this._cache.image[key] = obj;
this._resolveURL(url, obj);
},
////////////////////////////
// Sound Related Methods //
////////////////////////////
/**
* Reload a Sound file from the server.
*
* @method Phaser.Cache#reloadSound
* @param {string} key - The key of the asset within the cache.
*/
reloadSound: function (key) {
var _this = this;
var sound = this.getSound(key);
if (sound)
{
sound.data.src = sound.url;
sound.data.addEventListener('canplaythrough', function () {
return _this.reloadSoundComplete(key);
}, false);
sound.data.load();
}
},
/**
* Fires the onSoundUnlock event when the sound has completed reloading.
*
* @method Phaser.Cache#reloadSoundComplete
* @param {string} key - The key of the asset within the cache.
*/
reloadSoundComplete: function (key) {
var sound = this.getSound(key);
if (sound)
{
sound.locked = false;
this.onSoundUnlock.dispatch(key);
}
},
/**
* Updates the sound object in the cache.
*
* @method Phaser.Cache#updateSound
* @param {string} key - The key of the asset within the cache.
*/
updateSound: function (key, property, value) {
var sound = this.getSound(key);
if (sound)
{
sound[property] = value;
}
},
/**
* Add a new decoded sound.
*
* @method Phaser.Cache#decodedSound
* @param {string} key - The key of the asset within the cache.
* @param {object} data - Extra sound data.
*/
decodedSound: function (key, data) {
var sound = this.getSound(key);
sound.data = data;
sound.decoded = true;
sound.isDecoding = false;
},
/**
* Check if the given sound has finished decoding.
*
* @method Phaser.Cache#isSoundDecoded
* @param {string} key - The key of the asset within the cache.
* @return {boolean} The decoded state of the Sound object.
*/
isSoundDecoded: function (key) {
var sound = this.getItem(key, Phaser.Cache.SOUND, 'isSoundDecoded');
if (sound)
{
return sound.decoded;
}
},
/**
* Check if the given sound is ready for playback.
* A sound is considered ready when it has finished decoding and the device is no longer touch locked.
*
* @method Phaser.Cache#isSoundReady
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the sound is decoded and the device is not touch locked.
*/
isSoundReady: function (key) {
var sound = this.getItem(key, Phaser.Cache.SOUND, 'isSoundDecoded');
if (sound)
{
return (sound.decoded && !this.game.sound.touchLocked);
}
},
////////////////////////
// Check Key Methods //
////////////////////////
/**
* Checks if a key for the given cache object type exists.
*
* @method Phaser.Cache#checkKey
* @param {integer} cache - The cache to search. One of the Cache consts such as `Phaser.Cache.IMAGE` or `Phaser.Cache.SOUND`.
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists, otherwise false.
*/
checkKey: function (cache, key) {
if (this._cacheMap[cache][key])
{
return true;
}
return false;
},
/**
* Checks if the given URL has been loaded into the Cache.
* This method will only work if Cache.autoResolveURL was set to `true` before any preloading took place.
* The method will make a DOM src call to the URL given, so please be aware of this for certain file types, such as Sound files on Firefox
* which may cause double-load instances.
*
* @method Phaser.Cache#checkURL
* @param {string} url - The url to check for in the cache.
* @return {boolean} True if the url exists, otherwise false.
*/
checkURL: function (url) {
if (this._urlMap[this._resolveURL(url)])
{
return true;
}
return false;
},
/**
* Checks if the given key exists in the Canvas Cache.
*
* @method Phaser.Cache#checkCanvasKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkCanvasKey: function (key) {
return this.checkKey(Phaser.Cache.CANVAS, key);
},
/**
* Checks if the given key exists in the Image Cache. Note that this also includes Texture Atlases, Sprite Sheets and Retro Fonts.
*
* @method Phaser.Cache#checkImageKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkImageKey: function (key) {
return this.checkKey(Phaser.Cache.IMAGE, key);
},
/**
* Checks if the given key exists in the Texture Cache.
*
* @method Phaser.Cache#checkTextureKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkTextureKey: function (key) {
return this.checkKey(Phaser.Cache.TEXTURE, key);
},
/**
* Checks if the given key exists in the Sound Cache.
*
* @method Phaser.Cache#checkSoundKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkSoundKey: function (key) {
return this.checkKey(Phaser.Cache.SOUND, key);
},
/**
* Checks if the given key exists in the Text Cache.
*
* @method Phaser.Cache#checkTextKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkTextKey: function (key) {
return this.checkKey(Phaser.Cache.TEXT, key);
},
/**
* Checks if the given key exists in the Physics Cache.
*
* @method Phaser.Cache#checkPhysicsKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkPhysicsKey: function (key) {
return this.checkKey(Phaser.Cache.PHYSICS, key);
},
/**
* Checks if the given key exists in the Tilemap Cache.
*
* @method Phaser.Cache#checkTilemapKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkTilemapKey: function (key) {
return this.checkKey(Phaser.Cache.TILEMAP, key);
},
/**
* Checks if the given key exists in the Binary Cache.
*
* @method Phaser.Cache#checkBinaryKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkBinaryKey: function (key) {
return this.checkKey(Phaser.Cache.BINARY, key);
},
/**
* Checks if the given key exists in the BitmapData Cache.
*
* @method Phaser.Cache#checkBitmapDataKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkBitmapDataKey: function (key) {
return this.checkKey(Phaser.Cache.BITMAPDATA, key);
},
/**
* Checks if the given key exists in the BitmapFont Cache.
*
* @method Phaser.Cache#checkBitmapFontKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkBitmapFontKey: function (key) {
return this.checkKey(Phaser.Cache.BITMAPFONT, key);
},
/**
* Checks if the given key exists in the JSON Cache.
*
* @method Phaser.Cache#checkJSONKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkJSONKey: function (key) {
return this.checkKey(Phaser.Cache.JSON, key);
},
/**
* Checks if the given key exists in the XML Cache.
*
* @method Phaser.Cache#checkXMLKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkXMLKey: function (key) {
return this.checkKey(Phaser.Cache.XML, key);
},
/**
* Checks if the given key exists in the Video Cache.
*
* @method Phaser.Cache#checkVideoKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkVideoKey: function (key) {
return this.checkKey(Phaser.Cache.VIDEO, key);
},
/**
* Checks if the given key exists in the Fragment Shader Cache.
*
* @method Phaser.Cache#checkShaderKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkShaderKey: function (key) {
return this.checkKey(Phaser.Cache.SHADER, key);
},
/**
* Checks if the given key exists in the Render Texture Cache.
*
* @method Phaser.Cache#checkRenderTextureKey
* @param {string} key - The key of the asset within the cache.
* @return {boolean} True if the key exists in the cache, otherwise false.
*/
checkRenderTextureKey: function (key) {
return this.checkKey(Phaser.Cache.RENDER_TEXTURE, key);
},
////////////////
// Get Items //
////////////////
/**
* Get an item from a cache based on the given key and property.
*
* This method is mostly used internally by other Cache methods such as `getImage` but is exposed
* publicly for your own use as well.
*
* @method Phaser.Cache#getItem
* @param {string} key - The key of the asset within the cache.
* @param {integer} cache - The cache to search. One of the Cache consts such as `Phaser.Cache.IMAGE` or `Phaser.Cache.SOUND`.
* @param {string} [method] - The string name of the method calling getItem. Can be empty, in which case no console warning is output.
* @param {string} [property] - If you require a specific property from the cache item, specify it here.
* @return {object} The cached item if found, otherwise `null`. If the key is invalid and `method` is set then a console.warn is output.
*/
getItem: function (key, cache, method, property) {
if (!this.checkKey(cache, key))
{
if (method)
{
console.warn('Phaser.Cache.' + method + ': Key "' + key + '" not found in Cache.');
}
}
else
{
if (property === undefined)
{
return this._cacheMap[cache][key];
}
else
{
return this._cacheMap[cache][key][property];
}
}
return null;
},
/**
* Gets a Canvas object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getCanvas
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {object} The canvas object or `null` if no item could be found matching the given key.
*/
getCanvas: function (key) {
return this.getItem(key, Phaser.Cache.CANVAS, 'getCanvas', 'canvas');
},
/**
* Gets a Image object from the cache. This returns a DOM Image object, not a Phaser.Image object.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* Only the Image cache is searched, which covers images loaded via Loader.image, Sprite Sheets and Texture Atlases.
*
* If you need the image used by a bitmap font or similar then please use those respective 'get' methods.
*
* @method Phaser.Cache#getImage
* @param {string} [key] - The key of the asset to retrieve from the cache. If not given or null it will return a default image. If given but not found in the cache it will throw a warning and return the missing image.
* @param {boolean} [full=false] - If true the full image object will be returned, if false just the HTML Image object is returned.
* @return {Image} The Image object if found in the Cache, otherwise `null`. If `full` was true then a JavaScript object is returned.
*/
getImage: function (key, full) {
if (key === undefined || key === null)
{
key = '__default';
}
if (full === undefined) { full = false; }
var img = this.getItem(key, Phaser.Cache.IMAGE, 'getImage');
if (img === null)
{
img = this.getItem('__missing', Phaser.Cache.IMAGE, 'getImage');
}
if (full)
{
return img;
}
else
{
return img.data;
}
},
/**
* Get a single texture frame by key.
*
* You'd only do this to get the default Frame created for a non-atlas / spritesheet image.
*
* @method Phaser.Cache#getTextureFrame
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {Phaser.Frame} The frame data.
*/
getTextureFrame: function (key) {
return this.getItem(key, Phaser.Cache.TEXTURE, 'getTextureFrame', 'frame');
},
/**
* Gets a Phaser.Sound object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getSound
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {Phaser.Sound} The sound object.
*/
getSound: function (key) {
return this.getItem(key, Phaser.Cache.SOUND, 'getSound');
},
/**
* Gets a raw Sound data object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getSoundData
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {object} The sound data.
*/
getSoundData: function (key) {
return this.getItem(key, Phaser.Cache.SOUND, 'getSoundData', 'data');
},
/**
* Gets a Text object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getText
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {object} The text data.
*/
getText: function (key) {
return this.getItem(key, Phaser.Cache.TEXT, 'getText', 'data');
},
/**
* Gets a Physics Data object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* You can get either the entire data set, a single object or a single fixture of an object from it.
*
* @method Phaser.Cache#getPhysicsData
* @param {string} key - The key of the asset to retrieve from the cache.
* @param {string} [object=null] - If specified it will return just the physics object that is part of the given key, if null it will return them all.
* @param {string} fixtureKey - Fixture key of fixture inside an object. This key can be set per fixture with the Phaser Exporter.
* @return {object} The requested physics object data if found.
*/
getPhysicsData: function (key, object, fixtureKey) {
var data = this.getItem(key, Phaser.Cache.PHYSICS, 'getPhysicsData', 'data');
if (data === null || object === undefined || object === null)
{
return data;
}
else
{
if (data[object])
{
var fixtures = data[object];
// Try to find a fixture by its fixture key if given
if (fixtures && fixtureKey)
{
for (var fixture in fixtures)
{
// This contains the fixture data of a polygon or a circle
fixture = fixtures[fixture];
// Test the key
if (fixture.fixtureKey === fixtureKey)
{
return fixture;
}
}
// We did not find the requested fixture
console.warn('Phaser.Cache.getPhysicsData: Could not find given fixtureKey: "' + fixtureKey + ' in ' + key + '"');
}
else
{
return fixtures;
}
}
else
{
console.warn('Phaser.Cache.getPhysicsData: Invalid key/object: "' + key + ' / ' + object + '"');
}
}
return null;
},
/**
* Gets a raw Tilemap data object from the cache. This will be in either CSV or JSON format.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getTilemapData
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {object} The raw tilemap data in CSV or JSON format.
*/
getTilemapData: function (key) {
return this.getItem(key, Phaser.Cache.TILEMAP, 'getTilemapData');
},
/**
* Gets a binary object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getBinary
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {object} The binary data object.
*/
getBinary: function (key) {
return this.getItem(key, Phaser.Cache.BINARY, 'getBinary');
},
/**
* Gets a BitmapData object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getBitmapData
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {Phaser.BitmapData} The requested BitmapData object if found, or null if not.
*/
getBitmapData: function (key) {
return this.getItem(key, Phaser.Cache.BITMAPDATA, 'getBitmapData', 'data');
},
/**
* Gets a Bitmap Font object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getBitmapFont
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {Phaser.BitmapFont} The requested BitmapFont object if found, or null if not.
*/
getBitmapFont: function (key) {
return this.getItem(key, Phaser.Cache.BITMAPFONT, 'getBitmapFont');
},
/**
* Gets a JSON object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* You can either return the object by reference (the default), or return a clone
* of it by setting the `clone` argument to `true`.
*
* @method Phaser.Cache#getJSON
* @param {string} key - The key of the asset to retrieve from the cache.
* @param {boolean} [clone=false] - Return a clone of the original object (true) or a reference to it? (false)
* @return {object} The JSON object, or an Array if the key points to an Array property. If the property wasn't found, it returns null.
*/
getJSON: function (key, clone) {
var data = this.getItem(key, Phaser.Cache.JSON, 'getJSON', 'data');
if (data)
{
if (clone)
{
return Phaser.Utils.extend(true, Array.isArray(data) ? [] : {}, data);
}
else
{
return data;
}
}
else
{
return null;
}
},
/**
* Gets an XML object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getXML
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {object} The XML object.
*/
getXML: function (key) {
return this.getItem(key, Phaser.Cache.XML, 'getXML', 'data');
},
/**
* Gets a Phaser.Video object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getVideo
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {Phaser.Video} The video object.
*/
getVideo: function (key) {
return this.getItem(key, Phaser.Cache.VIDEO, 'getVideo');
},
/**
* Gets a fragment shader object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getShader
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {string} The shader object.
*/
getShader: function (key) {
return this.getItem(key, Phaser.Cache.SHADER, 'getShader', 'data');
},
/**
* Gets a RenderTexture object from the cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getRenderTexture
* @param {string} key - The key of the asset to retrieve from the cache.
* @return {Object} The object with Phaser.RenderTexture and Phaser.Frame.
*/
getRenderTexture: function (key) {
return this.getItem(key, Phaser.Cache.RENDER_TEXTURE, 'getRenderTexture');
},
////////////////////////////
// Frame Related Methods //
////////////////////////////
/**
* Gets a PIXI.BaseTexture by key from the given Cache.
*
* @method Phaser.Cache#getBaseTexture
* @param {string} key - Asset key of the image for which you want the BaseTexture for.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search for the item in.
* @return {PIXI.BaseTexture} The BaseTexture object.
*/
getBaseTexture: function (key, cache) {
if (cache === undefined) { cache = Phaser.Cache.IMAGE; }
return this.getItem(key, cache, 'getBaseTexture', 'base');
},
/**
* Get a single frame by key. You'd only do this to get the default Frame created for a non-atlas/spritesheet image.
*
* @method Phaser.Cache#getFrame
* @param {string} key - Asset key of the frame data to retrieve from the Cache.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search for the item in.
* @return {Phaser.Frame} The frame data.
*/
getFrame: function (key, cache) {
if (cache === undefined) { cache = Phaser.Cache.IMAGE; }
return this.getItem(key, cache, 'getFrame', 'frame');
},
/**
* Get the total number of frames contained in the FrameData object specified by the given key.
*
* @method Phaser.Cache#getFrameCount
* @param {string} key - Asset key of the FrameData you want.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search for the item in.
* @return {number} Then number of frames. 0 if the image is not found.
*/
getFrameCount: function (key, cache) {
var data = this.getFrameData(key, cache);
if (data)
{
return data.total;
}
else
{
return 0;
}
},
/**
* Gets a Phaser.FrameData object from the Image Cache.
*
* The object is looked-up based on the key given.
*
* Note: If the object cannot be found a `console.warn` message is displayed.
*
* @method Phaser.Cache#getFrameData
* @param {string} key - Asset key of the frame data to retrieve from the Cache.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search for the item in.
* @return {Phaser.FrameData} The frame data.
*/
getFrameData: function (key, cache) {
if (cache === undefined) { cache = Phaser.Cache.IMAGE; }
return this.getItem(key, cache, 'getFrameData', 'frameData');
},
/**
* Check if the FrameData for the given key exists in the Image Cache.
*
* @method Phaser.Cache#hasFrameData
* @param {string} key - Asset key of the frame data to retrieve from the Cache.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search for the item in.
* @return {boolean} True if the given key has frameData in the cache, otherwise false.
*/
hasFrameData: function (key, cache) {
if (cache === undefined) { cache = Phaser.Cache.IMAGE; }
return (this.getItem(key, cache, '', 'frameData') !== null);
},
/**
* Replaces a set of frameData with a new Phaser.FrameData object.
*
* @method Phaser.Cache#updateFrameData
* @param {string} key - The unique key by which you will reference this object.
* @param {number} frameData - The new FrameData.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search. One of the Cache consts such as `Phaser.Cache.IMAGE` or `Phaser.Cache.SOUND`.
*/
updateFrameData: function (key, frameData, cache) {
if (cache === undefined) { cache = Phaser.Cache.IMAGE; }
if (this._cacheMap[cache][key])
{
this._cacheMap[cache][key].frameData = frameData;
}
},
/**
* Get a single frame out of a frameData set by key.
*
* @method Phaser.Cache#getFrameByIndex
* @param {string} key - Asset key of the frame data to retrieve from the Cache.
* @param {number} index - The index of the frame you want to get.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search. One of the Cache consts such as `Phaser.Cache.IMAGE` or `Phaser.Cache.SOUND`.
* @return {Phaser.Frame} The frame object.
*/
getFrameByIndex: function (key, index, cache) {
var data = this.getFrameData(key, cache);
if (data)
{
return data.getFrame(index);
}
else
{
return null;
}
},
/**
* Get a single frame out of a frameData set by key.
*
* @method Phaser.Cache#getFrameByName
* @param {string} key - Asset key of the frame data to retrieve from the Cache.
* @param {string} name - The name of the frame you want to get.
* @param {integer} [cache=Phaser.Cache.IMAGE] - The cache to search. One of the Cache consts such as `Phaser.Cache.IMAGE` or `Phaser.Cache.SOUND`.
* @return {Phaser.Frame} The frame object.
*/
getFrameByName: function (key, name, cache) {
var data = this.getFrameData(key, cache);
if (data)
{
return data.getFrameByName(name);
}
else
{
return null;
}
},
/**
* Get a cached object by the URL.
* This only returns a value if you set Cache.autoResolveURL to `true` *before* starting the preload of any assets.
* Be aware that every call to this function makes a DOM src query, so use carefully and double-check for implications in your target browsers/devices.
*
* @method Phaser.Cache#getURL
* @param {string} url - The url for the object loaded to get from the cache.
* @return {object} The cached object.
*/
getURL: function (url) {
var url = this._resolveURL(url);
if (url)
{
return this._urlMap[url];
}
else
{
console.warn('Phaser.Cache.getUrl: Invalid url: "' + url + '" or Cache.autoResolveURL was false');
return null;
}
},
/**
* Gets all keys used in the requested Cache.
*
* @method Phaser.Cache#getKeys
* @param {integer} [cache=Phaser.Cache.IMAGE] - The Cache you wish to get the keys from. Can be any of the Cache consts such as `Phaser.Cache.IMAGE`, `Phaser.Cache.SOUND` etc.
* @return {Array} The array of keys in the requested cache.
*/
getKeys: function (cache) {
if (cache === undefined) { cache = Phaser.Cache.IMAGE; }
var out = [];
if (this._cacheMap[cache])
{
for (var key in this._cacheMap[cache])
{
if (key !== '__default' && key !== '__missing')
{
out.push(key);
}
}
}
return out;
},
/////////////////////
// Remove Methods //
/////////////////////
/**
* Removes a canvas from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeCanvas
* @param {string} key - Key of the asset you want to remove.
*/
removeCanvas: function (key) {
delete this._cache.canvas[key];
},
/**
* Removes an image from the cache.
*
* You can optionally elect to destroy it as well. This calls BaseTexture.destroy on it.
*
* Note that this only removes it from the Phaser Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeImage
* @param {string} key - Key of the asset you want to remove.
* @param {boolean} [destroyBaseTexture=true] - Should the BaseTexture behind this image also be destroyed?
*/
removeImage: function (key, destroyBaseTexture) {
if (destroyBaseTexture === undefined) { destroyBaseTexture = true; }
var img = this.getImage(key, true);
if (destroyBaseTexture && img.base)
{
img.base.destroy();
}
delete this._cache.image[key];
},
/**
* Removes a sound from the cache.
*
* If any `Phaser.Sound` objects use the audio file in the cache that you remove with this method, they will
* _automatically_ destroy themselves. If you wish to have full control over when Sounds are destroyed then
* you must finish your house-keeping and destroy them all yourself first, before calling this method.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeSound
* @param {string} key - Key of the asset you want to remove.
*/
removeSound: function (key) {
delete this._cache.sound[key];
},
/**
* Removes a text file from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeText
* @param {string} key - Key of the asset you want to remove.
*/
removeText: function (key) {
delete this._cache.text[key];
},
/**
* Removes a physics data file from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removePhysics
* @param {string} key - Key of the asset you want to remove.
*/
removePhysics: function (key) {
delete this._cache.physics[key];
},
/**
* Removes a tilemap from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeTilemap
* @param {string} key - Key of the asset you want to remove.
*/
removeTilemap: function (key) {
delete this._cache.tilemap[key];
},
/**
* Removes a binary file from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeBinary
* @param {string} key - Key of the asset you want to remove.
*/
removeBinary: function (key) {
delete this._cache.binary[key];
},
/**
* Removes a bitmap data from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeBitmapData
* @param {string} key - Key of the asset you want to remove.
*/
removeBitmapData: function (key) {
delete this._cache.bitmapData[key];
},
/**
* Removes a bitmap font from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeBitmapFont
* @param {string} key - Key of the asset you want to remove.
*/
removeBitmapFont: function (key) {
delete this._cache.bitmapFont[key];
},
/**
* Removes a json object from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeJSON
* @param {string} key - Key of the asset you want to remove.
*/
removeJSON: function (key) {
delete this._cache.json[key];
},
/**
* Removes a xml object from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeXML
* @param {string} key - Key of the asset you want to remove.
*/
removeXML: function (key) {
delete this._cache.xml[key];
},
/**
* Removes a video from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeVideo
* @param {string} key - Key of the asset you want to remove.
*/
removeVideo: function (key) {
delete this._cache.video[key];
},
/**
* Removes a shader from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeShader
* @param {string} key - Key of the asset you want to remove.
*/
removeShader: function (key) {
delete this._cache.shader[key];
},
/**
* Removes a Render Texture from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeRenderTexture
* @param {string} key - Key of the asset you want to remove.
*/
removeRenderTexture: function (key) {
delete this._cache.renderTexture[key];
},
/**
* Removes a Sprite Sheet from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeSpriteSheet
* @param {string} key - Key of the asset you want to remove.
*/
removeSpriteSheet: function (key) {
delete this._cache.spriteSheet[key];
},
/**
* Removes a Texture Atlas from the cache.
*
* Note that this only removes it from the Phaser.Cache. If you still have references to the data elsewhere
* then it will persist in memory.
*
* @method Phaser.Cache#removeTextureAtlas
* @param {string} key - Key of the asset you want to remove.
*/
removeTextureAtlas: function (key) {
delete this._cache.atlas[key];
},
/**
* Empties out all of the GL Textures from Images stored in the cache.
* This is called automatically when the WebGL context is lost and then restored.
*
* @method Phaser.Cache#clearGLTextures
* @protected
*/
clearGLTextures: function () {
for (var key in this._cache.image)
{
this._cache.image[key].base._glTextures = [];
}
},
/**
* Resolves a URL to its absolute form and stores it in Cache._urlMap as long as Cache.autoResolveURL is set to `true`.
* This is then looked-up by the Cache.getURL and Cache.checkURL calls.
*
* @method Phaser.Cache#_resolveURL
* @private
* @param {string} url - The URL to resolve. This is appended to Loader.baseURL.
* @param {object} [data] - The data associated with the URL to be stored to the URL Map.
* @return {string} The resolved URL.
*/
_resolveURL: function (url, data) {
if (!this.autoResolveURL)
{
return null;
}
this._urlResolver.src = this.game.load.baseURL + url;
this._urlTemp = this._urlResolver.src;
// Ensure no request is actually made
this._urlResolver.src = '';
// Record the URL to the map
if (data)
{
this._urlMap[this._urlTemp] = data;
}
return this._urlTemp;
},
/**
* Clears the cache. Removes every local cache object reference.
* If an object in the cache has a `destroy` method it will also be called.
*
* @method Phaser.Cache#destroy
*/
destroy: function () {
for (var i = 0; i < this._cacheMap.length; i++)
{
var cache = this._cacheMap[i];
for (var key in cache)
{
if (key !== '__default' && key !== '__missing')
{
if (cache[key]['destroy'])
{
cache[key].destroy();
}
delete cache[key];
}
}
}
this._urlMap = null;
this._urlResolver = null;
this._urlTemp = null;
}
};
Phaser.Cache.prototype.constructor = Phaser.Cache;
/* jshint wsh:true */
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* The Loader handles loading all external content such as Images, Sounds, Texture Atlases and data files.
*
* The loader uses a combination of tag loading (eg. Image elements) and XHR and provides progress and completion callbacks.
*
* Parallel loading (see {@link #enableParallel}) is supported and enabled by default.
* Load-before behavior of parallel resources is controlled by synchronization points as discussed in {@link #withSyncPoint}.
*
* Texture Atlases can be created with tools such as [Texture Packer](https://www.codeandweb.com/texturepacker/phaser) and
* [Shoebox](http://renderhjs.net/shoebox/)
*
* @class Phaser.Loader
* @param {Phaser.Game} game - A reference to the currently running game.
*/
Phaser.Loader = function (game) {
/**
* Local reference to game.
* @property {Phaser.Game} game
* @protected
*/
this.game = game;
/**
* Local reference to the Phaser.Cache.
* @property {Phaser.Cache} cache
* @protected
*/
this.cache = game.cache;
/**
* If true all calls to Loader.reset will be ignored. Useful if you need to create a load queue before swapping to a preloader state.
* @property {boolean} resetLocked
* @default
*/
this.resetLocked = false;
/**
* True if the Loader is in the process of loading the queue.
* @property {boolean} isLoading
* @default
*/
this.isLoading = false;
/**
* True if all assets in the queue have finished loading.
* @property {boolean} hasLoaded
* @default
*/
this.hasLoaded = false;
/**
* You can optionally link a progress sprite with {@link Phaser.Loader#setPreloadSprite setPreloadSprite}.
*
* This property is an object containing: sprite, rect, direction, width and height
*
* @property {?object} preloadSprite
* @protected
*/
this.preloadSprite = null;
/**
* The crossOrigin value applied to loaded images. Very often this needs to be set to 'anonymous'.
* @property {boolean|string} crossOrigin
* @default
*/
this.crossOrigin = false;
/**
* If you want to append a URL before the path of any asset you can set this here.
* Useful if allowing the asset base url to be configured outside of the game code.
* The string _must_ end with a "/".
*
* @property {string} baseURL
*/
this.baseURL = '';
/**
* The value of `path`, if set, is placed before any _relative_ file path given. For example:
*
* `load.path = "images/sprites/";
* load.image("ball", "ball.png");
* load.image("tree", "level1/oaktree.png");
* load.image("boom", "http://server.com/explode.png");`
*
* Would load the `ball` file from `images/sprites/ball.png` and the tree from
* `images/sprites/level1/oaktree.png` but the file `boom` would load from the URL
* given as it's an absolute URL.
*
* Please note that the path is added before the filename but *after* the baseURL (if set.)
*
* The string _must_ end with a "/".
*
* @property {string} path
*/
this.path = '';
/**
* Used to map the application mime-types to to the Accept header in XHR requests.
* If you don't require these mappings, or they cause problems on your server, then
* remove them from the headers object and the XHR request will not try to use them.
*
* This object can also be used to set the `X-Requested-With` header to
* `XMLHttpRequest` (or any other value you need). To enable this do:
*
* `this.load.headers.requestedWith = 'XMLHttpRequest'`
*
* before adding anything to the Loader. The XHR loader will then call:
*
* `setRequestHeader('X-Requested-With', this.headers['requestedWith'])`
*
* @property {object} headers
* @default
*/
this.headers = {
"requestedWith": false,
"json": "application/json",
"xml": "application/xml"
};
/**
* This event is dispatched when the loading process starts: before the first file has been requested,
* but after all the initial packs have been loaded.
*
* @property {Phaser.Signal} onLoadStart
*/
this.onLoadStart = new Phaser.Signal();
/**
* This event is dispatched when the final file in the load queue has either loaded or failed.
*
* @property {Phaser.Signal} onLoadComplete
*/
this.onLoadComplete = new Phaser.Signal();
/**
* This event is dispatched when an asset pack has either loaded or failed to load.
*
* This is called when the asset pack manifest file has loaded and successfully added its contents to the loader queue.
*
* Params: `(pack key, success?, total packs loaded, total packs)`
*
* @property {Phaser.Signal} onPackComplete
*/
this.onPackComplete = new Phaser.Signal();
/**
* This event is dispatched immediately before a file starts loading.
* It's possible the file may fail (eg. download error, invalid format) after this event is sent.
*
* Params: `(progress, file key, file url)`
*
* @property {Phaser.Signal} onFileStart
*/
this.onFileStart = new Phaser.Signal();
/**
* This event is dispatched when a file has either loaded or failed to load.
*
* Any function bound to this will receive the following parameters:
*
* progress, file key, success?, total loaded files, total files
*
* Where progress is a number between 1 and 100 (inclusive) representing the percentage of the load.
*
* @property {Phaser.Signal} onFileComplete
*/
this.onFileComplete = new Phaser.Signal();
/**
* This event is dispatched when a file (or pack) errors as a result of the load request.
*
* For files it will be triggered before `onFileComplete`. For packs it will be triggered before `onPackComplete`.
*
* Params: `(file key, file)`
*
* @property {Phaser.Signal} onFileError
*/
this.onFileError = new Phaser.Signal();
/**
* If true and if the browser supports XDomainRequest, it will be used in preference for XHR.
*
* This is only relevant for IE 9 and should _only_ be enabled for IE 9 clients when required by the server/CDN.
*
* @property {boolean} useXDomainRequest
* @deprecated This is only relevant for IE 9.
*/
this.useXDomainRequest = false;
/**
* @private
* @property {boolean} _warnedAboutXDomainRequest - Control number of warnings for using XDR outside of IE 9.
*/
this._warnedAboutXDomainRequest = false;
/**
* If true (the default) then parallel downloading will be enabled.
*
* To disable all parallel downloads this must be set to false prior to any resource being loaded.
*
* @property {boolean} enableParallel
*/
this.enableParallel = true;
/**
* The number of concurrent / parallel resources to try and fetch at once.
*
* Many current browsers limit 6 requests per domain; this is slightly conservative.
*
* @property {integer} maxParallelDownloads
* @protected
*/
this.maxParallelDownloads = 4;
/**
* A counter: if more than zero, files will be automatically added as a synchronization point.
* @property {integer} _withSyncPointDepth;
*/
this._withSyncPointDepth = 0;
/**
* Contains all the information for asset files (including packs) to load.
*
* File/assets are only removed from the list after all loading completes.
*
* @property {file[]} _fileList
* @private
*/
this._fileList = [];
/**
* Inflight files (or packs) that are being fetched/processed.
*
* This means that if there are any files in the flight queue there should still be processing
* going on; it should only be empty before or after loading.
*
* The files in the queue may have additional properties added to them,
* including `requestObject` which is normally the associated XHR.
*
* @property {file[]} _flightQueue
* @private
*/
this._flightQueue = [];
/**
* The offset into the fileList past all the complete (loaded or error) entries.
*
* @property {integer} _processingHead
* @private
*/
this._processingHead = 0;
/**
* True when the first file (not pack) has loading started.
* This used to to control dispatching `onLoadStart` which happens after any initial packs are loaded.
*
* @property {boolean} _initialPacksLoaded
* @private
*/
this._fileLoadStarted = false;
/**
* Total packs seen - adjusted when a pack is added.
* @property {integer} _totalPackCount
* @private
*/
this._totalPackCount = 0;
/**
* Total files seen - adjusted when a file is added.
* @property {integer} _totalFileCount
* @private
*/
this._totalFileCount = 0;
/**
* Total packs loaded - adjusted just prior to `onPackComplete`.
* @property {integer} _loadedPackCount
* @private
*/
this._loadedPackCount = 0;
/**
* Total files loaded - adjusted just prior to `onFileComplete`.
* @property {integer} _loadedFileCount
* @private
*/
this._loadedFileCount = 0;
};
/**
* @constant
* @type {number}
*/
Phaser.Loader.TEXTURE_ATLAS_JSON_ARRAY = 0;
/**
* @constant
* @type {number}
*/
Phaser.Loader.TEXTURE_ATLAS_JSON_HASH = 1;
/**
* @constant
* @type {number}
*/
Phaser.Loader.TEXTURE_ATLAS_XML_STARLING = 2;
/**
* @constant
* @type {number}
*/
Phaser.Loader.PHYSICS_LIME_CORONA_JSON = 3;
/**
* @constant
* @type {number}
*/
Phaser.Loader.PHYSICS_PHASER_JSON = 4;
/**
* @constant
* @type {number}
*/
Phaser.Loader.TEXTURE_ATLAS_JSON_PYXEL = 5;
Phaser.Loader.prototype = {
/**
* Set a Sprite to be a "preload" sprite by passing it to this method.
*
* A "preload" sprite will have its width or height crop adjusted based on the percentage of the loader in real-time.
* This allows you to easily make loading bars for games.
*
* The sprite will automatically be made visible when calling this.
*
* @method Phaser.Loader#setPreloadSprite
* @param {Phaser.Sprite|Phaser.Image} sprite - The sprite or image that will be cropped during the load.
* @param {number} [direction=0] - A value of zero means the sprite will be cropped horizontally, a value of 1 means its will be cropped vertically.
*/
setPreloadSprite: function (sprite, direction) {
direction = direction || 0;
this.preloadSprite = { sprite: sprite, direction: direction, width: sprite.width, height: sprite.height, rect: null };
if (direction === 0)
{
// Horizontal rect
this.preloadSprite.rect = new Phaser.Rectangle(0, 0, 1, sprite.height);
}
else
{
// Vertical rect
this.preloadSprite.rect = new Phaser.Rectangle(0, 0, sprite.width, 1);
}
sprite.crop(this.preloadSprite.rect);
sprite.visible = true;
},
/**
* Called automatically by ScaleManager when the game resizes in RESIZE scalemode.
*
* This can be used to adjust the preloading sprite size, eg.
*
* @method Phaser.Loader#resize
* @protected
*/
resize: function () {
if (this.preloadSprite && this.preloadSprite.height !== this.preloadSprite.sprite.height)
{
this.preloadSprite.rect.height = this.preloadSprite.sprite.height;
}
},
/**
* Check whether a file/asset with a specific key is queued to be loaded.
*
* To access a loaded asset use Phaser.Cache, eg. {@link Phaser.Cache#checkImageKey}
*
* @method Phaser.Loader#checkKeyExists
* @param {string} type - The type asset you want to check.
* @param {string} key - Key of the asset you want to check.
* @return {boolean} Return true if exists, otherwise return false.
*/
checkKeyExists: function (type, key) {
return this.getAssetIndex(type, key) > -1;
},
/**
* Get the queue-index of the file/asset with a specific key.
*
* Only assets in the download file queue will be found.
*
* @method Phaser.Loader#getAssetIndex
* @param {string} type - The type asset you want to check.
* @param {string} key - Key of the asset you want to check.
* @return {number} The index of this key in the filelist, or -1 if not found.
* The index may change and should only be used immediately following this call
*/
getAssetIndex: function (type, key) {
var bestFound = -1;
for (var i = 0; i < this._fileList.length; i++)
{
var file = this._fileList[i];
if (file.type === type && file.key === key)
{
bestFound = i;
// An already loaded/loading file may be superceded.
if (!file.loaded && !file.loading)
{
break;
}
}
}
return bestFound;
},
/**
* Find a file/asset with a specific key.
*
* Only assets in the download file queue will be found.
*
* @method Phaser.Loader#getAsset
* @param {string} type - The type asset you want to check.
* @param {string} key - Key of the asset you want to check.
* @return {any} Returns an object if found that has 2 properties: `index` and `file`; otherwise a non-true value is returned.
* The index may change and should only be used immediately following this call.
*/
getAsset: function (type, key) {
var fileIndex = this.getAssetIndex(type, key);
if (fileIndex > -1)
{
return { index: fileIndex, file: this._fileList[fileIndex] };
}
return false;
},
/**
* Reset the loader and clear any queued assets. If `Loader.resetLocked` is true this operation will abort.
*
* This will abort any loading and clear any queued assets.
*
* Optionally you can clear any associated events.
*
* @method Phaser.Loader#reset
* @protected
* @param {boolean} [hard=false] - If true then the preload sprite and other artifacts may also be cleared.
* @param {boolean} [clearEvents=false] - If true then the all Loader signals will have removeAll called on them.
*/
reset: function (hard, clearEvents) {
if (clearEvents === undefined) { clearEvents = false; }
if (this.resetLocked)
{
return;
}
if (hard)
{
this.preloadSprite = null;
}
this.isLoading = false;
this._processingHead = 0;
this._fileList.length = 0;
this._flightQueue.length = 0;
this._fileLoadStarted = false;
this._totalFileCount = 0;
this._totalPackCount = 0;
this._loadedPackCount = 0;
this._loadedFileCount = 0;
if (clearEvents)
{
this.onLoadStart.removeAll();
this.onLoadComplete.removeAll();
this.onPackComplete.removeAll();
this.onFileStart.removeAll();
this.onFileComplete.removeAll();
this.onFileError.removeAll();
}
},
/**
* Internal function that adds a new entry to the file list. Do not call directly.
*
* @method Phaser.Loader#addToFileList
* @protected
* @param {string} type - The type of resource to add to the list (image, audio, xml, etc).
* @param {string} key - The unique Cache ID key of this resource.
* @param {string} [url] - The URL the asset will be loaded from.
* @param {object} [properties=(none)] - Any additional properties needed to load the file. These are added directly to the added file object and overwrite any defaults.
* @param {boolean} [overwrite=false] - If true then this will overwrite a file asset of the same type/key. Otherwise it will only add a new asset. If overwrite is true, and the asset is already being loaded (or has been loaded), then it is appended instead.
* @param {string} [extension] - If no URL is given the Loader will sometimes auto-generate the URL based on the key, using this as the extension.
* @return {Phaser.Loader} This instance of the Phaser Loader.
*/
addToFileList: function (type, key, url, properties, overwrite, extension) {
if (overwrite === undefined) { overwrite = false; }
if (key === undefined || key === '')
{
console.warn("Phaser.Loader: Invalid or no key given of type " + type);
return this;
}
if (url === undefined || url === null)
{
if (extension)
{
url = key + extension;
}
else
{
console.warn("Phaser.Loader: No URL given for file type: " + type + " key: " + key);
return this;
}
}
var file = {
type: type,
key: key,
path: this.path,
url: url,
syncPoint: this._withSyncPointDepth > 0,
data: null,
loading: false,
loaded: false,
error: false
};
if (properties)
{
for (var prop in properties)
{
file[prop] = properties[prop];
}
}
var fileIndex = this.getAssetIndex(type, key);
if (overwrite && fileIndex > -1)
{
var currentFile = this._fileList[fileIndex];
if (!currentFile.loading && !currentFile.loaded)
{
this._fileList[fileIndex] = file;
}
else
{
this._fileList.push(file);
this._totalFileCount++;
}
}
else if (fileIndex === -1)
{
this._fileList.push(file);
this._totalFileCount++;
}
return this;
},
/**
* Internal function that replaces an existing entry in the file list with a new one. Do not call directly.
*
* @method Phaser.Loader#replaceInFileList
* @protected
* @param {string} type - The type of resource to add to the list (image, audio, xml, etc).
* @param {string} key - The unique Cache ID key of this resource.
* @param {string} url - The URL the asset will be loaded from.
* @param {object} properties - Any additional properties needed to load the file.
*/
replaceInFileList: function (type, key, url, properties) {
return this.addToFileList(type, key, url, properties, true);
},
/**
* Add a JSON resource pack ('packfile') to the Loader.
*
* A packfile is a JSON file that contains a list of assets to the be loaded.
* Please see the example 'loader/asset pack' in the Phaser Examples repository.
*
* Packs are always put before the first non-pack file that is not loaded / loading.
*
* This means that all packs added before any loading has started are added to the front
* of the file queue, in the order added.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* The URL of the packfile can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* @method Phaser.Loader#pack
* @param {string} key - Unique asset key of this resource pack.
* @param {string} [url] - URL of the Asset Pack JSON file. If you wish to pass a json object instead set this to null and pass the object as the data parameter.
* @param {object} [data] - The Asset Pack JSON data. Use this to pass in a json data object rather than loading it from a URL. TODO
* @param {object} [callbackContext=(loader)] - Some Loader operations, like Binary and Script require a context for their callbacks. Pass the context here.
* @return {Phaser.Loader} This Loader instance.
*/
pack: function (key, url, data, callbackContext) {
if (url === undefined) { url = null; }
if (data === undefined) { data = null; }
if (callbackContext === undefined) { callbackContext = null; }
if (!url && !data)
{
console.warn('Phaser.Loader.pack - Both url and data are null. One must be set.');
return this;
}
var pack = {
type: 'packfile',
key: key,
url: url,
path: this.path,
syncPoint: true,
data: null,
loading: false,
loaded: false,
error: false,
callbackContext: callbackContext
};
// A data object has been given
if (data)
{
if (typeof data === 'string')
{
data = JSON.parse(data);
}
pack.data = data || {};
// Already consider 'loaded'
pack.loaded = true;
}
// Add before first non-pack/no-loaded ~ last pack from start prior to loading
// (Read one past for splice-to-end)
for (var i = 0; i < this._fileList.length + 1; i++)
{
var file = this._fileList[i];
if (!file || (!file.loaded && !file.loading && file.type !== 'packfile'))
{
this._fileList.splice(i, 0, pack);
this._totalPackCount++;
break;
}
}
return this;
},
/**
* Adds an Image to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* Phaser can load all common image types: png, jpg, gif and any other format the browser can natively handle.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the image via `Cache.getImage(key)`
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.png". It will always add `.png` as the extension.
* If you do not desire this action then provide a URL.
*
* This method also supports passing in a texture object as the `url` argument. This allows you to load
* compressed textures into Phaser. You can also use `Loader.texture` to do this.
*
* Compressed Textures are a WebGL only feature, and require 3rd party tools to create.
* Available tools include Texture Packer, PVRTexTool, DirectX Texture Tool and Mali Texture Compression Tool.
*
* Supported texture compression formats are: PVRTC, S3TC and ETC1.
* Supported file formats are: PVR, DDS, KTX and PKM.
*
* The formats that support all 3 compression algorithms are PVR and KTX.
* PKM only supports ETC1, and DDS only S3TC for now.
*
* The texture path object looks like this:
*
* ```
* load.image('factory', {
* etc1: 'assets/factory_etc1.pkm',
* s3tc: 'assets/factory_dxt1.pvr',
* pvrtc: 'assets/factory_pvrtc.pvr',
* truecolor: 'assets/factory.png'
* });
* ```
*
* The `truecolor` property points to a standard PNG file, that will be used if none of the
* compressed formats are supported by the browser / GPU.
*
* @method Phaser.Loader#image
* @param {string} key - Unique asset key of this image file.
* @param {string|object} [url] - URL of an image file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png". Can also be a texture data object.
* @param {boolean} [overwrite=false] - If an unloaded file with a matching key already exists in the queue, this entry will overwrite it.
* @return {Phaser.Loader} This Loader instance.
*/
image: function (key, url, overwrite) {
if (typeof url === 'object')
{
return this.texture(key, url, overwrite);
}
else
{
return this.addToFileList('image', key, url, undefined, overwrite, '.png');
}
},
/**
* Adds a Compressed Texture Image to the current load queue.
*
* Compressed Textures are a WebGL only feature, and require 3rd party tools to create.
* Available tools include Texture Packer, PVRTexTool, DirectX Texture Tool and Mali Texture Compression Tool.
*
* Supported texture compression formats are: PVRTC, S3TC and ETC1.
* Supported file formats are: PVR, DDS, KTX and PKM.
*
* The formats that support all 3 compression algorithms are PVR and KTX.
* PKM only supports ETC1, and DDS only S3TC for now.
*
* The texture path object looks like this:
*
* ```
* load.texture('factory', {
* etc1: 'assets/factory_etc1.pkm',
* s3tc: 'assets/factory_dxt1.pvr',
* pvrtc: 'assets/factory_pvrtc.pvr',
* truecolor: 'assets/factory.png'
* });
* ```
*
* The `truecolor` property points to a standard PNG file, that will be used if none of the
* compressed formats are supported by the browser / GPU.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the image via `Cache.getImage(key)`
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.pvr". It will always add `.pvr` as the extension.
* If you do not desire this action then provide a URL.
*
* @method Phaser.Loader#texture
* @param {string} key - Unique asset key of this image file.
* @param {object} object - The texture path data object.
* @param {boolean} [overwrite=false] - If an unloaded file with a matching key already exists in the queue, this entry will overwrite it.
* @return {Phaser.Loader} This Loader instance.
*/
texture: function (key, object, overwrite) {
if (this.game.renderType === Phaser.WEBGL)
{
var compression = this.game.renderer.extensions.compression;
var exkey;
for (exkey in object)
{
if (exkey.toUpperCase() in compression)
{
return this.addToFileList('texture', key, object[exkey], undefined, overwrite, '.pvr');
}
}
}
// Check if we have a truecolor texture to fallback.
// Also catches calls to this function that are from a Canvas renderer
if (object['truecolor'])
{
this.addToFileList('image', key, object['truecolor'], undefined, overwrite, '.png');
}
return this;
},
/**
* Adds an array of images to the current load queue.
*
* It works by passing each element of the array to the Loader.image method.
*
* The files are **not** loaded immediately after calling this method. The files are added to the queue ready to be loaded when the loader starts.
*
* Phaser can load all common image types: png, jpg, gif and any other format the browser can natively handle.
*
* The keys must be unique Strings. They are used to add the files to the Phaser.Cache upon successful load.
*
* Retrieve the images via `Cache.getImage(key)`
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.png". It will always add `.png` as the extension.
* If you do not desire this action then provide a URL.
*
* @method Phaser.Loader#images
* @param {array} keys - An array of unique asset keys of the image files.
* @param {array} [urls] - Optional array of URLs. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png". If provided the URLs array length must match the keys array length.
* @return {Phaser.Loader} This Loader instance.
*/
images: function (keys, urls) {
if (Array.isArray(urls))
{
for (var i = 0; i < keys.length; i++)
{
this.image(keys[i], urls[i]);
}
}
else
{
for (var i = 0; i < keys.length; i++)
{
this.image(keys[i]);
}
}
return this;
},
/**
* Adds a Text file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getText(key)`
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.txt". It will always add `.txt` as the extension.
* If you do not desire this action then provide a URL.
*
* @method Phaser.Loader#text
* @param {string} key - Unique asset key of the text file.
* @param {string} [url] - URL of the text file. If undefined or `null` the url will be set to `<key>.txt`, i.e. if `key` was "alien" then the URL will be "alien.txt".
* @param {boolean} [overwrite=false] - If an unloaded file with a matching key already exists in the queue, this entry will overwrite it.
* @return {Phaser.Loader} This Loader instance.
*/
text: function (key, url, overwrite) {
return this.addToFileList('text', key, url, undefined, overwrite, '.txt');
},
/**
* Adds a JSON file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getJSON(key)`. JSON files are automatically parsed upon load.
* If you need to control when the JSON is parsed then use `Loader.text` instead and parse the text file as needed.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.json". It will always add `.json` as the extension.
* If you do not desire this action then provide a URL.
*
* @method Phaser.Loader#json
* @param {string} key - Unique asset key of the json file.
* @param {string} [url] - URL of the JSON file. If undefined or `null` the url will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.json".
* @param {boolean} [overwrite=false] - If an unloaded file with a matching key already exists in the queue, this entry will overwrite it.
* @return {Phaser.Loader} This Loader instance.
*/
json: function (key, url, overwrite) {
return this.addToFileList('json', key, url, undefined, overwrite, '.json');
},
/**
* Adds a fragment shader file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getShader(key)`.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "blur"
* and no URL is given then the Loader will set the URL to be "blur.frag". It will always add `.frag` as the extension.
* If you do not desire this action then provide a URL.
*
* @method Phaser.Loader#shader
* @param {string} key - Unique asset key of the fragment file.
* @param {string} [url] - URL of the fragment file. If undefined or `null` the url will be set to `<key>.frag`, i.e. if `key` was "blur" then the URL will be "blur.frag".
* @param {boolean} [overwrite=false] - If an unloaded file with a matching key already exists in the queue, this entry will overwrite it.
* @return {Phaser.Loader} This Loader instance.
*/
shader: function (key, url, overwrite) {
return this.addToFileList('shader', key, url, undefined, overwrite, '.frag');
},
/**
* Adds an XML file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getXML(key)`.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.xml". It will always add `.xml` as the extension.
* If you do not desire this action then provide a URL.
*
* @method Phaser.Loader#xml
* @param {string} key - Unique asset key of the xml file.
* @param {string} [url] - URL of the XML file. If undefined or `null` the url will be set to `<key>.xml`, i.e. if `key` was "alien" then the URL will be "alien.xml".
* @param {boolean} [overwrite=false] - If an unloaded file with a matching key already exists in the queue, this entry will overwrite it.
* @return {Phaser.Loader} This Loader instance.
*/
xml: function (key, url, overwrite) {
return this.addToFileList('xml', key, url, undefined, overwrite, '.xml');
},
/**
* Adds a JavaScript file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.js". It will always add `.js` as the extension.
* If you do not desire this action then provide a URL.
*
* Upon successful load the JavaScript is automatically turned into a script tag and executed, so be careful what you load!
*
* A callback, which will be invoked as the script tag has been created, can also be specified.
* The callback must return relevant `data`.
*
* @method Phaser.Loader#script
* @param {string} key - Unique asset key of the script file.
* @param {string} [url] - URL of the JavaScript file. If undefined or `null` the url will be set to `<key>.js`, i.e. if `key` was "alien" then the URL will be "alien.js".
* @param {function} [callback=(none)] - Optional callback that will be called after the script tag has loaded, so you can perform additional processing.
* @param {object} [callbackContext=(loader)] - The context under which the callback will be applied. If not specified it will use the Phaser Loader as the context.
* @return {Phaser.Loader} This Loader instance.
*/
script: function (key, url, callback, callbackContext) {
if (callback === undefined) { callback = false; }
if (callback !== false && callbackContext === undefined) { callbackContext = this; }
return this.addToFileList('script', key, url, { syncPoint: true, callback: callback, callbackContext: callbackContext }, false, '.js');
},
/**
* Adds a binary file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getBinary(key)`.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.bin". It will always add `.bin` as the extension.
* If you do not desire this action then provide a URL.
*
* It will be loaded via xhr with a responseType of "arraybuffer". You can specify an optional callback to process the file after load.
* When the callback is called it will be passed 2 parameters: the key of the file and the file data.
*
* WARNING: If a callback is specified the data will be set to whatever it returns. Always return the data object, even if you didn't modify it.
*
* @method Phaser.Loader#binary
* @param {string} key - Unique asset key of the binary file.
* @param {string} [url] - URL of the binary file. If undefined or `null` the url will be set to `<key>.bin`, i.e. if `key` was "alien" then the URL will be "alien.bin".
* @param {function} [callback=(none)] - Optional callback that will be passed the file after loading, so you can perform additional processing on it.
* @param {object} [callbackContext] - The context under which the callback will be applied. If not specified it will use the callback itself as the context.
* @return {Phaser.Loader} This Loader instance.
*/
binary: function (key, url, callback, callbackContext) {
if (callback === undefined) { callback = false; }
// Why is the default callback context the ..callback?
if (callback !== false && callbackContext === undefined) { callbackContext = callback; }
return this.addToFileList('binary', key, url, { callback: callback, callbackContext: callbackContext }, false, '.bin');
},
/**
* Adds a Sprite Sheet to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* To clarify the terminology that Phaser uses: A Sprite Sheet is an image containing frames, usually of an animation, that are all equal
* dimensions and often in sequence. For example if the frame size is 32x32 then every frame in the sprite sheet will be that size.
* Sometimes (outside of Phaser) the term "sprite sheet" is used to refer to a texture atlas.
* A Texture Atlas works by packing together images as best it can, using whatever frame sizes it likes, often with cropping and trimming
* the frames in the process. Software such as Texture Packer, Flash CC or Shoebox all generate texture atlases, not sprite sheets.
* If you've got an atlas then use `Loader.atlas` instead.
*
* The key must be a unique String. It is used to add the image to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getImage(key)`. Sprite sheets, being image based, live in the same Cache as all other Images.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
* and no URL is given then the Loader will set the URL to be "alien.png". It will always add `.png` as the extension.
* If you do not desire this action then provide a URL.
*
* @method Phaser.Loader#spritesheet
* @param {string} key - Unique asset key of the sheet file.
* @param {string} url - URL of the sprite sheet file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png".
* @param {number} frameWidth - Width in pixels of a single frame in the sprite sheet.
* @param {number} frameHeight - Height in pixels of a single frame in the sprite sheet.
* @param {number} [frameMax=-1] - How many frames in this sprite sheet. If not specified it will divide the whole image into frames.
* @param {number} [margin=0] - If the frames have been drawn with a margin, specify the amount here.
* @param {number} [spacing=0] - If the frames have been drawn with spacing between them, specify the amount here.
* @param {number} [skipFrames=0] - Skip a number of frames. Useful when there are multiple sprite sheets in one image.
* @return {Phaser.Loader} This Loader instance.
*/
spritesheet: function (key, url, frameWidth, frameHeight, frameMax, margin, spacing, skipFrames) {
if (frameMax === undefined) { frameMax = -1; }
if (margin === undefined) { margin = 0; }
if (spacing === undefined) { spacing = 0; }
if (skipFrames === undefined) { skipFrames = 0; }
return this.addToFileList('spritesheet', key, url, { frameWidth: frameWidth, frameHeight: frameHeight, frameMax: frameMax, margin: margin, spacing: spacing, skipFrames: skipFrames }, false, '.png');
},
/**
* Adds an audio file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getSound(key)`.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* Mobile warning: There are some mobile devices (certain iPad 2 and iPad Mini revisions) that cannot play 48000 Hz audio.
* When they try to play the audio becomes extremely distorted and buzzes, eventually crashing the sound system.
* The solution is to use a lower encoding rate such as 44100 Hz.
*
* @method Phaser.Loader#audio
* @param {string} key - Unique asset key of the audio file.
* @param {string|string[]|object[]} urls - Either a single string or an array of URIs or pairs of `{uri: .., type: ..}`.
* If an array is specified then the first URI (or URI + mime pair) that is device-compatible will be selected.
* For example: `"jump.mp3"`, `['jump.mp3', 'jump.ogg', 'jump.m4a']`, or `[{uri: "data:<opus_resource>", type: 'opus'}, 'fallback.mp3']`.
* BLOB and DATA URIs can be used but only support automatic detection when used in the pair form; otherwise the format must be manually checked before adding the resource.
* @param {boolean} [autoDecode=true] - When using Web Audio the audio files can either be decoded at load time or run-time.
* Audio files can't be played until they are decoded and, if specified, this enables immediate decoding. Decoding is a non-blocking async process, however it consumes huge amounts of CPU time on mobiles especially.
* @return {Phaser.Loader} This Loader instance.
*/
audio: function (key, urls, autoDecode) {
if (this.game.sound.noAudio)
{
return this;
}
if (autoDecode === undefined) { autoDecode = true; }
if (typeof urls === 'string')
{
urls = [urls];
}
return this.addToFileList('audio', key, urls, { buffer: null, autoDecode: autoDecode });
},
/**
* Adds an audio sprite file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Audio Sprites are a combination of audio files and a JSON configuration.
*
* The JSON follows the format of that created by https://github.com/tonistiigi/audiosprite
*
* Retrieve the file via `Cache.getSoundData(key)`.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* @method Phaser.Loader#audioSprite
* @param {string} key - Unique asset key of the audio file.
* @param {Array|string} urls - An array containing the URLs of the audio files, i.e.: [ 'audiosprite.mp3', 'audiosprite.ogg', 'audiosprite.m4a' ] or a single string containing just one URL.
* @param {string} [jsonURL=null] - The URL of the audiosprite configuration JSON object. If you wish to pass the data directly set this parameter to null.
* @param {string|object} [jsonData=null] - A JSON object or string containing the audiosprite configuration data. This is ignored if jsonURL is not null.
* @param {boolean} [autoDecode=true] - When using Web Audio the audio files can either be decoded at load time or run-time.
* Audio files can't be played until they are decoded and, if specified, this enables immediate decoding. Decoding is a non-blocking async process, however it consumes huge amounts of CPU time on mobiles especially.
* @return {Phaser.Loader} This Loader instance.
*/
audioSprite: function (key, urls, jsonURL, jsonData, autoDecode) {
if (this.game.sound.noAudio)
{
return this;
}
if (jsonURL === undefined) { jsonURL = null; }
if (jsonData === undefined) { jsonData = null; }
if (autoDecode === undefined) { autoDecode = true; }
this.audio(key, urls, autoDecode);
if (jsonURL)
{
this.json(key + '-audioatlas', jsonURL);
}
else if (jsonData)
{
if (typeof jsonData === 'string')
{
jsonData = JSON.parse(jsonData);
}
this.cache.addJSON(key + '-audioatlas', '', jsonData);
}
else
{
console.warn('Phaser.Loader.audiosprite - You must specify either a jsonURL or provide a jsonData object');
}
return this;
},
/**
* A legacy alias for Loader.audioSprite. Please see that method for documentation.
*
* @method Phaser.Loader#audiosprite
* @param {string} key - Unique asset key of the audio file.
* @param {Array|string} urls - An array containing the URLs of the audio files, i.e.: [ 'audiosprite.mp3', 'audiosprite.ogg', 'audiosprite.m4a' ] or a single string containing just one URL.
* @param {string} [jsonURL=null] - The URL of the audiosprite configuration JSON object. If you wish to pass the data directly set this parameter to null.
* @param {string|object} [jsonData=null] - A JSON object or string containing the audiosprite configuration data. This is ignored if jsonURL is not null.
* @param {boolean} [autoDecode=true] - When using Web Audio the audio files can either be decoded at load time or run-time.
* Audio files can't be played until they are decoded and, if specified, this enables immediate decoding. Decoding is a non-blocking async process, however it consumes huge amounts of CPU time on mobiles especially.
* @return {Phaser.Loader} This Loader instance.
*/
audiosprite: function (key, urls, jsonURL, jsonData, autoDecode) {
return this.audioSprite(key, urls, jsonURL, jsonData, autoDecode);
},
/**
* Adds a video file to the current load queue.
*
* The file is **not** loaded immediately after calling this method. The file is added to the queue ready to be loaded when the loader starts.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getVideo(key)`.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* You don't need to preload a video in order to play it in your game. See `Video.createVideoFromURL` for details.
*
* @method Phaser.Loader#video
* @param {string} key - Unique asset key of the video file.
* @param {string|string[]|object[]} urls - Either a single string or an array of URIs or pairs of `{uri: .., type: ..}`.
* If an array is specified then the first URI (or URI + mime pair) that is device-compatible will be selected.
* For example: `"boom.mp4"`, `['boom.mp4', 'boom.ogg', 'boom.webm']`, or `[{uri: "data:<opus_resource>", type: 'opus'}, 'fallback.mp4']`.
* BLOB and DATA URIs can be used but only support automatic detection when used in the pair form; otherwise the format must be manually checked before adding the resource.
* @param {string} [loadEvent='canplaythrough'] - This sets the Video source event to listen for before the load is considered complete.
* 'canplaythrough' implies the video has downloaded enough, and bandwidth is high enough that it can be played to completion.
* 'canplay' implies the video has downloaded enough to start playing, but not necessarily to finish.
* 'loadeddata' just makes sure that the video meta data and first frame have downloaded. Phaser uses this value automatically if the
* browser is detected as being Firefox and no `loadEvent` is given, otherwise it defaults to `canplaythrough`.
* @param {boolean} [asBlob=false] - Video files can either be loaded via the creation of a video element which has its src property set.
* Or they can be loaded via xhr, stored as binary data in memory and then converted to a Blob. This isn't supported in IE9 or Android 2.
* If you need to have the same video playing at different times across multiple Sprites then you need to load it as a Blob.
* @return {Phaser.Loader} This Loader instance.
*/
video: function (key, urls, loadEvent, asBlob) {
if (loadEvent === undefined)
{
if (this.game.device.firefox)
{
loadEvent = 'loadeddata';
}
else
{
loadEvent = 'canplaythrough';
}
}
if (asBlob === undefined) { asBlob = false; }
if (typeof urls === 'string')
{
urls = [urls];
}
return this.addToFileList('video', key, urls, { buffer: null, asBlob: asBlob, loadEvent: loadEvent });
},
/**
* Adds a Tile Map data file to the current load queue.
*
* Phaser can load data in two different formats: CSV and Tiled JSON.
*
* Tiled is a free software package, specifically for creating tilemaps, and is available from http://www.mapeditor.org
*
* You can choose to either load the data externally, by providing a URL to a json file.
* Or you can pass in a JSON object or String via the `data` parameter.
* If you pass a String the data is automatically run through `JSON.parse` and then immediately added to the Phaser.Cache.
*
* If a URL is provided the file is **not** loaded immediately after calling this method, but is added to the load queue.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getTilemapData(key)`. JSON files are automatically parsed upon load.
* If you need to control when the JSON is parsed then use `Loader.text` instead and parse the text file as needed.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified and no data is given then the Loader will take the key and create a filename from that.
* For example if the key is "level1" and no URL or data is given then the Loader will set the URL to be "level1.json".
* If you set the format to be Tilemap.CSV it will set the URL to be "level1.csv" instead.
*
* If you do not desire this action then provide a URL or data object.
*
* @method Phaser.Loader#tilemap
* @param {string} key - Unique asset key of the tilemap data.
* @param {string} [url] - URL of the tile map file. If undefined or `null` and no data is given the url will be set to `<key>.json`, i.e. if `key` was "level1" then the URL will be "level1.json".
* @param {object|string} [data] - An optional JSON data object. If given then the url is ignored and this JSON object is used for map data instead.
* @param {number} [format=Phaser.Tilemap.CSV] - The format of the map data. Either Phaser.Tilemap.CSV or Phaser.Tilemap.TILED_JSON.
* @return {Phaser.Loader} This Loader instance.
*/
tilemap: function (key, url, data, format) {
if (url === undefined) { url = null; }
if (data === undefined) { data = null; }
if (format === undefined) { format = Phaser.Tilemap.CSV; }
if (!url && !data)
{
if (format === Phaser.Tilemap.CSV)
{
url = key + '.csv';
}
else
{
url = key + '.json';
}
}
// A map data object has been given
if (data)
{
switch (format)
{
// A csv string or object has been given
case Phaser.Tilemap.CSV:
break;
// A json string or object has been given
case Phaser.Tilemap.TILED_JSON:
if (typeof data === 'string')
{
data = JSON.parse(data);
}
break;
}
this.cache.addTilemap(key, null, data, format);
}
else
{
this.addToFileList('tilemap', key, url, { format: format });
}
return this;
},
/**
* Adds a physics data file to the current load queue.
*
* The data must be in `Lime + Corona` JSON format. [Physics Editor](https://www.codeandweb.com) by code'n'web exports in this format natively.
*
* You can choose to either load the data externally, by providing a URL to a json file.
* Or you can pass in a JSON object or String via the `data` parameter.
* If you pass a String the data is automatically run through `JSON.parse` and then immediately added to the Phaser.Cache.
*
* If a URL is provided the file is **not** loaded immediately after calling this method, but is added to the load queue.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getJSON(key)`. JSON files are automatically parsed upon load.
* If you need to control when the JSON is parsed then use `Loader.text` instead and parse the text file as needed.
*
* The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the URL isn't specified and no data is given then the Loader will take the key and create a filename from that.
* For example if the key is "alien" and no URL or data is given then the Loader will set the URL to be "alien.json".
* It will always use `.json` as the extension.
*
* If you do not desire this action then provide a URL or data object.
*
* @method Phaser.Loader#physics
* @param {string} key - Unique asset key of the physics json data.
* @param {string} [url] - URL of the physics data file. If undefined or `null` and no data is given the url will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.json".
* @param {object|string} [data] - An optional JSON data object. If given then the url is ignored and this JSON object is used for physics data instead.
* @param {string} [format=Phaser.Physics.LIME_CORONA_JSON] - The format of the physics data.
* @return {Phaser.Loader} This Loader instance.
*/
physics: function (key, url, data, format) {
if (url === undefined) { url = null; }
if (data === undefined) { data = null; }
if (format === undefined) { format = Phaser.Physics.LIME_CORONA_JSON; }
if (!url && !data)
{
url = key + '.json';
}
// A map data object has been given
if (data)
{
if (typeof data === 'string')
{
data = JSON.parse(data);
}
this.cache.addPhysicsData(key, null, data, format);
}
else
{
this.addToFileList('physics', key, url, { format: format });
}
return this;
},
/**
* Adds Bitmap Font files to the current load queue.
*
* To create the Bitmap Font files you can use:
*
* BMFont (Windows, free): http://www.angelcode.com/products/bmfont/
* Glyph Designer (OS X, commercial): http://www.71squared.com/en/glyphdesigner
* Littera (Web-based, free): http://kvazars.com/littera/
*
* You can choose to either load the data externally, by providing a URL to an xml file.
* Or you can pass in an XML object or String via the `xmlData` parameter.
* If you pass a String the data is automatically run through `Loader.parseXML` and then immediately added to the Phaser.Cache.
*
* If URLs are provided the files are **not** loaded immediately after calling this method, but are added to the load queue.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getBitmapFont(key)`. XML files are automatically parsed upon load.
* If you need to control when the XML is parsed then use `Loader.text` instead and parse the XML file as needed.
*
* The URLs can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the textureURL isn't specified then the Loader will take the key and create a filename from that.
* For example if the key is "megaFont" and textureURL is null then the Loader will set the URL to be "megaFont.png".
* The same is true for the atlasURL. If atlasURL isn't specified and no atlasData has been provided then the Loader will
* set the atlasURL to be the key. For example if the key is "megaFont" the atlasURL will be set to "megaFont.xml".
*
* If you do not desire this action then provide URLs and / or a data object.
*
* @method Phaser.Loader#bitmapFont
* @param {string} key - Unique asset key of the bitmap font.
* @param {string} textureURL - URL of the Bitmap Font texture file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "megaFont" then the URL will be "megaFont.png".
* @param {string} atlasURL - URL of the Bitmap Font atlas file (xml/json). If undefined or `null` AND `atlasData` is null, the url will be set to `<key>.xml`, i.e. if `key` was "megaFont" then the URL will be "megaFont.xml".
* @param {object} atlasData - An optional Bitmap Font atlas in string form (stringified xml/json).
* @param {number} [xSpacing=0] - If you'd like to add additional horizontal spacing between the characters then set the pixel value here.
* @param {number} [ySpacing=0] - If you'd like to add additional vertical spacing between the lines then set the pixel value here.
* @return {Phaser.Loader} This Loader instance.
*/
bitmapFont: function (key, textureURL, atlasURL, atlasData, xSpacing, ySpacing) {
if (textureURL === undefined || textureURL === null)
{
textureURL = key + '.png';
}
if (atlasURL === undefined) { atlasURL = null; }
if (atlasData === undefined) { atlasData = null; }
if (atlasURL === null && atlasData === null)
{
atlasURL = key + '.xml';
}
if (xSpacing === undefined) { xSpacing = 0; }
if (ySpacing === undefined) { ySpacing = 0; }
// A URL to a json/xml atlas has been given
if (atlasURL)
{
this.addToFileList('bitmapfont', key, textureURL, { atlasURL: atlasURL, xSpacing: xSpacing, ySpacing: ySpacing });
}
else
{
// A stringified xml/json atlas has been given
if (typeof atlasData === 'string')
{
var json, xml;
try
{
json = JSON.parse(atlasData);
}
catch ( e )
{
xml = this.parseXml(atlasData);
}
if (!xml && !json)
{
throw new Error("Phaser.Loader. Invalid Bitmap Font atlas given");
}
this.addToFileList('bitmapfont', key, textureURL, { atlasURL: null, atlasData: json || xml,
atlasType: (!!json ? 'json' : 'xml'), xSpacing: xSpacing, ySpacing: ySpacing });
}
}
return this;
},
/**
* Adds a Texture Atlas file to the current load queue.
*
* Unlike `Loader.atlasJSONHash` this call expects the atlas data to be in a JSON Array format.
*
* To create the Texture Atlas you can use tools such as:
*
* [Texture Packer](https://www.codeandweb.com/texturepacker/phaser)
* [Shoebox](http://renderhjs.net/shoebox/)
*
* If using Texture Packer we recommend you enable "Trim sprite names".
* If your atlas software has an option to "rotate" the resulting frames, you must disable it.
*
* You can choose to either load the data externally, by providing a URL to a json file.
* Or you can pass in a JSON object or String via the `atlasData` parameter.
* If you pass a String the data is automatically run through `JSON.parse` and then immediately added to the Phaser.Cache.
*
* If URLs are provided the files are **not** loaded immediately after calling this method, but are added to the load queue.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getImage(key)`. JSON files are automatically parsed upon load.
* If you need to control when the JSON is parsed then use `Loader.text` instead and parse the JSON file as needed.
*
* The URLs can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the textureURL isn't specified then the Loader will take the key and create a filename from that.
* For example if the key is "player" and textureURL is null then the Loader will set the URL to be "player.png".
* The same is true for the atlasURL. If atlasURL isn't specified and no atlasData has been provided then the Loader will
* set the atlasURL to be the key. For example if the key is "player" the atlasURL will be set to "player.json".
*
* If you do not desire this action then provide URLs and / or a data object.
*
* @method Phaser.Loader#atlasJSONArray
* @param {string} key - Unique asset key of the texture atlas file.
* @param {string} [textureURL] - URL of the texture atlas image file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png".
* @param {string} [atlasURL] - URL of the texture atlas data file. If undefined or `null` and no atlasData is given, the url will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.json".
* @param {object} [atlasData] - A JSON data object. You don't need this if the data is being loaded from a URL.
* @return {Phaser.Loader} This Loader instance.
*/
atlasJSONArray: function (key, textureURL, atlasURL, atlasData) {
return this.atlas(key, textureURL, atlasURL, atlasData, Phaser.Loader.TEXTURE_ATLAS_JSON_ARRAY);
},
/**
* Adds a Texture Atlas file to the current load queue.
*
* Unlike `Loader.atlas` this call expects the atlas data to be in a JSON Hash format.
*
* To create the Texture Atlas you can use tools such as:
*
* [Texture Packer](https://www.codeandweb.com/texturepacker/phaser)
* [Shoebox](http://renderhjs.net/shoebox/)
*
* If using Texture Packer we recommend you enable "Trim sprite names".
* If your atlas software has an option to "rotate" the resulting frames, you must disable it.
*
* You can choose to either load the data externally, by providing a URL to a json file.
* Or you can pass in a JSON object or String via the `atlasData` parameter.
* If you pass a String the data is automatically run through `JSON.parse` and then immediately added to the Phaser.Cache.
*
* If URLs are provided the files are **not** loaded immediately after calling this method, but are added to the load queue.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getImage(key)`. JSON files are automatically parsed upon load.
* If you need to control when the JSON is parsed then use `Loader.text` instead and parse the JSON file as needed.
*
* The URLs can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the textureURL isn't specified then the Loader will take the key and create a filename from that.
* For example if the key is "player" and textureURL is null then the Loader will set the URL to be "player.png".
* The same is true for the atlasURL. If atlasURL isn't specified and no atlasData has been provided then the Loader will
* set the atlasURL to be the key. For example if the key is "player" the atlasURL will be set to "player.json".
*
* If you do not desire this action then provide URLs and / or a data object.
*
* @method Phaser.Loader#atlasJSONHash
* @param {string} key - Unique asset key of the texture atlas file.
* @param {string} [textureURL] - URL of the texture atlas image file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png".
* @param {string} [atlasURL] - URL of the texture atlas data file. If undefined or `null` and no atlasData is given, the url will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.json".
* @param {object} [atlasData] - A JSON data object. You don't need this if the data is being loaded from a URL.
* @return {Phaser.Loader} This Loader instance.
*/
atlasJSONHash: function (key, textureURL, atlasURL, atlasData) {
return this.atlas(key, textureURL, atlasURL, atlasData, Phaser.Loader.TEXTURE_ATLAS_JSON_HASH);
},
/**
* Adds a Texture Atlas file to the current load queue.
*
* This call expects the atlas data to be in the Starling XML data format.
*
* To create the Texture Atlas you can use tools such as:
*
* [Texture Packer](https://www.codeandweb.com/texturepacker/phaser)
* [Shoebox](http://renderhjs.net/shoebox/)
*
* If using Texture Packer we recommend you enable "Trim sprite names".
* If your atlas software has an option to "rotate" the resulting frames, you must disable it.
*
* You can choose to either load the data externally, by providing a URL to an xml file.
* Or you can pass in an XML object or String via the `atlasData` parameter.
* If you pass a String the data is automatically run through `Loader.parseXML` and then immediately added to the Phaser.Cache.
*
* If URLs are provided the files are **not** loaded immediately after calling this method, but are added to the load queue.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getImage(key)`. XML files are automatically parsed upon load.
* If you need to control when the XML is parsed then use `Loader.text` instead and parse the XML file as needed.
*
* The URLs can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the textureURL isn't specified then the Loader will take the key and create a filename from that.
* For example if the key is "player" and textureURL is null then the Loader will set the URL to be "player.png".
* The same is true for the atlasURL. If atlasURL isn't specified and no atlasData has been provided then the Loader will
* set the atlasURL to be the key. For example if the key is "player" the atlasURL will be set to "player.xml".
*
* If you do not desire this action then provide URLs and / or a data object.
*
* @method Phaser.Loader#atlasXML
* @param {string} key - Unique asset key of the texture atlas file.
* @param {string} [textureURL] - URL of the texture atlas image file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png".
* @param {string} [atlasURL] - URL of the texture atlas data file. If undefined or `null` and no atlasData is given, the url will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.xml".
* @param {object} [atlasData] - An XML data object. You don't need this if the data is being loaded from a URL.
* @return {Phaser.Loader} This Loader instance.
*/
atlasXML: function (key, textureURL, atlasURL, atlasData) {
if (atlasURL === undefined) { atlasURL = null; }
if (atlasData === undefined) { atlasData = null; }
if (!atlasURL && !atlasData)
{
atlasURL = key + '.xml';
}
return this.atlas(key, textureURL, atlasURL, atlasData, Phaser.Loader.TEXTURE_ATLAS_XML_STARLING);
},
/**
* Adds a Texture Atlas file to the current load queue.
*
* To create the Texture Atlas you can use tools such as:
*
* [Texture Packer](https://www.codeandweb.com/texturepacker/phaser)
* [Shoebox](http://renderhjs.net/shoebox/)
*
* If using Texture Packer we recommend you enable "Trim sprite names".
* If your atlas software has an option to "rotate" the resulting frames, you must disable it.
*
* You can choose to either load the data externally, by providing a URL to a json file.
* Or you can pass in a JSON object or String via the `atlasData` parameter.
* If you pass a String the data is automatically run through `JSON.parse` and then immediately added to the Phaser.Cache.
*
* If URLs are provided the files are **not** loaded immediately after calling this method, but are added to the load queue.
*
* The key must be a unique String. It is used to add the file to the Phaser.Cache upon successful load.
*
* Retrieve the file via `Cache.getImage(key)`. JSON files are automatically parsed upon load.
* If you need to control when the JSON is parsed then use `Loader.text` instead and parse the JSON file as needed.
*
* The URLs can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
*
* If the textureURL isn't specified then the Loader will take the key and create a filename from that.
* For example if the key is "player" and textureURL is null then the Loader will set the URL to be "player.png".
* The same is true for the atlasURL. If atlasURL isn't specified and no atlasData has been provided then the Loader will
* set the atlasURL to be the key. For example if the key is "player" the atlasURL will be set to "player.json".
*
* If you do not desire this action then provide URLs and / or a data object.
*
* @method Phaser.Loader#atlas
* @param {string} key - Unique asset key of the texture atlas file.
* @param {string} [textureURL] - URL of the texture atlas image file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png".
* @param {string} [atlasURL] - URL of the texture atlas data file. If undefined or `null` and no atlasData is given, the url will be set to `<key>.json`, i.e. if `key` was "alien" then the URL will be "alien.json".
* @param {object} [atlasData] - A JSON or XML data object. You don't need this if the data is being loaded from a URL.
* @param {number} [format] - The format of the data. Can be Phaser.Loader.TEXTURE_ATLAS_JSON_ARRAY (the default), Phaser.Loader.TEXTURE_ATLAS_JSON_HASH or Phaser.Loader.TEXTURE_ATLAS_XML_STARLING.
* @return {Phaser.Loader} This Loader instance.
*/
atlas: function (key, textureURL, atlasURL, atlasData, format) {
if (textureURL === undefined || textureURL === null)
{
textureURL = key + '.png';
}
if (atlasURL === undefined) { atlasURL = null; }
if (atlasData === undefined) { atlasData = null; }
if (format === undefined) { format = Phaser.Loader.TEXTURE_ATLAS_JSON_ARRAY; }
if (!atlasURL && !atlasData)
{
if (format === Phaser.Loader.TEXTURE_ATLAS_XML_STARLING)
{
atlasURL = key + '.xml';
}
else
{
atlasURL = key + '.json';
}
}
// A URL to a json/xml file has been given
if (atlasURL)
{
this.addToFileList('textureatlas', key, textureURL, { atlasURL: atlasURL, format: format });
}
else
{
switch (format)
{
// A json string or object has been given
case Phaser.Loader.TEXTURE_ATLAS_JSON_ARRAY:
if (typeof atlasData === 'string')
{
atlasData = JSON.parse(atlasData);
}
break;
// An xml string or object has been given
case Phaser.Loader.TEXTURE_ATLAS_XML_STARLING:
if (typeof atlasData === 'string')
{
var xml = this.parseXml(atlasData);
if (!xml)
{
throw new Error("Phaser.Loader. Invalid Texture Atlas XML given");
}
atlasData = xml;
}
break;
}
this.addToFileList('textureatlas', key, textureURL, { atlasURL: null, atlasData: atlasData, format: format });
}
return this;
},
/**
* Add a synchronization point to the assets/files added within the supplied callback.
*
* A synchronization point denotes that an asset _must_ be completely loaded before
* subsequent assets can be loaded. An asset marked as a sync-point does not need to wait
* for previous assets to load (unless they are sync-points). Resources, such as packs, may still
* be downloaded around sync-points, as long as they do not finalize loading.
*
* @method Phaser.Loader#withSyncPoints
* @param {function} callback - The callback is invoked and is supplied with a single argument: the loader.
* @param {object} [callbackContext=(loader)] - Context for the callback.
* @return {Phaser.Loader} This Loader instance.
*/
withSyncPoint: function (callback, callbackContext) {
this._withSyncPointDepth++;
try {
callback.call(callbackContext || this, this);
} finally {
this._withSyncPointDepth--;
}
return this;
},
/**
* Add a synchronization point to a specific file/asset in the load queue.
*
* This has no effect on already loaded assets.
*
* @method Phaser.Loader#addSyncPoint
* @param {string} type - The type of resource to turn into a sync point (image, audio, xml, etc).
* @param {string} key - Key of the file you want to turn into a sync point.
* @return {Phaser.Loader} This Loader instance.
* @see {@link Phaser.Loader#withSyncPoint withSyncPoint}
*/
addSyncPoint: function (type, key) {
var asset = this.getAsset(type, key);
if (asset)
{
asset.file.syncPoint = true;
}
return this;
},
/**
* Remove a file/asset from the loading queue.
*
* A file that is loaded or has started loading cannot be removed.
*
* @method Phaser.Loader#removeFile
* @protected
* @param {string} type - The type of resource to add to the list (image, audio, xml, etc).
* @param {string} key - Key of the file you want to remove.
*/
removeFile: function (type, key) {
var asset = this.getAsset(type, key);
if (asset)
{
if (!asset.loaded && !asset.loading)
{
this._fileList.splice(asset.index, 1);
}
}
},
/**
* Remove all file loading requests - this is _insufficient_ to stop current loading. Use `reset` instead.
*
* @method Phaser.Loader#removeAll
* @protected
*/
removeAll: function () {
this._fileList.length = 0;
this._flightQueue.length = 0;
},
/**
* Start loading the assets. Normally you don't need to call this yourself as the StateManager will do so.
*
* @method Phaser.Loader#start
*/
start: function () {
if (this.isLoading)
{
return;
}
this.hasLoaded = false;
this.isLoading = true;
this.updateProgress();
this.processLoadQueue();
},
/**
* Process the next item(s) in the file/asset queue.
*
* Process the queue and start loading enough items to fill up the inflight queue.
*
* If a sync-file is encountered then subsequent asset processing is delayed until it completes.
* The exception to this rule is that packfiles can be downloaded (but not processed) even if
* there appear other sync files (ie. packs) - this enables multiple packfiles to be fetched in parallel.
* such as during the start phaser.
*
* @method Phaser.Loader#processLoadQueue
* @private
*/
processLoadQueue: function () {
if (!this.isLoading)
{
console.warn('Phaser.Loader - active loading canceled / reset');
this.finishedLoading(true);
return;
}
// Empty the flight queue as applicable
for (var i = 0; i < this._flightQueue.length; i++)
{
var file = this._flightQueue[i];
if (file.loaded || file.error)
{
this._flightQueue.splice(i, 1);
i--;
file.loading = false;
file.requestUrl = null;
file.requestObject = null;
if (file.error)
{
this.onFileError.dispatch(file.key, file);
}
if (file.type !== 'packfile')
{
this._loadedFileCount++;
this.onFileComplete.dispatch(this.progress, file.key, !file.error, this._loadedFileCount, this._totalFileCount);
}
else if (file.type === 'packfile' && file.error)
{
// Non-error pack files are handled when processing the file queue
this._loadedPackCount++;
this.onPackComplete.dispatch(file.key, !file.error, this._loadedPackCount, this._totalPackCount);
}
}
}
// When true further non-pack file downloads are suppressed
var syncblock = false;
var inflightLimit = this.enableParallel ? Phaser.Math.clamp(this.maxParallelDownloads, 1, 12) : 1;
for (var i = this._processingHead; i < this._fileList.length; i++)
{
var file = this._fileList[i];
// Pack is fetched (ie. has data) and is currently at the start of the process queue.
if (file.type === 'packfile' && !file.error && file.loaded && i === this._processingHead)
{
// Processing the pack / adds more files
this.processPack(file);
this._loadedPackCount++;
this.onPackComplete.dispatch(file.key, !file.error, this._loadedPackCount, this._totalPackCount);
}
if (file.loaded || file.error)
{
// Item at the start of file list finished, can skip it in future
if (i === this._processingHead)
{
this._processingHead = i + 1;
}
}
else if (!file.loading && this._flightQueue.length < inflightLimit)
{
// -> not loaded/failed, not loading
if (file.type === 'packfile' && !file.data)
{
// Fetches the pack data: the pack is processed above as it reaches queue-start.
// (Packs do not trigger onLoadStart or onFileStart.)
this._flightQueue.push(file);
file.loading = true;
this.loadFile(file);
}
else if (!syncblock)
{
if (!this._fileLoadStarted)
{
this._fileLoadStarted = true;
this.onLoadStart.dispatch();
}
this._flightQueue.push(file);
file.loading = true;
this.onFileStart.dispatch(this.progress, file.key, file.url);
this.loadFile(file);
}
}
if (!file.loaded && file.syncPoint)
{
syncblock = true;
}
// Stop looking if queue full - or if syncblocked and there are no more packs.
// (As only packs can be loaded around a syncblock)
if (this._flightQueue.length >= inflightLimit ||
(syncblock && this._loadedPackCount === this._totalPackCount))
{
break;
}
}
this.updateProgress();
// True when all items in the queue have been advanced over
// (There should be no inflight items as they are complete - loaded/error.)
if (this._processingHead >= this._fileList.length)
{
this.finishedLoading();
}
else if (!this._flightQueue.length)
{
// Flight queue is empty but file list is not done being processed.
// This indicates a critical internal error with no known recovery.
console.warn("Phaser.Loader - aborting: processing queue empty, loading may have stalled");
var _this = this;
setTimeout(function () {
_this.finishedLoading(true);
}, 2000);
}
},
/**
* The loading is all finished.
*
* @method Phaser.Loader#finishedLoading
* @private
* @param {boolean} [abnormal=true] - True if the loading finished abnormally.
*/
finishedLoading: function (abnormal) {
if (this.hasLoaded)
{
return;
}
this.hasLoaded = true;
this.isLoading = false;
// If there were no files make sure to trigger the event anyway, for consistency
if (!abnormal && !this._fileLoadStarted)
{
this._fileLoadStarted = true;
this.onLoadStart.dispatch();
}
this.onLoadComplete.dispatch();
this.game.state.loadComplete();
this.reset();
},
/**
* Informs the loader that the given file resource has been fetched and processed;
* or such a request has failed.
*
* @method Phaser.Loader#asyncComplete
* @private
* @param {object} file
* @param {string} [error=''] - The error message, if any. No message implies no error.
*/
asyncComplete: function (file, errorMessage) {
if (errorMessage === undefined) { errorMessage = ''; }
file.loaded = true;
file.error = !!errorMessage;
if (errorMessage)
{
file.errorMessage = errorMessage;
console.warn('Phaser.Loader - ' + file.type + '[' + file.key + ']' + ': ' + errorMessage);
}
this.processLoadQueue();
},
/**
* Process pack data. This will usually modify the file list.
*
* @method Phaser.Loader#processPack
* @private
* @param {object} pack
*/
processPack: function (pack) {
var packData = pack.data[pack.key];
if (!packData)
{
console.warn('Phaser.Loader - ' + pack.key + ': pack has data, but not for pack key');
return;
}
for (var i = 0; i < packData.length; i++)
{
var file = packData[i];
switch (file.type)
{
case "image":
this.image(file.key, file.url, file.overwrite);
break;
case "text":
this.text(file.key, file.url, file.overwrite);
break;
case "json":
this.json(file.key, file.url, file.overwrite);
break;
case "xml":
this.xml(file.key, file.url, file.overwrite);
break;
case "script":
this.script(file.key, file.url, file.callback, pack.callbackContext || this);
break;
case "binary":
this.binary(file.key, file.url, file.callback, pack.callbackContext || this);
break;
case "spritesheet":
this.spritesheet(file.key, file.url, file.frameWidth, file.frameHeight, file.frameMax, file.margin, file.spacing, file.skipFrames);
break;
case "video":
this.video(file.key, file.urls);
break;
case "audio":
this.audio(file.key, file.urls, file.autoDecode);
break;
case "audiosprite":
this.audiosprite(file.key, file.urls, file.jsonURL, file.jsonData, file.autoDecode);
break;
case "tilemap":
this.tilemap(file.key, file.url, file.data, Phaser.Tilemap[file.format]);
break;
case "physics":
this.physics(file.key, file.url, file.data, Phaser.Loader[file.format]);
break;
case "bitmapFont":
this.bitmapFont(file.key, file.textureURL, file.atlasURL, file.atlasData, file.xSpacing, file.ySpacing);
break;
case "atlasJSONArray":
this.atlasJSONArray(file.key, file.textureURL, file.atlasURL, file.atlasData);
break;
case "atlasJSONHash":
this.atlasJSONHash(file.key, file.textureURL, file.atlasURL, file.atlasData);
break;
case "atlasXML":
this.atlasXML(file.key, file.textureURL, file.atlasURL, file.atlasData);
break;
case "atlas":
this.atlas(file.key, file.textureURL, file.atlasURL, file.atlasData, Phaser.Loader[file.format]);
break;
case "shader":
this.shader(file.key, file.url, file.overwrite);
break;
}
}
},
/**
* Transforms the asset URL.
*
* The default implementation prepends the baseURL if the url doesn't begin with http or //
*
* @method Phaser.Loader#transformUrl
* @protected
* @param {string} url - The url to transform.
* @param {object} file - The file object being transformed.
* @return {string} The transformed url. In rare cases where the url isn't specified it will return false instead.
*/
transformUrl: function (url, file) {
if (!url)
{
return false;
}
if (url.match(/^(?:blob:|data:|http:\/\/|https:\/\/|\/\/)/))
{
return url;
}
else
{
return this.baseURL + file.path + url;
}
},
/**
* Start fetching a resource.
*
* All code paths, async or otherwise, from this function must return to `asyncComplete`.
*
* @method Phaser.Loader#loadFile
* @private
* @param {object} file
*/
loadFile: function (file) {
// Image or Data?
switch (file.type)
{
case 'packfile':
this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.fileComplete);
break;
case 'image':
case 'spritesheet':
case 'textureatlas':
case 'bitmapfont':
this.loadImageTag(file);
break;
case 'audio':
file.url = this.getAudioURL(file.url);
if (file.url)
{
// WebAudio or Audio Tag?
if (this.game.sound.usingWebAudio)
{
this.xhrLoad(file, this.transformUrl(file.url, file), 'arraybuffer', this.fileComplete);
}
else if (this.game.sound.usingAudioTag)
{
this.loadAudioTag(file);
}
}
else
{
this.fileError(file, null, 'No supported audio URL specified or device does not have audio playback support');
}
break;
case 'video':
file.url = this.getVideoURL(file.url);
if (file.url)
{
if (file.asBlob)
{
this.xhrLoad(file, this.transformUrl(file.url, file), 'blob', this.fileComplete);
}
else
{
this.loadVideoTag(file);
}
}
else
{
this.fileError(file, null, 'No supported video URL specified or device does not have video playback support');
}
break;
case 'json':
this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.jsonLoadComplete);
break;
case 'xml':
this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.xmlLoadComplete);
break;
case 'tilemap':
if (file.format === Phaser.Tilemap.TILED_JSON)
{
this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.jsonLoadComplete);
}
else if (file.format === Phaser.Tilemap.CSV)
{
this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.csvLoadComplete);
}
else
{
this.asyncComplete(file, "invalid Tilemap format: " + file.format);
}
break;
case 'text':
case 'script':
case 'shader':
case 'physics':
this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.fileComplete);
break;
case 'texture':
if (file.key.split('_').pop() === 'truecolor')
{
this.loadImageTag(file);
}
break;
case 'binary':
this.xhrLoad(file, this.transformUrl(file.url, file), 'arraybuffer', this.fileComplete);
break;
}
},
/**
* Continue async loading through an Image tag.
* @private
*/
loadImageTag: function (file) {
var _this = this;
file.data = new Image();
file.data.name = file.key;
if (this.crossOrigin)
{
file.data.crossOrigin = this.crossOrigin;
}
file.data.onload = function () {
if (file.data.onload)
{
file.data.onload = null;
file.data.onerror = null;
_this.fileComplete(file);
}
};
file.data.onerror = function () {
if (file.data.onload)
{
file.data.onload = null;
file.data.onerror = null;
_this.fileError(file);
}
};
file.data.src = this.transformUrl(file.url, file);
// Image is immediately-available/cached
if (file.data.complete && file.data.width && file.data.height)
{
file.data.onload = null;
file.data.onerror = null;
this.fileComplete(file);
}
},
/**
* Continue async loading through a Video tag.
* @private
*/
loadVideoTag: function (file) {
var _this = this;
file.data = document.createElement("video");
file.data.name = file.key;
file.data.controls = false;
file.data.autoplay = false;
var videoLoadEvent = function () {
file.data.removeEventListener(file.loadEvent, videoLoadEvent, false);
file.data.onerror = null;
file.data.canplay = true;
Phaser.GAMES[_this.game.id].load.fileComplete(file);
};
file.data.onerror = function () {
file.data.removeEventListener(file.loadEvent, videoLoadEvent, false);
file.data.onerror = null;
file.data.canplay = false;
_this.fileError(file);
};
file.data.addEventListener(file.loadEvent, videoLoadEvent, false);
file.data.src = this.transformUrl(file.url, file);
file.data.load();
},
/**
* Continue async loading through an Audio tag.
* @private
*/
loadAudioTag: function (file) {
var _this = this;
if (this.game.sound.touchLocked)
{
// If audio is locked we can't do this yet, so need to queue this load request. Bum.
file.data = new Audio();
file.data.name = file.key;
file.data.preload = 'auto';
file.data.src = this.transformUrl(file.url, file);
this.fileComplete(file);
}
else
{
file.data = new Audio();
file.data.name = file.key;
var playThroughEvent = function () {
file.data.removeEventListener('canplaythrough', playThroughEvent, false);
file.data.onerror = null;
_this.fileComplete(file);
};
file.data.onerror = function () {
file.data.removeEventListener('canplaythrough', playThroughEvent, false);
file.data.onerror = null;
_this.fileError(file);
};
file.data.preload = 'auto';
file.data.src = this.transformUrl(file.url, file);
file.data.addEventListener('canplaythrough', playThroughEvent, false);
file.data.load();
}
},
/**
* Starts the xhr loader.
*
* This is designed specifically to use with asset file processing.
*
* @method Phaser.Loader#xhrLoad
* @private
* @param {object} file - The file/pack to load.
* @param {string} url - The URL of the file.
* @param {string} type - The xhr responseType.
* @param {function} onload - The function to call on success. Invoked in `this` context and supplied with `(file, xhr)` arguments.
* @param {function} [onerror=fileError] The function to call on error. Invoked in `this` context and supplied with `(file, xhr)` arguments.
*/
xhrLoad: function (file, url, type, onload, onerror) {
if (this.useXDomainRequest && window.XDomainRequest)
{
this.xhrLoadWithXDR(file, url, type, onload, onerror);
return;
}
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = type;
if (this.headers['requestedWith'] !== false)
{
xhr.setRequestHeader('X-Requested-With', this.headers['requestedWith']);
}
if (this.headers[file.type])
{
xhr.setRequestHeader('Accept', this.headers[file.type]);
}
onerror = onerror || this.fileError;
var _this = this;
xhr.onload = function () {
try {
if (xhr.readyState === 4 && xhr.status >= 400 && xhr.status <= 599) { // Handle HTTP status codes of 4xx and 5xx as errors, even if xhr.onerror was not called.
return onerror.call(_this, file, xhr);
}
else {
return onload.call(_this, file, xhr);
}
} catch (e) {
// If this was the last file in the queue and an error is thrown in the create method
// then it's caught here, so be sure we don't carry on processing it
if (!_this.hasLoaded)
{
_this.asyncComplete(file, e.message || 'Exception');
}
else
{
if (window['console'])
{
console.error(e);
}
}
}
};
xhr.onerror = function () {
try {
return onerror.call(_this, file, xhr);
} catch (e) {
if (!_this.hasLoaded)
{
_this.asyncComplete(file, e.message || 'Exception');
}
else
{
if (window['console'])
{
console.error(e);
}
}
}
};
file.requestObject = xhr;
file.requestUrl = url;
xhr.send();
},
/**
* Starts the xhr loader - using XDomainRequest.
* This should _only_ be used with IE 9. Phaser does not support IE 8 and XDR is deprecated in IE 10.
*
* This is designed specifically to use with asset file processing.
*
* @method Phaser.Loader#xhrLoad
* @private
* @param {object} file - The file/pack to load.
* @param {string} url - The URL of the file.
* @param {string} type - The xhr responseType.
* @param {function} onload - The function to call on success. Invoked in `this` context and supplied with `(file, xhr)` arguments.
* @param {function} [onerror=fileError] The function to call on error. Invoked in `this` context and supplied with `(file, xhr)` arguments.
* @deprecated This is only relevant for IE 9.
*/
xhrLoadWithXDR: function (file, url, type, onload, onerror) {
// Special IE9 magic .. only
if (!this._warnedAboutXDomainRequest &&
(!this.game.device.ie || this.game.device.ieVersion >= 10))
{
this._warnedAboutXDomainRequest = true;
console.warn("Phaser.Loader - using XDomainRequest outside of IE 9");
}
// Ref: http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
var xhr = new window.XDomainRequest();
xhr.open('GET', url, true);
xhr.responseType = type;
// XDomainRequest has a few quirks. Occasionally it will abort requests
// A way to avoid this is to make sure ALL callbacks are set even if not used
// More info here: http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9
xhr.timeout = 3000;
onerror = onerror || this.fileError;
var _this = this;
xhr.onerror = function () {
try {
return onerror.call(_this, file, xhr);
} catch (e) {
_this.asyncComplete(file, e.message || 'Exception');
}
};
xhr.ontimeout = function () {
try {
return onerror.call(_this, file, xhr);
} catch (e) {
_this.asyncComplete(file, e.message || 'Exception');
}
};
xhr.onprogress = function() {};
xhr.onload = function () {
try {
if (xhr.readyState === 4 && xhr.status >= 400 && xhr.status <= 599) { // Handle HTTP status codes of 4xx and 5xx as errors, even if xhr.onerror was not called.
return onerror.call(_this, file, xhr);
}
else {
return onload.call(_this, file, xhr);
}
return onload.call(_this, file, xhr);
} catch (e) {
_this.asyncComplete(file, e.message || 'Exception');
}
};
file.requestObject = xhr;
file.requestUrl = url;
// Note: The xdr.send() call is wrapped in a timeout to prevent an issue with the interface where some requests are lost
// if multiple XDomainRequests are being sent at the same time.
setTimeout(function () {
xhr.send();
}, 0);
},
/**
* Give a bunch of URLs, return the first URL that has an extension this device thinks it can play.
*
* It is assumed that the device can play "blob:" or "data:" URIs - There is no mime-type checking on data URIs.
*
* @method Phaser.Loader#getVideoURL
* @private
* @param {object[]|string[]} urls - See {@link #video} for format.
* @return {string} The URL to try and fetch; or null.
*/
getVideoURL: function (urls) {
for (var i = 0; i < urls.length; i++)
{
var url = urls[i];
var videoType;
if (url.uri) // {uri: .., type: ..} pair
{
videoType = url.type;
url = url.uri;
if (this.game.device.canPlayVideo(videoType))
{
return url;
}
}
else
{
// Assume direct-data URI can be played if not in a paired form; select immediately
if (url.indexOf("blob:") === 0 || url.indexOf("data:") === 0)
{
return url;
}
if (url.indexOf("?") >= 0) // Remove query from URL
{
url = url.substr(0, url.indexOf("?"));
}
var extension = url.substr((Math.max(0, url.lastIndexOf(".")) || Infinity) + 1);
videoType = extension.toLowerCase();
if (this.game.device.canPlayVideo(videoType))
{
return urls[i];
}
}
}
return null;
},
/**
* Give a bunch of URLs, return the first URL that has an extension this device thinks it can play.
*
* It is assumed that the device can play "blob:" or "data:" URIs - There is no mime-type checking on data URIs.
*
* @method Phaser.Loader#getAudioURL
* @private
* @param {object[]|string[]} urls - See {@link #audio} for format.
* @return {string} The URL to try and fetch; or null.
*/
getAudioURL: function (urls) {
if (this.game.sound.noAudio)
{
return null;
}
for (var i = 0; i < urls.length; i++)
{
var url = urls[i];
var audioType;
if (url.uri) // {uri: .., type: ..} pair
{
audioType = url.type;
url = url.uri;
if (this.game.device.canPlayAudio(audioType))
{
return url;
}
}
else
{
// Assume direct-data URI can be played if not in a paired form; select immediately
if (url.indexOf("blob:") === 0 || url.indexOf("data:") === 0)
{
return url;
}
if (url.indexOf("?") >= 0) // Remove query from URL
{
url = url.substr(0, url.indexOf("?"));
}
var extension = url.substr((Math.max(0, url.lastIndexOf(".")) || Infinity) + 1);
audioType = extension.toLowerCase();
if (this.game.device.canPlayAudio(audioType))
{
return urls[i];
}
}
}
return null;
},
/**
* Error occurred when loading a file.
*
* @method Phaser.Loader#fileError
* @private
* @param {object} file
* @param {?XMLHttpRequest} xhr - XHR request, unspecified if loaded via other means (eg. tags)
* @param {string} reason
*/
fileError: function (file, xhr, reason) {
var url = file.requestUrl || this.transformUrl(file.url, file);
var message = 'error loading asset from URL ' + url;
if (!reason && xhr)
{
reason = xhr.status;
}
if (reason)
{
message = message + ' (' + reason + ')';
}
this.asyncComplete(file, message);
},
/**
* Called when a file has been downloaded and needs to be processed further.
*
* @method Phaser.Loader#fileComplete
* @private
* @param {object} file - File loaded
* @param {?XMLHttpRequest} xhr - XHR request, unspecified if loaded via other means (eg. tags)
*/
fileComplete: function (file, xhr) {
var loadNext = true;
switch (file.type)
{
case 'packfile':
// Pack data must never be false-ish after it is fetched without error
var data = JSON.parse(xhr.responseText);
file.data = data || {};
break;
case 'texture':
if (file.data !== null)
{
this.cache.addCompressedTextureMetaData(file.key, file.url, file.url.split('.').pop().toLowerCase(), file.data);
}
else
{
this.cache.addCompressedTextureMetaData(file.key, file.url, file.url.split('.').pop().toLowerCase(), xhr.response);
}
break;
case 'image':
this.cache.addImage(file.key, file.url, file.data);
break;
case 'spritesheet':
this.cache.addSpriteSheet(file.key, file.url, file.data, file.frameWidth, file.frameHeight, file.frameMax, file.margin, file.spacing, file.skipFrames);
break;
case 'textureatlas':
if (file.atlasURL == null)
{
this.cache.addTextureAtlas(file.key, file.url, file.data, file.atlasData, file.format);
}
else
{
// Load the JSON or XML before carrying on with the next file
loadNext = false;
if (file.format === Phaser.Loader.TEXTURE_ATLAS_JSON_ARRAY || file.format === Phaser.Loader.TEXTURE_ATLAS_JSON_HASH || file.format === Phaser.Loader.TEXTURE_ATLAS_JSON_PYXEL)
{
this.xhrLoad(file, this.transformUrl(file.atlasURL, file), 'text', this.jsonLoadComplete);
}
else if (file.format === Phaser.Loader.TEXTURE_ATLAS_XML_STARLING)
{
this.xhrLoad(file, this.transformUrl(file.atlasURL, file), 'text', this.xmlLoadComplete);
}
else
{
throw new Error("Phaser.Loader. Invalid Texture Atlas format: " + file.format);
}
}
break;
case 'bitmapfont':
if (!file.atlasURL)
{
this.cache.addBitmapFont(file.key, file.url, file.data, file.atlasData, file.atlasType, file.xSpacing, file.ySpacing);
}
else
{
// Load the XML before carrying on with the next file
loadNext = false;
this.xhrLoad(file, this.transformUrl(file.atlasURL, file), 'text', function (file, xhr) {
var json;
try
{
// Try to parse as JSON, if it fails, then it's hopefully XML
json = JSON.parse(xhr.responseText);
}
catch (e) {}
if (!!json)
{
file.atlasType = 'json';
this.jsonLoadComplete(file, xhr);
}
else
{
file.atlasType = 'xml';
this.xmlLoadComplete(file, xhr);
}
});
}
break;
case 'video':
if (file.asBlob)
{
try
{
file.data = xhr.response;
}
catch (e)
{
throw new Error("Phaser.Loader. Unable to parse video file as Blob: " + file.key);
}
}
this.cache.addVideo(file.key, file.url, file.data, file.asBlob);
break;
case 'audio':
if (this.game.sound.usingWebAudio)
{
file.data = xhr.response;
this.cache.addSound(file.key, file.url, file.data, true, false);
if (file.autoDecode)
{
this.game.sound.decode(file.key);
}
}
else
{
this.cache.addSound(file.key, file.url, file.data, false, true);
}
break;
case 'text':
file.data = xhr.responseText;
this.cache.addText(file.key, file.url, file.data);
break;
case 'shader':
file.data = xhr.responseText;
this.cache.addShader(file.key, file.url, file.data);
break;
case 'physics':
var data = JSON.parse(xhr.responseText);
this.cache.addPhysicsData(file.key, file.url, data, file.format);
break;
case 'script':
file.data = document.createElement('script');
file.data.language = 'javascript';
file.data.type = 'text/javascript';
file.data.defer = false;
file.data.text = xhr.responseText;
document.head.appendChild(file.data);
if (file.callback)
{
file.data = file.callback.call(file.callbackContext, file.key, xhr.responseText);
}
break;
case 'binary':
if (file.callback)
{
file.data = file.callback.call(file.callbackContext, file.key, xhr.response);
}
else
{
file.data = xhr.response;
}
this.cache.addBinary(file.key, file.data);
break;
}
if (loadNext)
{
this.asyncComplete(file);
}
},
/**
* Successfully loaded a JSON file - only used for certain types.
*
* @method Phaser.Loader#jsonLoadComplete
* @private
* @param {object} file - File associated with this request
* @param {XMLHttpRequest} xhr
*/
jsonLoadComplete: function (file, xhr) {
var data = JSON.parse(xhr.responseText);
if (file.type === 'tilemap')
{
this.cache.addTilemap(file.key, file.url, data, file.format);
}
else if (file.type === 'bitmapfont')
{
this.cache.addBitmapFont(file.key, file.url, file.data, data, file.atlasType, file.xSpacing, file.ySpacing);
}
else if (file.type === 'json')
{
this.cache.addJSON(file.key, file.url, data);
}
else
{
this.cache.addTextureAtlas(file.key, file.url, file.data, data, file.format);
}
this.asyncComplete(file);
},
/**
* Successfully loaded a CSV file - only used for certain types.
*
* @method Phaser.Loader#csvLoadComplete
* @private
* @param {object} file - File associated with this request
* @param {XMLHttpRequest} xhr
*/
csvLoadComplete: function (file, xhr) {
var data = xhr.responseText;
this.cache.addTilemap(file.key, file.url, data, file.format);
this.asyncComplete(file);
},
/**
* Successfully loaded an XML file - only used for certain types.
*
* @method Phaser.Loader#xmlLoadComplete
* @private
* @param {object} file - File associated with this request
* @param {XMLHttpRequest} xhr
*/
xmlLoadComplete: function (file, xhr) {
// Always try parsing the content as XML, regardless of actually response type
var data = xhr.responseText;
var xml = this.parseXml(data);
if (!xml)
{
var responseType = xhr.responseType || xhr.contentType; // contentType for MS-XDomainRequest
console.warn('Phaser.Loader - ' + file.key + ': invalid XML (' + responseType + ')');
this.asyncComplete(file, "invalid XML");
return;
}
if (file.type === 'bitmapfont')
{
this.cache.addBitmapFont(file.key, file.url, file.data, xml, file.atlasType, file.xSpacing, file.ySpacing);
}
else if (file.type === 'textureatlas')
{
this.cache.addTextureAtlas(file.key, file.url, file.data, xml, file.format);
}
else if (file.type === 'xml')
{
this.cache.addXML(file.key, file.url, xml);
}
this.asyncComplete(file);
},
/**
* Parses string data as XML.
*
* @method Phaser.Loader#parseXml
* @private
* @param {string} data - The XML text to parse
* @return {?XMLDocument} Returns the xml document, or null if such could not parsed to a valid document.
*/
parseXml: function (data) {
var xml;
try
{
if (window['DOMParser'])
{
var domparser = new DOMParser();
xml = domparser.parseFromString(data, "text/xml");
}
else
{
xml = new ActiveXObject("Microsoft.XMLDOM");
// Why is this 'false'?
xml.async = 'false';
xml.loadXML(data);
}
}
catch (e)
{
xml = null;
}
if (!xml || !xml.documentElement || xml.getElementsByTagName("parsererror").length)
{
return null;
}
else
{
return xml;
}
},
/**
* Update the loading sprite progress.
*
* @method Phaser.Loader#nextFile
* @private
* @param {object} previousFile
* @param {boolean} success - Whether the previous asset loaded successfully or not.
*/
updateProgress: function () {
if (this.preloadSprite)
{
if (this.preloadSprite.direction === 0)
{
this.preloadSprite.rect.width = Math.floor((this.preloadSprite.width / 100) * this.progress);
}
else
{
this.preloadSprite.rect.height = Math.floor((this.preloadSprite.height / 100) * this.progress);
}
if (this.preloadSprite.sprite)
{
this.preloadSprite.sprite.updateCrop();
}
else
{
// We seem to have lost our sprite - maybe it was destroyed?
this.preloadSprite = null;
}
}
},
/**
* Returns the number of files that have already been loaded, even if they errored.
*
* @method Phaser.Loader#totalLoadedFiles
* @protected
* @return {number} The number of files that have already been loaded (even if they errored)
*/
totalLoadedFiles: function () {
return this._loadedFileCount;
},
/**
* Returns the number of files still waiting to be processed in the load queue. This value decreases as each file in the queue is loaded.
*
* @method Phaser.Loader#totalQueuedFiles
* @protected
* @return {number} The number of files that still remain in the load queue.
*/
totalQueuedFiles: function () {
return this._totalFileCount - this._loadedFileCount;
},
/**
* Returns the number of asset packs that have already been loaded, even if they errored.
*
* @method Phaser.Loader#totalLoadedPacks
* @protected
* @return {number} The number of asset packs that have already been loaded (even if they errored)
*/
totalLoadedPacks: function () {
return this._totalPackCount;
},
/**
* Returns the number of asset packs still waiting to be processed in the load queue. This value decreases as each pack in the queue is loaded.
*
* @method Phaser.Loader#totalQueuedPacks
* @protected
* @return {number} The number of asset packs that still remain in the load queue.
*/
totalQueuedPacks: function () {
return this._totalPackCount - this._loadedPackCount;
}
};
/**
* The non-rounded load progress value (from 0.0 to 100.0).
*
* A general indicator of the progress.
* It is possible for the progress to decrease, after `onLoadStart`, if more files are dynamically added.
*
* @name Phaser.Loader#progressFloat
* @property {number}
*/
Object.defineProperty(Phaser.Loader.prototype, "progressFloat", {
get: function () {
var progress = (this._loadedFileCount / this._totalFileCount) * 100;
return Phaser.Math.clamp(progress || 0, 0, 100);
}
});
/**
* The rounded load progress percentage value (from 0 to 100). See {@link Phaser.Loader#progressFloat}.
*
* @name Phaser.Loader#progress
* @property {integer}
*/
Object.defineProperty(Phaser.Loader.prototype, "progress", {
get: function () {
return Math.round(this.progressFloat);
}
});
Phaser.Loader.prototype.constructor = Phaser.Loader;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Phaser.LoaderParser parses data objects from Phaser.Loader that need more preparation before they can be inserted into the Cache.
*
* @class Phaser.LoaderParser
*/
Phaser.LoaderParser = {
/**
* Alias for xmlBitmapFont, for backwards compatibility.
*
* @method Phaser.LoaderParser.bitmapFont
* @param {object} xml - XML data you want to parse.
* @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
* @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
* @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
* @return {object} The parsed Bitmap Font data.
*/
bitmapFont: function (xml, baseTexture, xSpacing, ySpacing) {
return this.xmlBitmapFont(xml, baseTexture, xSpacing, ySpacing);
},
/**
* Parse a Bitmap Font from an XML file.
*
* @method Phaser.LoaderParser.xmlBitmapFont
* @param {object} xml - XML data you want to parse.
* @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
* @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
* @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
* @param {Phaser.Frame} [frame] - Optional Frame, if this font is embedded in a texture atlas.
* @return {object} The parsed Bitmap Font data.
*/
xmlBitmapFont: function (xml, baseTexture, xSpacing, ySpacing, frame) {
var data = {};
var info = xml.getElementsByTagName('info')[0];
var common = xml.getElementsByTagName('common')[0];
data.font = info.getAttribute('face');
data.size = parseInt(info.getAttribute('size'), 10);
data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) + ySpacing;
data.chars = {};
var letters = xml.getElementsByTagName('char');
var x = (frame) ? frame.x : 0;
var y = (frame) ? frame.y : 0;
for (var i = 0; i < letters.length; i++)
{
var charCode = parseInt(letters[i].getAttribute('id'), 10);
data.chars[charCode] = {
x: x + parseInt(letters[i].getAttribute('x'), 10),
y: y + parseInt(letters[i].getAttribute('y'), 10),
width: parseInt(letters[i].getAttribute('width'), 10),
height: parseInt(letters[i].getAttribute('height'), 10),
xOffset: parseInt(letters[i].getAttribute('xoffset'), 10),
yOffset: parseInt(letters[i].getAttribute('yoffset'), 10),
xAdvance: parseInt(letters[i].getAttribute('xadvance'), 10) + xSpacing,
kerning: {}
};
}
var kernings = xml.getElementsByTagName('kerning');
for (i = 0; i < kernings.length; i++)
{
var first = parseInt(kernings[i].getAttribute('first'), 10);
var second = parseInt(kernings[i].getAttribute('second'), 10);
var amount = parseInt(kernings[i].getAttribute('amount'), 10);
data.chars[second].kerning[first] = amount;
}
return this.finalizeBitmapFont(baseTexture, data);
},
/**
* Parse a Bitmap Font from a JSON file.
*
* @method Phaser.LoaderParser.jsonBitmapFont
* @param {object} json - JSON data you want to parse.
* @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
* @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
* @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
* @param {Phaser.Frame} [frame] - Optional Frame, if this font is embedded in a texture atlas.
* @return {object} The parsed Bitmap Font data.
*/
jsonBitmapFont: function (json, baseTexture, xSpacing, ySpacing, frame) {
var data = {
font: json.font.info._face,
size: parseInt(json.font.info._size, 10),
lineHeight: parseInt(json.font.common._lineHeight, 10) + ySpacing,
chars: {}
};
var x = (frame) ? frame.x : 0;
var y = (frame) ? frame.y : 0;
json.font.chars["char"].forEach(
function parseChar(letter) {
var charCode = parseInt(letter._id, 10);
data.chars[charCode] = {
x: x + parseInt(letter._x, 10),
y: y + parseInt(letter._y, 10),
width: parseInt(letter._width, 10),
height: parseInt(letter._height, 10),
xOffset: parseInt(letter._xoffset, 10),
yOffset: parseInt(letter._yoffset, 10),
xAdvance: parseInt(letter._xadvance, 10) + xSpacing,
kerning: {}
};
}
);
if (json.font.kernings && json.font.kernings.kerning)
{
json.font.kernings.kerning.forEach(
function parseKerning(kerning) {
data.chars[kerning._second].kerning[kerning._first] = parseInt(kerning._amount, 10);
}
);
}
return this.finalizeBitmapFont(baseTexture, data);
},
/**
* Finalize Bitmap Font parsing.
*
* @method Phaser.LoaderParser.finalizeBitmapFont
* @private
* @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
* @param {object} bitmapFontData - Pre-parsed bitmap font data.
* @return {object} The parsed Bitmap Font data.
*/
finalizeBitmapFont: function (baseTexture, bitmapFontData) {
Object.keys(bitmapFontData.chars).forEach(
function addTexture(charCode) {
var letter = bitmapFontData.chars[charCode];
letter.texture = new PIXI.Texture(baseTexture, new Phaser.Rectangle(letter.x, letter.y, letter.width, letter.height));
}
);
return bitmapFontData;
},
/**
* Extract PVR header from loaded binary
*
* @method Phaser.LoaderParser.pvr
* @param {ArrayBuffer} arrayBuffer
* @return {object} The parsed PVR file including texture data.
*/
pvr: function (arrayBuffer) {
// Reference: http://cdn.imgtec.com/sdk-documentation/PVR+File+Format.Specification.pdf
// PVR 3 header structure
// ---------------------------------------
// address: 0, size: 4 bytes version
// address: 4, size: 4 bytes flags
// address: 8, size: 8 bytes pixel format
// address: 16, size: 4 bytes color space
// address: 20, size: 4 bytes channel type
// address: 24, size: 4 bytes height
// address: 28, size: 4 bytes width
// address: 32, size: 4 bytes depth
// address: 36, size: 4 bytes number of surfaces
// address: 40, size: 4 bytes number of faces
// address: 44, size: 4 bytes number of mipmaps
// address: 48, size: 4 bytes meta data size
// ---------------------------------------
var uintArray = new Uint32Array(arrayBuffer.slice(0, 52)),
byteArray = new Uint8Array(arrayBuffer),
pvrHeader = null,
pixelFormat = (uintArray[3] << 32 | uintArray[2]),
compressionAlgorithm,
glExtensionFormat = 0;
if (uintArray[0] === 0x03525650 &&
[ // Validate WebGL Pixel Format
0, 1, 2, 3,
6, 7, 9, 11
].indexOf(pixelFormat) >= 0
) {
if (pixelFormat >= 0 && pixelFormat <= 3) {
compressionAlgorithm = 'PVRTC';
} else if (pixelFormat >= 7 && pixelFormat <= 11) {
compressionAlgorithm = 'S3TC';
} else if (pixelFormat === 6) {
compressionAlgorithm = 'ETC1';
}
switch (pixelFormat) {
case 0:
glExtensionFormat = 0x8C01;
break;
case 1:
glExtensionFormat = 0x8C03;
break;
case 2:
glExtensionFormat = 0x8C00;
break;
case 3:
glExtensionFormat = 0x8C02;
break;
case 6:
glExtensionFormat = 0x8D64;
break;
case 7:
glExtensionFormat = 0x83F1;
break;
case 9:
glExtensionFormat = 0x83F2;
break;
case 11:
glExtensionFormat = 0x83F3;
break;
default:
glExtensionFormat = -1;
}
pvrHeader = {
complete: true,
fileFormat: 'PVR',
compressionAlgorithm: compressionAlgorithm,
flags: uintArray[1],
pixelFormat: pixelFormat,
colorSpace: uintArray[4],
channelType: uintArray[5],
height: uintArray[6],
width: uintArray[7],
depth: uintArray[8],
numberOfSurfaces: uintArray[9],
numberOfFaces: uintArray[10],
numberOfMipmaps: uintArray[11],
metaDataSize: uintArray[12],
textureData: byteArray.subarray(52 + uintArray[12], byteArray.byteLength),
glExtensionFormat: glExtensionFormat
};
}
return pvrHeader;
},
/**
* Extract DDS header from loaded binary
*
* @method Phaser.LoaderParser.dds
* @param {ArrayBuffer} arrayBuffer
* @return {object} The parsed DDS file including texture data.
*/
dds: function (arrayBuffer) {
// Reference at: https://msdn.microsoft.com/en-us/library/windows/desktop/bb943982(v=vs.85).aspx
// DDS header structure
// ---------------------------------------
// address: 0, size: 4 bytes Identifier 'DDS '
// address: 4, size: 4 bytes size
// address: 8, size: 4 bytes flags
// address: 12, size: 4 bytes height
// address: 16, size: 4 bytes width
// address: 20, size: 4 bytes pitch or linear size
// address: 24, size: 4 bytes depth
// address: 28, size: 4 bytes mipmap count
// address: 32, size: 44 bytes reserved space 1
// address: 76, size: 4 pixel format size
// address: 80, size: 4 pixel format flag
// address: 84, size: 4 pixel format four CC
// address: 88, size: 4 pixel format bit count
// address: 92, size: 4 pixel format R bit mask
// address: 96, size: 4 pixel format G bit mask
// address: 100, size: 4 pixel format B bit mask
// address: 104, size: 4 pixel format A bit mask
// address: 108, size: 4 caps 1
// address: 112, size: 4 caps 2
// address: 116, size: 4 caps 3
// address: 120, size: 4 caps 4
// address: 124, size: 4 reserved 2
// -- DXT10 extension
// address: 130, size: 4 DXGI Format
// address: 134, size: 4 resource dimension
// address: 138, size: 4 misc flag
// address: 142, size: 4 array size
// address: 146, size: 4 misc flag 2
// ---------------------------------------
var byteArray = new Uint8Array(arrayBuffer),
uintArray = new Uint32Array(arrayBuffer),
ddsHeader = null;
if (byteArray[0] === 0x44 &&
byteArray[1] === 0x44 &&
byteArray[2] === 0x53 &&
byteArray[3] === 0x20) {
ddsHeader = {
complete: true,
fileFormat: 'DDS',
compressionAlgorithm: 'S3TC',
size: uintArray[1],
flags: uintArray[2],
height: uintArray[3],
width: uintArray[4],
pitch: uintArray[5],
depth: uintArray[6],
mipmapCount: uintArray[7],
formatSize: uintArray[19],
formatFlag: uintArray[19],
formatFourCC: [
String.fromCharCode(byteArray[84]),
String.fromCharCode(byteArray[85]),
String.fromCharCode(byteArray[86]),
String.fromCharCode(byteArray[87])
].join(''),
formatBitCount: uintArray[21],
formatRBitMask: uintArray[22],
formatGBitMask: uintArray[23],
formatBBitMask: uintArray[24],
formatABitMask: uintArray[25],
caps1: uintArray[26],
caps2: uintArray[27],
caps3: uintArray[28],
caps4: uintArray[29],
reserved2: uintArray[30],
DXGIFormat: null,
resourceDimension: null,
miscFlag: null,
arraySize: null,
textureData: byteArray.subarray(uintArray[1] + 4, byteArray.byteLength)
};
if (ddsHeader.formatFourCC === 'DX10') {
ddsHeader.DXGIFormat = uintArray[31];
ddsHeader.resourceDimension = uintArray[32];
ddsHeader.miscFlag = uintArray[33];
ddsHeader.arraySize = uintArray[34];
ddsHeader.miscFlag = uintArray[35];
}
}
return ddsHeader;
},
/**
* Extract KTX header from loaded binary
*
* @method Phaser.LoaderParser.ktx
* @param {ArrayBuffer} arrayBuffer
* @return {object} The parsed KTX file including texture data.
*/
ktx: function (arrayBuffer) {
// Reference: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
// KTX header structure
// ---------------------------------------
// address: 0, size 12 bytes: Identifier '«KTX 11»\r\n\x1A\n'
// address: 12, size 4 bytes: endianness
// address: 16, size 4 bytes: GL type
// address: 20, size 4 bytes: GL type size
// address: 24, size 4 bytes: GL format
// address: 28, size 4 bytes: GL internal format
// address: 32, size 4 bytes: GL base internal format
// address: 36, size 4 bytes: pixel width
// address: 40, size 4 bytes: pixel height
// address: 44, size 4 bytes: pixel depth
// address: 48, size 4 bytes: number of array elements
// address: 52, size 4 bytes: number of faces
// address: 56, size 4 bytes: number of mipmap levels
// address: 60, size 4 bytes: bytes of key value data
// address: 64, size 4 bytes: key and value bytes size
// address: X, size 1 byte : key and value
// address: X, size 1 byte : value padding
// address: X, size 4 byte : image size
// ---------------------------------------
var byteArray = new Uint8Array(arrayBuffer),
uintArray = new Uint32Array(arrayBuffer),
ktxHeader = null,
imageSizeOffset = 16 + (uintArray[15] / 4) | 0,
imageSize = uintArray[imageSizeOffset],
glInternalFormat = uintArray[7],
compressionAlgorithm = 0;
if (byteArray[0] === 0xAB && byteArray[1] === 0x4B &&
byteArray[2] === 0x54 && byteArray[3] === 0x58 &&
byteArray[4] === 0x20 && byteArray[5] === 0x31 &&
byteArray[6] === 0x31 && byteArray[7] === 0xBB &&
byteArray[8] === 0x0D && byteArray[9] === 0x0A &&
byteArray[10] === 0x1A && byteArray[11] === 0x0A &&
//Check if internal GL format is supported by WebGL
[
// ETC1
0x8D64,
// PVRTC
0x8C00, 0x8C01, 0x8C02, 0x8C03,
// DXTC | S3TC
0x83F0, 0x83F1, 0x83F2, 0x83F3
].indexOf(glInternalFormat) >= 0) {
switch (glInternalFormat) {
case 0x8D64:
compressionAlgorithm = 'ETC1';
break;
case 0x8C00:
case 0x8C01:
case 0x8C02:
case 0x8C03:
compressionAlgorithm = 'PVRTC';
break;
case 0x83F0:
case 0x83F1:
case 0x83F2:
case 0x83F3:
compressionAlgorithm = 'S3TC';
break;
}
ktxHeader = {
complete: true,
fileFormat: 'KTX',
compressionAlgorithm: compressionAlgorithm,
endianness: uintArray[3],
glType: uintArray[4],
glTypeSize: uintArray[5],
glFormat: uintArray[6],
glInternalFormat: uintArray[7],
glBaseInternalFormat: uintArray[8],
width: uintArray[9],
height: uintArray[10],
pixelDepth: uintArray[11],
numberOfArrayElements: uintArray[12],
numberOfFaces: uintArray[13],
numberOfMipmapLevels: uintArray[14],
bytesOfKeyValueData: uintArray[15],
keyAndValueByteSize: uintArray[16],
imageSize: imageSize,
textureData: byteArray.subarray((imageSizeOffset + 1) * 4, imageSize + 100)
};
}
return ktxHeader;
},
/**
* Extract PKM header from loaded binary
*
* @method Phaser.LoaderParser.pkm
* @param {ArrayBuffer} arrayBuffer
* @return {object} The parsed PKM file including texture data.
*/
pkm: function (arrayBuffer) {
// PKM header structure
// ---------------------------------------
// address: 0, size 4 bytes: for 'PKM '
// address: 4, size 2 bytes: for version
// address: 6, size 2 bytes: for type
// address: 8, size 2 bytes: for extended width
// address: 10, size 2 bytes: for extended height
// address: 12, size 2 bytes: for original width
// address: 14, size 2 bytes: for original height
// address: 16, texture data
// ---------------------------------------
var byteArray = new Uint8Array(arrayBuffer),
pkmHeader = null;
if (byteArray[0] === 0x50 &&
byteArray[1] === 0x4B &&
byteArray[2] === 0x4D &&
byteArray[3] === 0x20) {
pkmHeader = {
complete: true,
fileFormat: 'PKM',
compressionAlgorithm: 'ETC1',
format: ((byteArray[6] << 8 | byteArray[7])) & 0xFFFF,
width: ((byteArray[8] << 8 | byteArray[9])) & 0xFFFF,
height: ((byteArray[10] << 8 | byteArray[11])) & 0xFFFF,
originalWidth: ((byteArray[12] << 8 | byteArray[13])) & 0xFFFF,
originalHeight: ((byteArray[14] << 8 | byteArray[15])) & 0xFFFF,
textureData: byteArray.subarray(16, byteArray.length)
};
}
return pkmHeader;
}
};
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is a stub for the Phaser SoundManager.
* It allows you to exclude the default Sound Manager from your build, without making Game crash.
*/
Phaser.SoundManager = function () {
this.muteOnPause = false;
};
Phaser.SoundManager.prototype.boot = function () {};
Phaser.SoundManager.prototype.update = function () {};
Phaser.SoundManager.prototype.destroy = function () {};
Phaser.SoundManager.prototype.setMute = function () {};
Phaser.SoundManager.prototype.unsetMute = function () {};
Phaser.SoundManager.prototype.constructor = Phaser.SoundManager;
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* @classdesc
* The ScaleManager object handles the the scaling, resizing, and alignment of the
* Game size and the game Display canvas.
*
* The Game size is the logical size of the game; the Display canvas has size as an HTML element.
*
* The calculations of these are heavily influenced by the bounding Parent size which is the computed
* dimensions of the Display canvas's Parent container/element - the _effective CSS rules of the
* canvas's Parent element play an important role_ in the operation of the ScaleManager.
*
* The Display canvas - or Game size, depending {@link #scaleMode} - is updated to best utilize the Parent size.
* When in Fullscreen mode or with {@link #parentIsWindow} the Parent size is that of the visual viewport (see {@link Phaser.ScaleManager#getParentBounds getParentBounds}).
*
* Parent and Display canvas containment guidelines:
*
* - Style the Parent element (of the game canvas) to control the Parent size and
* thus the Display canvas's size and layout.
*
* - The Parent element's CSS styles should _effectively_ apply maximum (and minimum) bounding behavior.
*
* - The Parent element should _not_ apply a padding as this is not accounted for.
* If a padding is required apply it to the Parent's parent or apply a margin to the Parent.
* If you need to add a border, margin or any other CSS around your game container, then use a parent element and
* apply the CSS to this instead, otherwise you'll be constantly resizing the shape of the game container.
*
* - The Display canvas layout CSS styles (i.e. margins, size) should not be altered/specified as
* they may be updated by the ScaleManager.
*
* @description
* Create a new ScaleManager object - this is done automatically by {@link Phaser.Game}
*
* The `width` and `height` constructor parameters can either be a number which represents pixels or a string that represents a percentage: e.g. `800` (for 800 pixels) or `"80%"` for 80%.
*
* @class
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {number|string} width - The width of the game. See above.
* @param {number|string} height - The height of the game. See above.
*/
Phaser.ScaleManager = function (game, width, height) {
/**
* A reference to the currently running game.
* @property {Phaser.Game} game
* @protected
* @readonly
*/
this.game = game;
/**
* Provides access to some cross-device DOM functions.
* @property {Phaser.DOM} dom
* @protected
* @readonly
*/
this.dom = Phaser.DOM;
/**
* _EXPERIMENTAL:_ A responsive grid on which you can align game objects.
* @property {Phaser.FlexGrid} grid
* @public
*/
this.grid = null;
/**
* Target width (in pixels) of the Display canvas.
* @property {number} width
* @readonly
*/
this.width = 0;
/**
* Target height (in pixels) of the Display canvas.
* @property {number} height
* @readonly
*/
this.height = 0;
/**
* Minimum width the canvas should be scaled to (in pixels).
* Change with {@link #setMinMax}.
* @property {?number} minWidth
* @readonly
* @protected
*/
this.minWidth = null;
/**
* Maximum width the canvas should be scaled to (in pixels).
* If null it will scale to whatever width the browser can handle.
* Change with {@link #setMinMax}.
* @property {?number} maxWidth
* @readonly
* @protected
*/
this.maxWidth = null;
/**
* Minimum height the canvas should be scaled to (in pixels).
* Change with {@link #setMinMax}.
* @property {?number} minHeight
* @readonly
* @protected
*/
this.minHeight = null;
/**
* Maximum height the canvas should be scaled to (in pixels).
* If null it will scale to whatever height the browser can handle.
* Change with {@link #setMinMax}.
* @property {?number} maxHeight
* @readonly
* @protected
*/
this.maxHeight = null;
/**
* The offset coordinates of the Display canvas from the top-left of the browser window.
* The is used internally by Phaser.Pointer (for Input) and possibly other types.
* @property {Phaser.Point} offset
* @readonly
* @protected
*/
this.offset = new Phaser.Point();
/**
* If true, the game should only run in a landscape orientation.
* Change with {@link #forceOrientation}.
* @property {boolean} forceLandscape
* @readonly
* @default
* @protected
*/
this.forceLandscape = false;
/**
* If true, the game should only run in a portrait
* Change with {@link #forceOrientation}.
* @property {boolean} forcePortrait
* @readonly
* @default
* @protected
*/
this.forcePortrait = false;
/**
* True if {@link #forceLandscape} or {@link #forcePortrait} are set and do not agree with the browser orientation.
*
* This value is not updated immediately.
*
* @property {boolean} incorrectOrientation
* @readonly
* @protected
*/
this.incorrectOrientation = false;
/**
* See {@link #pageAlignHorizontally}.
* @property {boolean} _pageAlignHorizontally
* @private
*/
this._pageAlignHorizontally = false;
/**
* See {@link #pageAlignVertically}.
* @property {boolean} _pageAlignVertically
* @private
*/
this._pageAlignVertically = false;
/**
* This signal is dispatched when the orientation changes _or_ the validity of the current orientation changes.
*
* The signal is supplied with the following arguments:
* - `scale` - the ScaleManager object
* - `prevOrientation`, a string - The previous orientation as per {@link Phaser.ScaleManager#screenOrientation screenOrientation}.
* - `wasIncorrect`, a boolean - True if the previous orientation was last determined to be incorrect.
*
* Access the current orientation and validity with `scale.screenOrientation` and `scale.incorrectOrientation`.
* Thus the following tests can be done:
*
* // The orientation itself changed:
* scale.screenOrientation !== prevOrientation
* // The orientation just became incorrect:
* scale.incorrectOrientation && !wasIncorrect
*
* It is possible that this signal is triggered after {@link #forceOrientation} so the orientation
* correctness changes even if the orientation itself does not change.
*
* This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused.
*
* @property {Phaser.Signal} onOrientationChange
* @public
*/
this.onOrientationChange = new Phaser.Signal();
/**
* This signal is dispatched when the browser enters an incorrect orientation, as defined by {@link #forceOrientation}.
*
* This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused.
*
* @property {Phaser.Signal} enterIncorrectOrientation
* @public
*/
this.enterIncorrectOrientation = new Phaser.Signal();
/**
* This signal is dispatched when the browser leaves an incorrect orientation, as defined by {@link #forceOrientation}.
*
* This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused.
*
* @property {Phaser.Signal} leaveIncorrectOrientation
* @public
*/
this.leaveIncorrectOrientation = new Phaser.Signal();
/**
* This boolean provides you with a way to determine if the browser is in Full Screen
* mode (via the Full Screen API), and Phaser was the one responsible for activating it.
*
* It's possible that ScaleManager.isFullScreen returns `true` even if Phaser wasn't the
* one that made the browser go full-screen, so this flag lets you determine that.
*
* @property {boolean} hasPhaserSetFullScreen
* @default
*/
this.hasPhaserSetFullScreen = false;
/**
* If specified, this is the DOM element on which the Fullscreen API enter request will be invoked.
* The target element must have the correct CSS styling and contain the Display canvas.
*
* The elements style will be modified (ie. the width and height might be set to 100%)
* but it will not be added to, removed from, or repositioned within the DOM.
* An attempt is made to restore relevant style changes when fullscreen mode is left.
*
* For pre-2.2.0 behavior set `game.scale.fullScreenTarget = game.canvas`.
*
* @property {?DOMElement} fullScreenTarget
* @default
*/
this.fullScreenTarget = null;
/**
* The fullscreen target, as created by {@link #createFullScreenTarget}.
* This is not set if {@link #fullScreenTarget} is used and is cleared when fullscreen mode ends.
* @property {?DOMElement} _createdFullScreenTarget
* @private
*/
this._createdFullScreenTarget = null;
/**
* This signal is dispatched when fullscreen mode is ready to be initialized but
* before the fullscreen request.
*
* The signal is passed two arguments: `scale` (the ScaleManager), and an object in the form `{targetElement: DOMElement}`.
*
* The `targetElement` is the {@link #fullScreenTarget} element,
* if such is assigned, or a new element created by {@link #createFullScreenTarget}.
*
* Custom CSS styling or resets can be applied to `targetElement` as required.
*
* If `targetElement` is _not_ the same element as {@link #fullScreenTarget}:
* - After initialization the Display canvas is moved onto the `targetElement` for
* the duration of the fullscreen mode, and restored to it's original DOM location when fullscreen is exited.
* - The `targetElement` is moved/re-parented within the DOM and may have its CSS styles updated.
*
* The behavior of a pre-assigned target element is covered in {@link Phaser.ScaleManager#fullScreenTarget fullScreenTarget}.
*
* @property {Phaser.Signal} onFullScreenInit
* @public
*/
this.onFullScreenInit = new Phaser.Signal();
/**
* This signal is dispatched when the browser enters or leaves fullscreen mode, if supported.
*
* The signal is supplied with a single argument: `scale` (the ScaleManager). Use `scale.isFullScreen` to determine
* if currently running in Fullscreen mode.
*
* @property {Phaser.Signal} onFullScreenChange
* @public
*/
this.onFullScreenChange = new Phaser.Signal();
/**
* This signal is dispatched when the browser fails to enter fullscreen mode;
* or if the device does not support fullscreen mode and `startFullScreen` is invoked.
*
* The signal is supplied with a single argument: `scale` (the ScaleManager).
*
* @property {Phaser.Signal} onFullScreenError
* @public
*/
this.onFullScreenError = new Phaser.Signal();
/**
* The _last known_ orientation of the screen, as defined in the Window Screen Web API.
* See {@link Phaser.DOM.getScreenOrientation} for possible values.
*
* @property {string} screenOrientation
* @readonly
* @public
*/
this.screenOrientation = this.dom.getScreenOrientation();
/**
* The _current_ scale factor based on the game dimensions vs. the scaled dimensions.
* @property {Phaser.Point} scaleFactor
* @readonly
*/
this.scaleFactor = new Phaser.Point(1, 1);
/**
* The _current_ inversed scale factor. The displayed dimensions divided by the game dimensions.
* @property {Phaser.Point} scaleFactorInversed
* @readonly
* @protected
*/
this.scaleFactorInversed = new Phaser.Point(1, 1);
/**
* The Display canvas is aligned by adjusting the margins; the last margins are stored here.
*
* @property {Bounds-like} margin
* @readonly
* @protected
*/
this.margin = {left: 0, top: 0, right: 0, bottom: 0, x: 0, y: 0};
/**
* The bounds of the scaled game. The x/y will match the offset of the canvas element and the width/height the scaled width and height.
* @property {Phaser.Rectangle} bounds
* @readonly
*/
this.bounds = new Phaser.Rectangle();
/**
* The aspect ratio of the scaled Display canvas.
* @property {number} aspectRatio
* @readonly
*/
this.aspectRatio = 0;
/**
* The aspect ratio of the original game dimensions.
* @property {number} sourceAspectRatio
* @readonly
*/
this.sourceAspectRatio = 0;
/**
* The native browser events from Fullscreen API changes.
* @property {any} event
* @readonly
* @private
*/
this.event = null;
/**
* The edges on which to constrain the game Display/canvas in _addition_ to the restrictions of the parent container.
*
* The properties are strings and can be '', 'visual', 'layout', or 'layout-soft'.
* - If 'visual', the edge will be constrained to the Window / displayed screen area
* - If 'layout', the edge will be constrained to the CSS Layout bounds
* - An invalid value is treated as 'visual'
*
* @member
* @property {string} bottom
* @property {string} right
* @default
*/
this.windowConstraints = {
right: 'layout',
bottom: ''
};
/**
* Various compatibility settings.
* A value of "(auto)" indicates the setting is configured based on device and runtime information.
*
* A {@link #refresh} may need to be performed after making changes.
*
* @protected
*
* @property {boolean} [supportsFullScreen=(auto)] - True only if fullscreen support will be used. (Changing to fullscreen still might not work.)
*
* @property {boolean} [orientationFallback=(auto)] - See {@link Phaser.DOM.getScreenOrientation}.
*
* @property {boolean} [noMargins=false] - If true then the Display canvas's margins will not be updated anymore: existing margins must be manually cleared. Disabling margins prevents automatic canvas alignment/centering, possibly in fullscreen.
*
* @property {?Phaser.Point} [scrollTo=(auto)] - If specified the window will be scrolled to this position on every refresh.
*
* @property {boolean} [forceMinimumDocumentHeight=false] - If enabled the document elements minimum height is explicitly set on updates.
* The height set varies by device and may either be the height of the window or the viewport.
*
* @property {boolean} [canExpandParent=true] - If enabled then SHOW_ALL and USER_SCALE modes can try and expand the parent element. It may be necessary for the parent element to impose CSS width/height restrictions.
*
* @property {string} [clickTrampoline=(auto)] - On certain browsers (eg. IE) FullScreen events need to be triggered via 'click' events.
* A value of 'when-not-mouse' uses a click trampoline when a pointer that is not the primary mouse is used.
* Any other string value (including the empty string) prevents using click trampolines.
* For more details on click trampolines see {@link Phaser.Pointer#addClickTrampoline}.
*/
this.compatibility = {
supportsFullScreen: false,
orientationFallback: null,
noMargins: false,
scrollTo: null,
forceMinimumDocumentHeight: false,
canExpandParent: true,
clickTrampoline: ''
};
/**
* Scale mode to be used when not in fullscreen.
* @property {number} _scaleMode
* @private
*/
this._scaleMode = Phaser.ScaleManager.NO_SCALE;
/*
* Scale mode to be used in fullscreen.
* @property {number} _fullScreenScaleMode
* @private
*/
this._fullScreenScaleMode = Phaser.ScaleManager.NO_SCALE;
/**
* If the parent container of the Game canvas is the browser window itself (i.e. document.body),
* rather than another div, this should set to `true`.
*
* The {@link #parentNode} property is generally ignored while this is in effect.
*
* @property {boolean} parentIsWindow
*/
this.parentIsWindow = false;
/**
* The _original_ DOM element for the parent of the Display canvas.
* This may be different in fullscreen - see {@link #createFullScreenTarget}.
*
* This should only be changed after moving the Game canvas to a different DOM parent.
*
* @property {?DOMElement} parentNode
*/
this.parentNode = null;
/**
* The scale of the game in relation to its parent container.
* @property {Phaser.Point} parentScaleFactor
* @readonly
*/
this.parentScaleFactor = new Phaser.Point(1, 1);
/**
* The maximum time (in ms) between dimension update checks for the Canvas's parent element (or window).
* Update checks normally happen quicker in response to other events.
*
* @property {integer} trackParentInterval
* @default
* @protected
* @see {@link Phaser.ScaleManager#refresh refresh}
*/
this.trackParentInterval = 2000;
/**
* This signal is dispatched when the size of the Display canvas changes _or_ the size of the Game changes.
* When invoked this is done _after_ the Canvas size/position have been updated.
*
* This signal is _only_ called when a change occurs and a reflow may be required.
* For example, if the canvas does not change sizes because of CSS settings (such as min-width)
* then this signal will _not_ be triggered.
*
* Use this to handle responsive game layout options.
*
* This is signaled from `preUpdate` (or `pauseUpdate`) _even when_ the game is paused.
*
* @property {Phaser.Signal} onSizeChange
* @todo Formalize the arguments, if any, supplied to this signal.
*/
this.onSizeChange = new Phaser.Signal();
/**
* The callback that will be called each the parent container resizes.
* @property {function} onResize
* @private
*/
this.onResize = null;
/**
* The context in which the {@link #onResize} callback will be called.
* @property {object} onResizeContext
* @private
*/
this.onResizeContext = null;
/**
* @property {integer} _pendingScaleMode - Used to retain the scale mode if set from config before Boot.
* @private
*/
this._pendingScaleMode = null;
/**
* Information saved when fullscreen mode is started.
* @property {?object} _fullScreenRestore
* @private
*/
this._fullScreenRestore = null;
/**
* The _actual_ game dimensions, as initially set or set by {@link #setGameSize}.
* @property {Phaser.Rectangle} _gameSize
* @private
*/
this._gameSize = new Phaser.Rectangle();
/**
* The user-supplied scale factor, used with the USER_SCALE scaling mode.
* @property {Phaser.Point} _userScaleFactor
* @private
*/
this._userScaleFactor = new Phaser.Point(1, 1);
/**
* The user-supplied scale trim, used with the USER_SCALE scaling mode.
* @property {Phaser.Point} _userScaleTrim
* @private
*/
this._userScaleTrim = new Phaser.Point(0, 0);
/**
* The last time the bounds were checked in `preUpdate`.
* @property {number} _lastUpdate
* @private
*/
this._lastUpdate = 0;
/**
* Size checks updates are delayed according to the throttle.
* The throttle increases to `trackParentInterval` over time and is used to more
* rapidly detect changes in certain browsers (eg. IE) while providing back-off safety.
* @property {integer} _updateThrottle
* @private
*/
this._updateThrottle = 0;
/**
* The minimum throttle allowed until it has slowed down sufficiently.
* @property {integer} _updateThrottleReset
* @private
*/
this._updateThrottleReset = 100;
/**
* The cached result of the parent (possibly window) bounds; used to invalidate sizing.
* @property {Phaser.Rectangle} _parentBounds
* @private
*/
this._parentBounds = new Phaser.Rectangle();
/**
* Temporary bounds used for internal work to cut down on new objects created.
* @property {Phaser.Rectangle} _parentBounds
* @private
*/
this._tempBounds = new Phaser.Rectangle();
/**
* The Canvas size at which the last onSizeChange signal was triggered.
* @property {Phaser.Rectangle} _lastReportedCanvasSize
* @private
*/
this._lastReportedCanvasSize = new Phaser.Rectangle();
/**
* The Game size at which the last onSizeChange signal was triggered.
* @property {Phaser.Rectangle} _lastReportedGameSize
* @private
*/
this._lastReportedGameSize = new Phaser.Rectangle();
/**
* @property {boolean} _booted - ScaleManager booted state.
* @private
*/
this._booted = false;
if (game.config)
{
this.parseConfig(game.config);
}
this.setupScale(width, height);
};
/**
* A scale mode that stretches content to fill all available space - see {@link Phaser.ScaleManager#scaleMode scaleMode}.
*
* @constant
* @type {integer}
*/
Phaser.ScaleManager.EXACT_FIT = 0;
/**
* A scale mode that prevents any scaling - see {@link Phaser.ScaleManager#scaleMode scaleMode}.
*
* @constant
* @type {integer}
*/
Phaser.ScaleManager.NO_SCALE = 1;
/**
* A scale mode that shows the entire game while maintaining proportions - see {@link Phaser.ScaleManager#scaleMode scaleMode}.
*
* @constant
* @type {integer}
*/
Phaser.ScaleManager.SHOW_ALL = 2;
/**
* A scale mode that causes the Game size to change - see {@link Phaser.ScaleManager#scaleMode scaleMode}.
*
* @constant
* @type {integer}
*/
Phaser.ScaleManager.RESIZE = 3;
/**
* A scale mode that allows a custom scale factor - see {@link Phaser.ScaleManager#scaleMode scaleMode}.
*
* @constant
* @type {integer}
*/
Phaser.ScaleManager.USER_SCALE = 4;
Phaser.ScaleManager.prototype = {
/**
* Start the ScaleManager.
*
* @method Phaser.ScaleManager#boot
* @protected
*/
boot: function () {
// Configure device-dependent compatibility
var compat = this.compatibility;
compat.supportsFullScreen = this.game.device.fullscreen && !this.game.device.cocoonJS;
// We can't do anything about the status bars in iPads, web apps or desktops
if (!this.game.device.iPad && !this.game.device.webApp && !this.game.device.desktop)
{
if (this.game.device.android && !this.game.device.chrome)
{
compat.scrollTo = new Phaser.Point(0, 1);
}
else
{
compat.scrollTo = new Phaser.Point(0, 0);
}
}
if (this.game.device.desktop)
{
compat.orientationFallback = 'screen';
compat.clickTrampoline = 'when-not-mouse';
}
else
{
compat.orientationFallback = '';
compat.clickTrampoline = '';
}
// Configure event listeners
var _this = this;
this._orientationChange = function(event) {
return _this.orientationChange(event);
};
this._windowResize = function(event) {
return _this.windowResize(event);
};
// This does not appear to be on the standards track
window.addEventListener('orientationchange', this._orientationChange, false);
window.addEventListener('resize', this._windowResize, false);
if (this.compatibility.supportsFullScreen)
{
this._fullScreenChange = function(event) {
return _this.fullScreenChange(event);
};
this._fullScreenError = function(event) {
return _this.fullScreenError(event);
};
document.addEventListener('webkitfullscreenchange', this._fullScreenChange, false);
document.addEventListener('mozfullscreenchange', this._fullScreenChange, false);
document.addEventListener('MSFullscreenChange', this._fullScreenChange, false);
document.addEventListener('fullscreenchange', this._fullScreenChange, false);
document.addEventListener('webkitfullscreenerror', this._fullScreenError, false);
document.addEventListener('mozfullscreenerror', this._fullScreenError, false);
document.addEventListener('MSFullscreenError', this._fullScreenError, false);
document.addEventListener('fullscreenerror', this._fullScreenError, false);
}
this.game.onResume.add(this._gameResumed, this);
// Initialize core bounds
this.dom.getOffset(this.game.canvas, this.offset);
this.bounds.setTo(this.offset.x, this.offset.y, this.width, this.height);
this.setGameSize(this.game.width, this.game.height);
// Don't use updateOrientationState so events are not fired
this.screenOrientation = this.dom.getScreenOrientation(this.compatibility.orientationFallback);
if (Phaser.FlexGrid)
{
this.grid = new Phaser.FlexGrid(this, this.width, this.height);
}
this._booted = true;
if (this._pendingScaleMode !== null)
{
this.scaleMode = this._pendingScaleMode;
this._pendingScaleMode = null;
}
},
/**
* Load configuration settings.
*
* @method Phaser.ScaleManager#parseConfig
* @protected
* @param {object} config - The game configuration object.
*/
parseConfig: function (config) {
if (config['scaleMode'] !== undefined)
{
if (this._booted)
{
this.scaleMode = config['scaleMode'];
}
else
{
this._pendingScaleMode = config['scaleMode'];
}
}
if (config['fullScreenScaleMode'] !== undefined)
{
this.fullScreenScaleMode = config['fullScreenScaleMode'];
}
if (config['fullScreenTarget'])
{
this.fullScreenTarget = config['fullScreenTarget'];
}
},
/**
* Calculates and sets the game dimensions based on the given width and height.
*
* This should _not_ be called when in fullscreen mode.
*
* @method Phaser.ScaleManager#setupScale
* @protected
* @param {number|string} width - The width of the game.
* @param {number|string} height - The height of the game.
*/
setupScale: function (width, height) {
var target;
var rect = new Phaser.Rectangle();
if (this.game.parent !== '')
{
if (typeof this.game.parent === 'string')
{
// hopefully an element ID
target = document.getElementById(this.game.parent);
}
else if (this.game.parent && this.game.parent.nodeType === 1)
{
// quick test for a HTMLelement
target = this.game.parent;
}
}
// Fallback, covers an invalid ID and a non HTMLelement object
if (!target)
{
// Use the full window
this.parentNode = null;
this.parentIsWindow = true;
rect.width = this.dom.visualBounds.width;
rect.height = this.dom.visualBounds.height;
this.offset.set(0, 0);
}
else
{
this.parentNode = target;
this.parentIsWindow = false;
this.getParentBounds(this._parentBounds);
rect.width = this._parentBounds.width;
rect.height = this._parentBounds.height;
this.offset.set(this._parentBounds.x, this._parentBounds.y);
}
var newWidth = 0;
var newHeight = 0;
if (typeof width === 'number')
{
newWidth = width;
}
else
{
// Percentage based
this.parentScaleFactor.x = parseInt(width, 10) / 100;
newWidth = rect.width * this.parentScaleFactor.x;
}
if (typeof height === 'number')
{
newHeight = height;
}
else
{
// Percentage based
this.parentScaleFactor.y = parseInt(height, 10) / 100;
newHeight = rect.height * this.parentScaleFactor.y;
}
newWidth = Math.floor(newWidth);
newHeight = Math.floor(newHeight);
this._gameSize.setTo(0, 0, newWidth, newHeight);
this.updateDimensions(newWidth, newHeight, false);
},
/**
* Invoked when the game is resumed.
*
* @method Phaser.ScaleManager#_gameResumed
* @private
*/
_gameResumed: function () {
this.queueUpdate(true);
},
/**
* Set the actual Game size.
* Use this instead of directly changing `game.width` or `game.height`.
*
* The actual physical display (Canvas element size) depends on various settings including
* - Scale mode
* - Scaling factor
* - Size of Canvas's parent element or CSS rules such as min-height/max-height;
* - The size of the Window
*
* @method Phaser.ScaleManager#setGameSize
* @public
* @param {integer} width - _Game width_, in pixels.
* @param {integer} height - _Game height_, in pixels.
*/
setGameSize: function (width, height) {
this._gameSize.setTo(0, 0, width, height);
if (this.currentScaleMode !== Phaser.ScaleManager.RESIZE)
{
this.updateDimensions(width, height, true);
}
this.queueUpdate(true);
},
/**
* Set a User scaling factor used in the USER_SCALE scaling mode.
*
* The target canvas size is computed by:
*
* canvas.width = (game.width * hScale) - hTrim
* canvas.height = (game.height * vScale) - vTrim
*
* This method can be used in the {@link Phaser.ScaleManager#setResizeCallback resize callback}.
*
* @method Phaser.ScaleManager#setUserScale
* @param {number} hScale - Horizontal scaling factor.
* @param {numer} vScale - Vertical scaling factor.
* @param {integer} [hTrim=0] - Horizontal trim, applied after scaling.
* @param {integer} [vTrim=0] - Vertical trim, applied after scaling.
*/
setUserScale: function (hScale, vScale, hTrim, vTrim) {
this._userScaleFactor.setTo(hScale, vScale);
this._userScaleTrim.setTo(hTrim | 0, vTrim | 0);
this.queueUpdate(true);
},
/**
* Sets the callback that will be invoked before sizing calculations.
*
* This is the appropriate place to call {@link #setUserScale} if needing custom dynamic scaling.
*
* The callback is supplied with two arguments `scale` and `parentBounds` where `scale` is the ScaleManager
* and `parentBounds`, a Phaser.Rectangle, is the size of the Parent element.
*
* This callback
* - May be invoked even though the parent container or canvas sizes have not changed
* - Unlike {@link #onSizeChange}, it runs _before_ the canvas is guaranteed to be updated
* - Will be invoked from `preUpdate`, _even when_ the game is paused
*
* See {@link #onSizeChange} for a better way of reacting to layout updates.
*
* @method Phaser.ScaleManager#setResizeCallback
* @public
* @param {function} callback - The callback that will be called each time a window.resize event happens or if set, the parent container resizes.
* @param {object} context - The context in which the callback will be called.
*/
setResizeCallback: function (callback, context) {
this.onResize = callback;
this.onResizeContext = context;
},
/**
* Signals a resize - IF the canvas or Game size differs from the last signal.
*
* This also triggers updates on {@link #grid} (FlexGrid) and, if in a RESIZE mode, `game.state` (StateManager).
*
* @method Phaser.ScaleManager#signalSizeChange
* @private
*/
signalSizeChange: function () {
if (!Phaser.Rectangle.sameDimensions(this, this._lastReportedCanvasSize) ||
!Phaser.Rectangle.sameDimensions(this.game, this._lastReportedGameSize))
{
var width = this.width;
var height = this.height;
this._lastReportedCanvasSize.setTo(0, 0, width, height);
this._lastReportedGameSize.setTo(0, 0, this.game.width, this.game.height);
if (this.grid)
{
this.grid.onResize(width, height);
}
this.onSizeChange.dispatch(this, width, height);
// Per StateManager#onResizeCallback, it only occurs when in RESIZE mode.
if (this.currentScaleMode === Phaser.ScaleManager.RESIZE)
{
this.game.state.resize(width, height);
this.game.load.resize(width, height);
}
}
},
/**
* Set the min and max dimensions for the Display canvas.
*
* _Note:_ The min/max dimensions are only applied in some cases
* - When the device is not in an incorrect orientation; or
* - The scale mode is EXACT_FIT when not in fullscreen
*
* @method Phaser.ScaleManager#setMinMax
* @public
* @param {number} minWidth - The minimum width the game is allowed to scale down to.
* @param {number} minHeight - The minimum height the game is allowed to scale down to.
* @param {number} [maxWidth] - The maximum width the game is allowed to scale up to; only changed if specified.
* @param {number} [maxHeight] - The maximum height the game is allowed to scale up to; only changed if specified.
* @todo These values are only sometimes honored.
*/
setMinMax: function (minWidth, minHeight, maxWidth, maxHeight) {
this.minWidth = minWidth;
this.minHeight = minHeight;
if (typeof maxWidth !== 'undefined')
{
this.maxWidth = maxWidth;
}
if (typeof maxHeight !== 'undefined')
{
this.maxHeight = maxHeight;
}
},
/**
* The ScaleManager.preUpdate is called automatically by the core Game loop.
*
* @method Phaser.ScaleManager#preUpdate
* @protected
*/
preUpdate: function () {
if (this.game.time.time < (this._lastUpdate + this._updateThrottle))
{
return;
}
var prevThrottle = this._updateThrottle;
this._updateThrottleReset = prevThrottle >= 400 ? 0 : 100;
this.dom.getOffset(this.game.canvas, this.offset);
var prevWidth = this._parentBounds.width;
var prevHeight = this._parentBounds.height;
var bounds = this.getParentBounds(this._parentBounds);
var boundsChanged = bounds.width !== prevWidth || bounds.height !== prevHeight;
// Always invalidate on a newly detected orientation change
var orientationChanged = this.updateOrientationState();
if (boundsChanged || orientationChanged)
{
if (this.onResize)
{
this.onResize.call(this.onResizeContext, this, bounds);
}
this.updateLayout();
this.signalSizeChange();
}
// Next throttle, eg. 25, 50, 100, 200..
var throttle = this._updateThrottle * 2;
// Don't let an update be too eager about resetting the throttle.
if (this._updateThrottle < prevThrottle)
{
throttle = Math.min(prevThrottle, this._updateThrottleReset);
}
this._updateThrottle = Phaser.Math.clamp(throttle, 25, this.trackParentInterval);
this._lastUpdate = this.game.time.time;
},
/**
* Update method while paused.
*
* @method Phaser.ScaleManager#pauseUpdate
* @private
*/
pauseUpdate: function () {
this.preUpdate();
// Updates at slowest.
this._updateThrottle = this.trackParentInterval;
},
/**
* Update the dimensions taking the parent scaling factor into account.
*
* @method Phaser.ScaleManager#updateDimensions
* @private
* @param {number} width - The new width of the parent container.
* @param {number} height - The new height of the parent container.
* @param {boolean} resize - True if the renderer should be resized, otherwise false to just update the internal vars.
*/
updateDimensions: function (width, height, resize) {
this.width = width * this.parentScaleFactor.x;
this.height = height * this.parentScaleFactor.y;
this.game.width = this.width;
this.game.height = this.height;
this.sourceAspectRatio = this.width / this.height;
this.updateScalingAndBounds();
if (resize)
{
// Resize the renderer (which in turn resizes the Display canvas!)
this.game.renderer.resize(this.width, this.height);
// The Camera can never be smaller than the Game size
this.game.camera.setSize(this.width, this.height);
// This should only happen if the world is smaller than the new canvas size
this.game.world.resize(this.width, this.height);
}
},
/**
* Update relevant scaling values based on the ScaleManager dimension and game dimensions,
* which should already be set. This does not change {@link #sourceAspectRatio}.
*
* @method Phaser.ScaleManager#updateScalingAndBounds
* @private
*/
updateScalingAndBounds: function () {
this.scaleFactor.x = this.game.width / this.width;
this.scaleFactor.y = this.game.height / this.height;
this.scaleFactorInversed.x = this.width / this.game.width;
this.scaleFactorInversed.y = this.height / this.game.height;
this.aspectRatio = this.width / this.height;
// This can be invoked in boot pre-canvas
if (this.game.canvas)
{
this.dom.getOffset(this.game.canvas, this.offset);
}
this.bounds.setTo(this.offset.x, this.offset.y, this.width, this.height);
// Can be invoked in boot pre-input
if (this.game.input && this.game.input.scale)
{
this.game.input.scale.setTo(this.scaleFactor.x, this.scaleFactor.y);
}
},
/**
* Force the game to run in only one orientation.
*
* This enables generation of incorrect orientation signals and affects resizing but does not otherwise rotate or lock the orientation.
*
* Orientation checks are performed via the Screen Orientation API, if available in browser. This means it will check your monitor
* orientation on desktop, or your device orientation on mobile, rather than comparing actual game dimensions. If you need to check the
* viewport dimensions instead and bypass the Screen Orientation API then set: `ScaleManager.compatibility.orientationFallback = 'viewport'`
*
* @method Phaser.ScaleManager#forceOrientation
* @public
* @param {boolean} forceLandscape - true if the game should run in landscape mode only.
* @param {boolean} [forcePortrait=false] - true if the game should run in portrait mode only.
*/
forceOrientation: function (forceLandscape, forcePortrait) {
if (forcePortrait === undefined) { forcePortrait = false; }
this.forceLandscape = forceLandscape;
this.forcePortrait = forcePortrait;
this.queueUpdate(true);
},
/**
* Classify the orientation, per `getScreenOrientation`.
*
* @method Phaser.ScaleManager#classifyOrientation
* @private
* @param {string} orientation - The orientation string, e.g. 'portrait-primary'.
* @return {?string} The classified orientation: 'portrait', 'landscape`, or null.
*/
classifyOrientation: function (orientation) {
if (orientation === 'portrait-primary' || orientation === 'portrait-secondary')
{
return 'portrait';
}
else if (orientation === 'landscape-primary' || orientation === 'landscape-secondary')
{
return 'landscape';
}
else
{
return null;
}
},
/**
* Updates the current orientation and dispatches orientation change events.
*
* @method Phaser.ScaleManager#updateOrientationState
* @private
* @return {boolean} True if the orientation state changed which means a forced update is likely required.
*/
updateOrientationState: function () {
var previousOrientation = this.screenOrientation;
var previouslyIncorrect = this.incorrectOrientation;
this.screenOrientation = this.dom.getScreenOrientation(this.compatibility.orientationFallback);
this.incorrectOrientation = (this.forceLandscape && !this.isLandscape) ||
(this.forcePortrait && !this.isPortrait);
var changed = previousOrientation !== this.screenOrientation;
var correctnessChanged = previouslyIncorrect !== this.incorrectOrientation;
if (correctnessChanged)
{
if (this.incorrectOrientation)
{
this.enterIncorrectOrientation.dispatch();
}
else
{
this.leaveIncorrectOrientation.dispatch();
}
}
if (changed || correctnessChanged)
{
this.onOrientationChange.dispatch(this, previousOrientation, previouslyIncorrect);
}
return changed || correctnessChanged;
},
/**
* window.orientationchange event handler.
*
* @method Phaser.ScaleManager#orientationChange
* @private
* @param {Event} event - The orientationchange event data.
*/
orientationChange: function (event) {
this.event = event;
this.queueUpdate(true);
},
/**
* window.resize event handler.
*
* @method Phaser.ScaleManager#windowResize
* @private
* @param {Event} event - The resize event data.
*/
windowResize: function (event) {
this.event = event;
this.queueUpdate(true);
},
/**
* Scroll to the top - in some environments. See `compatibility.scrollTo`.
*
* @method Phaser.ScaleManager#scrollTop
* @private
*/
scrollTop: function () {
var scrollTo = this.compatibility.scrollTo;
if (scrollTo)
{
window.scrollTo(scrollTo.x, scrollTo.y);
}
},
/**
* The "refresh" methods informs the ScaleManager that a layout refresh is required.
*
* The ScaleManager automatically queues a layout refresh (eg. updates the Game size or Display canvas layout)
* when the browser is resized, the orientation changes, or when there is a detected change
* of the Parent size. Refreshing is also done automatically when public properties,
* such as {@link #scaleMode}, are updated or state-changing methods are invoked.
*
* The "refresh" method _may_ need to be used in a few (rare) situtations when
*
* - a device change event is not correctly detected; or
* - the Parent size changes (and an immediate reflow is desired); or
* - the ScaleManager state is updated by non-standard means; or
* - certain {@link #compatibility} properties are manually changed.
*
* The queued layout refresh is not immediate but will run promptly in an upcoming `preRender`.
*
* @method Phaser.ScaleManager#refresh
* @public
*/
refresh: function () {
this.scrollTop();
this.queueUpdate(true);
},
/**
* Updates the game / canvas position and size.
*
* @method Phaser.ScaleManager#updateLayout
* @private
*/
updateLayout: function () {
var scaleMode = this.currentScaleMode;
if (scaleMode === Phaser.ScaleManager.RESIZE)
{
this.reflowGame();
return;
}
this.scrollTop();
if (this.compatibility.forceMinimumDocumentHeight)
{
// (This came from older code, by why is it here?)
// Set minimum height of content to new window height
document.documentElement.style.minHeight = window.innerHeight + 'px';
}
if (this.incorrectOrientation)
{
this.setMaximum();
}
else
{
if (scaleMode === Phaser.ScaleManager.EXACT_FIT)
{
this.setExactFit();
}
else if (scaleMode === Phaser.ScaleManager.SHOW_ALL)
{
if (!this.isFullScreen && this.boundingParent &&
this.compatibility.canExpandParent)
{
// Try to expand parent out, but choosing maximizing dimensions.
// Then select minimize dimensions which should then honor parent
// maximum bound applications.
this.setShowAll(true);
this.resetCanvas();
this.setShowAll();
}
else
{
this.setShowAll();
}
}
else if (scaleMode === Phaser.ScaleManager.NO_SCALE)
{
this.width = this.game.width;
this.height = this.game.height;
}
else if (scaleMode === Phaser.ScaleManager.USER_SCALE)
{
this.width = (this.game.width * this._userScaleFactor.x) - this._userScaleTrim.x;
this.height = (this.game.height * this._userScaleFactor.y) - this._userScaleTrim.y;
}
}
if (!this.compatibility.canExpandParent &&
(scaleMode === Phaser.ScaleManager.SHOW_ALL || scaleMode === Phaser.ScaleManager.USER_SCALE))
{
var bounds = this.getParentBounds(this._tempBounds);
this.width = Math.min(this.width, bounds.width);
this.height = Math.min(this.height, bounds.height);
}
// Always truncate / force to integer
this.width = this.width | 0;
this.height = this.height | 0;
this.reflowCanvas();
},
/**
* Returns the computed Parent size/bounds that the Display canvas is allowed/expected to fill.
*
* If in fullscreen mode or without parent (see {@link #parentIsWindow}),
* this will be the bounds of the visual viewport itself.
*
* This function takes the {@link #windowConstraints} into consideration - if the parent is partially outside
* the viewport then this function may return a smaller than expected size.
*
* Values are rounded to the nearest pixel.
*
* @method Phaser.ScaleManager#getParentBounds
* @protected
* @param {Phaser.Rectangle} [target=(new Rectangle)] - The rectangle to update; a new one is created as needed.
* @return {Phaser.Rectangle} The established parent bounds.
*/
getParentBounds: function (target) {
var bounds = target || new Phaser.Rectangle();
var parentNode = this.boundingParent;
var visualBounds = this.dom.visualBounds;
var layoutBounds = this.dom.layoutBounds;
if (!parentNode)
{
bounds.setTo(0, 0, visualBounds.width, visualBounds.height);
}
else
{
// Ref. http://msdn.microsoft.com/en-us/library/hh781509(v=vs.85).aspx for getBoundingClientRect
var clientRect = parentNode.getBoundingClientRect();
var parentRect = (parentNode.offsetParent) ? parentNode.offsetParent.getBoundingClientRect() : parentNode.getBoundingClientRect();
bounds.setTo(clientRect.left - parentRect.left, clientRect.top - parentRect.top, clientRect.width, clientRect.height);
var wc = this.windowConstraints;
if (wc.right)
{
var windowBounds = wc.right === 'layout' ? layoutBounds : visualBounds;
bounds.right = Math.min(bounds.right, windowBounds.width);
}
if (wc.bottom)
{
var windowBounds = wc.bottom === 'layout' ? layoutBounds : visualBounds;
bounds.bottom = Math.min(bounds.bottom, windowBounds.height);
}
}
bounds.setTo(
Math.round(bounds.x), Math.round(bounds.y),
Math.round(bounds.width), Math.round(bounds.height));
return bounds;
},
/**
* Update the canvas position/margins - for alignment within the parent container.
*
* The canvas margins _must_ be reset/cleared prior to invoking this.
*
* @method Phaser.ScaleManager#alignCanvas
* @private
* @param {boolean} horizontal - Align horizontally?
* @param {boolean} vertical - Align vertically?
*/
alignCanvas: function (horizontal, vertical) {
var parentBounds = this.getParentBounds(this._tempBounds);
var canvas = this.game.canvas;
var margin = this.margin;
if (horizontal)
{
margin.left = margin.right = 0;
var canvasBounds = canvas.getBoundingClientRect();
if (this.width < parentBounds.width && !this.incorrectOrientation)
{
var currentEdge = canvasBounds.left - parentBounds.x;
var targetEdge = (parentBounds.width / 2) - (this.width / 2);
targetEdge = Math.max(targetEdge, 0);
var offset = targetEdge - currentEdge;
margin.left = Math.round(offset);
}
canvas.style.marginLeft = margin.left + 'px';
if (margin.left !== 0)
{
margin.right = -(parentBounds.width - canvasBounds.width - margin.left);
canvas.style.marginRight = margin.right + 'px';
}
}
if (vertical)
{
margin.top = margin.bottom = 0;
var canvasBounds = canvas.getBoundingClientRect();
if (this.height < parentBounds.height && !this.incorrectOrientation)
{
var currentEdge = canvasBounds.top - parentBounds.y;
var targetEdge = (parentBounds.height / 2) - (this.height / 2);
targetEdge = Math.max(targetEdge, 0);
var offset = targetEdge - currentEdge;
margin.top = Math.round(offset);
}
canvas.style.marginTop = margin.top + 'px';
if (margin.top !== 0)
{
margin.bottom = -(parentBounds.height - canvasBounds.height - margin.top);
canvas.style.marginBottom = margin.bottom + 'px';
}
}
// Silly backwards compatibility..
margin.x = margin.left;
margin.y = margin.top;
},
/**
* Updates the Game state / size.
*
* The canvas margins may always be adjusted, even if alignment is not in effect.
*
* @method Phaser.ScaleManager#reflowGame
* @private
*/
reflowGame: function () {
this.resetCanvas('', '');
var bounds = this.getParentBounds(this._tempBounds);
this.updateDimensions(bounds.width, bounds.height, true);
},
/**
* Updates the Display canvas size.
*
* The canvas margins may always be adjusted, even alignment is not in effect.
*
* @method Phaser.ScaleManager#reflowCanvas
* @private
*/
reflowCanvas: function () {
if (!this.incorrectOrientation)
{
this.width = Phaser.Math.clamp(this.width, this.minWidth || 0, this.maxWidth || this.width);
this.height = Phaser.Math.clamp(this.height, this.minHeight || 0, this.maxHeight || this.height);
}
this.resetCanvas();
if (!this.compatibility.noMargins)
{
if (this.isFullScreen && this._createdFullScreenTarget)
{
this.alignCanvas(true, true);
}
else
{
this.alignCanvas(this.pageAlignHorizontally, this.pageAlignVertically);
}
}
this.updateScalingAndBounds();
},
/**
* "Reset" the Display canvas and set the specified width/height.
*
* @method Phaser.ScaleManager#resetCanvas
* @private
* @param {string} [cssWidth=(current width)] - The css width to set.
* @param {string} [cssHeight=(current height)] - The css height to set.
*/
resetCanvas: function (cssWidth, cssHeight) {
if (cssWidth === undefined) { cssWidth = this.width + 'px'; }
if (cssHeight === undefined) { cssHeight = this.height + 'px'; }
var canvas = this.game.canvas;
if (!this.compatibility.noMargins)
{
canvas.style.marginLeft = '';
canvas.style.marginTop = '';
canvas.style.marginRight = '';
canvas.style.marginBottom = '';
}
canvas.style.width = cssWidth;
canvas.style.height = cssHeight;
},
/**
* Queues/marks a size/bounds check as needing to occur (from `preUpdate`).
*
* @method Phaser.ScaleManager#queueUpdate
* @private
* @param {boolean} force - If true resets the parent bounds to ensure the check is dirty.
*/
queueUpdate: function (force) {
if (force)
{
this._parentBounds.width = 0;
this._parentBounds.height = 0;
}
this._updateThrottle = this._updateThrottleReset;
},
/**
* Reset internal data/state.
*
* @method Phaser.ScaleManager#reset
* @private
*/
reset: function (clearWorld) {
if (clearWorld && this.grid)
{
this.grid.reset();
}
},
/**
* Updates the width/height to that of the window.
*
* @method Phaser.ScaleManager#setMaximum
* @private
*/
setMaximum: function () {
this.width = this.dom.visualBounds.width;
this.height = this.dom.visualBounds.height;
},
/**
* Updates the width/height such that the game is scaled proportionally.
*
* @method Phaser.ScaleManager#setShowAll
* @private
* @param {boolean} expanding - If true then the maximizing dimension is chosen.
*/
setShowAll: function (expanding) {
var bounds = this.getParentBounds(this._tempBounds);
var width = bounds.width;
var height = bounds.height;
var multiplier;
if (expanding)
{
multiplier = Math.max((height / this.game.height), (width / this.game.width));
}
else
{
multiplier = Math.min((height / this.game.height), (width / this.game.width));
}
this.width = Math.round(this.game.width * multiplier);
this.height = Math.round(this.game.height * multiplier);
},
/**
* Updates the width/height such that the game is stretched to the available size.
* Honors {@link #maxWidth} and {@link #maxHeight} when _not_ in fullscreen.
*
* @method Phaser.ScaleManager#setExactFit
* @private
*/
setExactFit: function () {
var bounds = this.getParentBounds(this._tempBounds);
this.width = bounds.width;
this.height = bounds.height;
if (this.isFullScreen)
{
// Max/min not honored fullscreen
return;
}
if (this.maxWidth)
{
this.width = Math.min(this.width, this.maxWidth);
}
if (this.maxHeight)
{
this.height = Math.min(this.height, this.maxHeight);
}
},
/**
* Creates a fullscreen target. This is called automatically as as needed when entering
* fullscreen mode and the resulting element is supplied to {@link #onFullScreenInit}.
*
* Use {@link #onFullScreenInit} to customize the created object.
*
* @method Phaser.ScaleManager#createFullScreenTarget
* @protected
*/
createFullScreenTarget: function () {
var fsTarget = document.createElement('div');
fsTarget.style.margin = '0';
fsTarget.style.padding = '0';
fsTarget.style.background = '#000';
return fsTarget;
},
/**
* Start the browsers fullscreen mode - this _must_ be called from a user input Pointer or Mouse event.
*
* The Fullscreen API must be supported by the browser for this to work - it is not the same as setting
* the game size to fill the browser window. See {@link Phaser.ScaleManager#compatibility compatibility.supportsFullScreen} to check if the current
* device is reported to support fullscreen mode.
*
* The {@link #fullScreenFailed} signal will be dispatched if the fullscreen change request failed or the game does not support the Fullscreen API.
*
* @method Phaser.ScaleManager#startFullScreen
* @public
* @param {boolean} [antialias] - Changes the anti-alias feature of the canvas before jumping in to fullscreen (false = retain pixel art, true = smooth art). If not specified then no change is made. Only works in CANVAS mode.
* @param {boolean} [allowTrampoline=undefined] - Internal argument. If `false` click trampolining is suppressed.
* @return {boolean} Returns true if the device supports fullscreen mode and fullscreen mode was attempted to be started. (It might not actually start, wait for the signals.)
*/
startFullScreen: function (antialias, allowTrampoline) {
if (this.isFullScreen)
{
return false;
}
if (!this.compatibility.supportsFullScreen)
{
// Error is called in timeout to emulate the real fullscreenerror event better
var _this = this;
setTimeout(function () {
_this.fullScreenError();
}, 10);
return;
}
if (this.compatibility.clickTrampoline === 'when-not-mouse')
{
var input = this.game.input;
if (input.activePointer &&
input.activePointer !== input.mousePointer &&
(allowTrampoline || allowTrampoline !== false))
{
input.activePointer.addClickTrampoline("startFullScreen", this.startFullScreen, this, [antialias, false]);
return;
}
}
if (antialias !== undefined && this.game.renderType === Phaser.CANVAS)
{
this.game.stage.smoothed = antialias;
}
var fsTarget = this.fullScreenTarget;
if (!fsTarget)
{
this.cleanupCreatedTarget();
this._createdFullScreenTarget = this.createFullScreenTarget();
fsTarget = this._createdFullScreenTarget;
}
var initData = {
targetElement: fsTarget
};
this.hasPhaserSetFullScreen = true;
this.onFullScreenInit.dispatch(this, initData);
if (this._createdFullScreenTarget)
{
// Move the Display canvas inside of the target and add the target to the DOM
// (The target has to be added for the Fullscreen API to work.)
var canvas = this.game.canvas;
var parent = canvas.parentNode;
parent.insertBefore(fsTarget, canvas);
fsTarget.appendChild(canvas);
}
if (this.game.device.fullscreenKeyboard)
{
fsTarget[this.game.device.requestFullscreen](Element.ALLOW_KEYBOARD_INPUT);
}
else
{
fsTarget[this.game.device.requestFullscreen]();
}
return true;
},
/**
* Stops / exits fullscreen mode, if active.
*
* @method Phaser.ScaleManager#stopFullScreen
* @public
* @return {boolean} Returns true if the browser supports fullscreen mode and fullscreen mode will be exited.
*/
stopFullScreen: function () {
if (!this.isFullScreen || !this.compatibility.supportsFullScreen)
{
return false;
}
this.hasPhaserSetFullScreen = false;
document[this.game.device.cancelFullscreen]();
return true;
},
/**
* Cleans up the previous fullscreen target, if such was automatically created.
* This ensures the canvas is restored to its former parent, assuming the target didn't move.
*
* @method Phaser.ScaleManager#cleanupCreatedTarget
* @private
*/
cleanupCreatedTarget: function () {
var fsTarget = this._createdFullScreenTarget;
if (fsTarget && fsTarget.parentNode)
{
// Make sure to cleanup synthetic target for sure;
// swap the canvas back to the parent.
var parent = fsTarget.parentNode;
parent.insertBefore(this.game.canvas, fsTarget);
parent.removeChild(fsTarget);
}
this._createdFullScreenTarget = null;
},
/**
* Used to prepare/restore extra fullscreen mode settings.
* (This does move any elements within the DOM tree.)
*
* @method Phaser.ScaleManager#prepScreenMode
* @private
* @param {boolean} enteringFullscreen - True if _entering_ fullscreen, false if _leaving_.
*/
prepScreenMode: function (enteringFullscreen) {
var createdTarget = !!this._createdFullScreenTarget;
var fsTarget = this._createdFullScreenTarget || this.fullScreenTarget;
if (enteringFullscreen)
{
if (createdTarget || this.fullScreenScaleMode === Phaser.ScaleManager.EXACT_FIT)
{
// Resize target, as long as it's not the canvas
if (fsTarget !== this.game.canvas)
{
this._fullScreenRestore = {
targetWidth: fsTarget.style.width,
targetHeight: fsTarget.style.height
};
fsTarget.style.width = '100%';
fsTarget.style.height = '100%';
}
}
}
else
{
// Have restore information
if (this._fullScreenRestore)
{
fsTarget.style.width = this._fullScreenRestore.targetWidth;
fsTarget.style.height = this._fullScreenRestore.targetHeight;
this._fullScreenRestore = null;
}
// Always reset to game size
this.updateDimensions(this._gameSize.width, this._gameSize.height, true);
this.resetCanvas();
}
},
/**
* Called automatically when the browser enters of leaves fullscreen mode.
*
* @method Phaser.ScaleManager#fullScreenChange
* @private
* @param {Event} [event=undefined] - The fullscreenchange event
*/
fullScreenChange: function (event) {
this.event = event;
if (this.isFullScreen)
{
this.prepScreenMode(true);
this.updateLayout();
this.queueUpdate(true);
}
else
{
this.prepScreenMode(false);
this.cleanupCreatedTarget();
this.updateLayout();
this.queueUpdate(true);
}
this.onFullScreenChange.dispatch(this, this.width, this.height);
},
/**
* Called automatically when the browser fullscreen request fails;
* or called when a fullscreen request is made on a device for which it is not supported.
*
* @method Phaser.ScaleManager#fullScreenError
* @private
* @param {Event} [event=undefined] - The fullscreenerror event; undefined if invoked on a device that does not support the Fullscreen API.
*/
fullScreenError: function (event) {
this.event = event;
this.cleanupCreatedTarget();
console.warn('Phaser.ScaleManager: requestFullscreen failed or device does not support the Fullscreen API');
this.onFullScreenError.dispatch(this);
},
/**
* Takes a Sprite or Image object and scales it to fit the given dimensions.
* Scaling happens proportionally without distortion to the sprites texture.
* The letterBox parameter controls if scaling will produce a letter-box effect or zoom the
* sprite until it fills the given values. Note that with letterBox set to false the scaled sprite may spill out over either
* the horizontal or vertical sides of the target dimensions. If you wish to stop this you can crop the Sprite.
*
* @method Phaser.ScaleManager#scaleSprite
* @protected
* @param {Phaser.Sprite|Phaser.Image} sprite - The sprite we want to scale.
* @param {integer} [width] - The target width that we want to fit the sprite in to. If not given it defaults to ScaleManager.width.
* @param {integer} [height] - The target height that we want to fit the sprite in to. If not given it defaults to ScaleManager.height.
* @param {boolean} [letterBox=false] - True if we want the `fitted` mode. Otherwise, the function uses the `zoom` mode.
* @return {Phaser.Sprite|Phaser.Image} The scaled sprite.
*/
scaleSprite: function (sprite, width, height, letterBox) {
if (width === undefined) { width = this.width; }
if (height === undefined) { height = this.height; }
if (letterBox === undefined) { letterBox = false; }
if (!sprite || !sprite['scale'])
{
return sprite;
}
sprite.scale.x = 1;
sprite.scale.y = 1;
if ((sprite.width <= 0) || (sprite.height <= 0) || (width <= 0) || (height <= 0))
{
return sprite;
}
var scaleX1 = width;
var scaleY1 = (sprite.height * width) / sprite.width;
var scaleX2 = (sprite.width * height) / sprite.height;
var scaleY2 = height;
var scaleOnWidth = (scaleX2 > width);
if (scaleOnWidth)
{
scaleOnWidth = letterBox;
}
else
{
scaleOnWidth = !letterBox;
}
if (scaleOnWidth)
{
sprite.width = Math.floor(scaleX1);
sprite.height = Math.floor(scaleY1);
}
else
{
sprite.width = Math.floor(scaleX2);
sprite.height = Math.floor(scaleY2);
}
// Enable at some point?
// sprite.x = Math.floor((width - sprite.width) / 2);
// sprite.y = Math.floor((height - sprite.height) / 2);
return sprite;
},
/**
* Destroys the ScaleManager and removes any event listeners.
* This should probably only be called when the game is destroyed.
*
* @method Phaser.ScaleManager#destroy
* @protected
*/
destroy: function () {
this.game.onResume.remove(this._gameResumed, this);
window.removeEventListener('orientationchange', this._orientationChange, false);
window.removeEventListener('resize', this._windowResize, false);
if (this.compatibility.supportsFullScreen)
{
document.removeEventListener('webkitfullscreenchange', this._fullScreenChange, false);
document.removeEventListener('mozfullscreenchange', this._fullScreenChange, false);
document.removeEventListener('MSFullscreenChange', this._fullScreenChange, false);
document.removeEventListener('fullscreenchange', this._fullScreenChange, false);
document.removeEventListener('webkitfullscreenerror', this._fullScreenError, false);
document.removeEventListener('mozfullscreenerror', this._fullScreenError, false);
document.removeEventListener('MSFullscreenError', this._fullScreenError, false);
document.removeEventListener('fullscreenerror', this._fullScreenError, false);
}
}
};
Phaser.ScaleManager.prototype.constructor = Phaser.ScaleManager;
/**
* The DOM element that is considered the Parent bounding element, if any.
*
* This `null` if {@link #parentIsWindow} is true or if fullscreen mode is entered and {@link #fullScreenTarget} is specified.
* It will also be null if there is no game canvas or if the game canvas has no parent.
*
* @name Phaser.ScaleManager#boundingParent
* @property {?DOMElement} boundingParent
* @readonly
*/
Object.defineProperty(Phaser.ScaleManager.prototype, "boundingParent", {
get: function () {
if (this.parentIsWindow ||
(this.isFullScreen && this.hasPhaserSetFullScreen && !this._createdFullScreenTarget))
{
return null;
}
var parentNode = this.game.canvas && this.game.canvas.parentNode;
return parentNode || null;
}
});
/**
* The scaling method used by the ScaleManager when not in fullscreen.
*
* <dl>
* <dt>{@link Phaser.ScaleManager.NO_SCALE}</dt>
* <dd>
* 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.
* </dd>
* <dt>{@link Phaser.ScaleManager.EXACT_FIT}</dt>
* <dd>
* 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.
* </dd>
* <dt>{@link Phaser.ScaleManager.SHOW_ALL}</dt>
* <dd>
* Show the entire game display area while _maintaining_ the original aspect ratio.
* </dd>
* <dt>{@link Phaser.ScaleManager.RESIZE}</dt>
* <dd>
* 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.
* <p>
* Any manually set Game size (see {@link #setGameSize}) is ignored while in effect.
* </dd>
* <dt>{@link Phaser.ScaleManager.USER_SCALE}</dt>
* <dd>
* The game Display is scaled according to the user-specified scale set by {@link Phaser.ScaleManager#setUserScale setUserScale}.
* <p>
* This scale can be adjusted in the {@link Phaser.ScaleManager#setResizeCallback resize callback}
* for flexible custom-sizing needs.
* </dd>
* </dl>
*
* @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 Steven Rogers <soldoutactivist@gmail.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* This is a stub for the Phaser Debug Class.
* It allows you to exclude the default Debug from your build, without making Game crash.
*/
var debugNoop = function () {};
Phaser.Utils.Debug = debugNoop;
Phaser.Utils.Debug.prototype = {
isDisabled: true,
boot: debugNoop,
preUpdate: debugNoop,
reset: debugNoop,
start: debugNoop,
stop: debugNoop,
line: debugNoop,
soundInfo: debugNoop,
cameraInfo: debugNoop,
timer: debugNoop,
pointer: debugNoop,
spriteInputInfo: debugNoop,
key: debugNoop,
inputInfo: debugNoop,
spriteBounds: debugNoop,
ropeSegments: debugNoop,
spriteInfo: debugNoop,
spriteCoords: debugNoop,
lineInfo: debugNoop,
pixel: debugNoop,
geom: debugNoop,
rectangle: debugNoop,
text: debugNoop,
quadTree: debugNoop,
body: debugNoop,
bodyInfo: debugNoop,
box2dWorld: debugNoop,
box2dBody: debugNoop
};
Phaser.Utils.Debug.prototype.constructor = Phaser.Utils.Debug;
/**
* @author Richard Davey <rich@photonstorm.com>
* @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 = null == object ? this.visualBounds : 1 === object.nodeType ? 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 <rich@photonstorm.com>
* @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 <rich@photonstorm.com>
* @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
* @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
* @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;
}
},
/**
* A standard Fisher-Yates Array shuffle implementation which modifies the array in place.
*
* @method
* @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
* @param {Array<any[]>} array - The matrix to transpose.
* @return {Array<any[]>} 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
* @param {Array<any[]>} 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<any[]>} 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 an array.
* The result will always be in the range `[first_value, last_value]`.
*
* @method
* @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;
},
/**
* 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.rotate
* @deprecated Please use Phaser.ArrayUtils.rotate instead.
* @param {any[]} array - The array to rotate. The array is modified.
* @return {any} The rotated value.
*/
rotate: 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]`.
* This is equivalent to `numberArrayStep(start, end, 1)`.
*
* @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) {
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 <rich@photonstorm.com>
* @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 <rich@photonstorm.com>
* @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');`
*
* @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.
* @return {PIXI.Texture} The newly generated texture.
*/
texture: function (key, data, pixelWidth, pixelHeight, palette) {
if (pixelWidth === undefined) { pixelWidth = 8; }
if (pixelHeight === undefined) { pixelHeight = pixelWidth; }
if (palette === undefined) { palette = 0; }
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 this.bmd.generateTexture(key);
},
/**
* Creates a grid texture based on the given dimensions.
*
* @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)`.
* @return {PIXI.Texture} The newly generated texture.
*/
grid: function (key, width, height, cellWidth, cellHeight, color) {
// 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 this.bmd.generateTexture(key);
}
};
Phaser.Create.prototype.constructor = Phaser.Create;
/**
* @author Richard Davey <rich@photonstorm.com>
* @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 (typeof children !== 'undefined' && typeof children !== null)
{
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 (typeof children !== 'undefined' && typeof children !== null)
{
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 <rich@photonstorm.com>
* @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 <rich@photonstorm.com>
* @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 = {
/**
* 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.toString() + ',' + out.g.toString() + ',' + out.b.toString() + ',' + 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 = parseInt(result[1], 10);
out.g = parseInt(result[2], 10);
out.b = parseInt(result[3], 10);
out.a = result[4] !== undefined ? parseFloat(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.
* @returns {number} The interpolated color value.
*/
interpolateColor: function (color1, color2, steps, currentStep, alpha) {
if (alpha === undefined) { alpha = 255; }
var src1 = Phaser.Color.getRGB(color1);
var src2 = Phaser.Color.getRGB(color2);
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;
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);
},
/**
* 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 <rich@photonstorm.com>
* @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 <rich@photonstorm.com>
* @copyright 2016 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* Phaser.Particles is the Particle Manager for the game. It is called during the game update loop and in turn updates 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.name] = 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.name];
},
/**
* Called by the core game loop. Updates all Emitters who have their exists value set to true.
* @method Phaser.Particles#update
* @protected
*/
update: function () {
for (var key in this.emitters)
{
if (this.emitters[key].exists)
{
this.emitters[key].update();
}
}
}
};
Phaser.Particles.prototype.constructor = Phaser.Particles;
/* global Phaser:true */
/**
* @author Richard Davey <rich@photonstorm.com>
* @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 PIXI.Matrix();
}
PIXI.TextureSilentFail = true;
/**
* @author Richard Davey <rich@photonstorm.com>
* @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
*/