phaser/src/gameobjects/container/Container.js

1348 lines
44 KiB
JavaScript

/**
* @author Richard Davey <rich@photonstorm.com>
* @author Felipe Alfonso <@bitnenfer>
* @copyright 2020 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var ArrayUtils = require('../../utils/array');
var BlendModes = require('../../renderer/BlendModes');
var Class = require('../../utils/Class');
var Components = require('../components');
var Events = require('../events');
var GameObject = require('../GameObject');
var Rectangle = require('../../geom/rectangle/Rectangle');
var Render = require('./ContainerRender');
var Union = require('../../geom/rectangle/Union');
var Vector2 = require('../../math/Vector2');
/**
* @classdesc
* A Container Game Object.
*
* A Container, as the name implies, can 'contain' other types of Game Object.
* When a Game Object is added to a Container, the Container becomes responsible for the rendering of it.
* By default it will be removed from the Display List and instead added to the Containers own internal list.
*
* The position of the Game Object automatically becomes relative to the position of the Container.
*
* The origin of a Container is 0x0 (in local space) and that cannot be changed. The children you add to the
* Container should be positioned with this value in mind. I.e. you should treat 0x0 as being the center of
* the Container, and position children positively and negative around it as required.
*
* When the Container is rendered, all of its children are rendered as well, in the order in which they exist
* within the Container. Container children can be repositioned using methods such as `MoveUp`, `MoveDown` and `SendToBack`.
*
* If you modify a transform property of the Container, such as `Container.x` or `Container.rotation` then it will
* automatically influence all children as well.
*
* Containers can include other Containers for deeply nested transforms.
*
* Containers can have masks set on them and can be used as a mask too. However, Container children cannot be masked.
* The masks do not 'stack up'. Only a Container on the root of the display list will use its mask.
*
* Containers can be enabled for input. Because they do not have a texture you need to provide a shape for them
* to use as their hit area. Container children can also be enabled for input, independent of the Container.
*
* If input enabling a _child_ you should not set both the `origin` and a **negative** scale factor on the child,
* or the input area will become misaligned.
*
* Containers can be given a physics body for either Arcade Physics, Impact Physics or Matter Physics. However,
* if Container _children_ are enabled for physics you may get unexpected results, such as offset bodies,
* if the Container itself, or any of its ancestors, is positioned anywhere other than at 0 x 0. Container children
* with physics do not factor in the Container due to the excessive extra calculations needed. Please structure
* your game to work around this.
*
* It's important to understand the impact of using Containers. They add additional processing overhead into
* every one of their children. The deeper you nest them, the more the cost escalates. This is especially true
* for input events. You also loose the ability to set the display depth of Container children in the same
* flexible manner as those not within them. In short, don't use them for the sake of it. You pay a small cost
* every time you create one, try to structure your game around avoiding that where possible.
*
* @class Container
* @extends Phaser.GameObjects.GameObject
* @memberof Phaser.GameObjects
* @constructor
* @since 3.4.0
*
* @extends Phaser.GameObjects.Components.AlphaSingle
* @extends Phaser.GameObjects.Components.BlendMode
* @extends Phaser.GameObjects.Components.ComputedSize
* @extends Phaser.GameObjects.Components.Depth
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Pipeline
* @extends Phaser.GameObjects.Components.Transform
* @extends Phaser.GameObjects.Components.Visible
*
* @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time.
* @param {number} [x=0] - The horizontal position of this Game Object in the world.
* @param {number} [y=0] - The vertical position of this Game Object in the world.
* @param {Phaser.GameObjects.GameObject[]} [children] - An optional array of Game Objects to add to this Container.
*/
var Container = new Class({
Extends: GameObject,
Mixins: [
Components.AlphaSingle,
Components.BlendMode,
Components.ComputedSize,
Components.Depth,
Components.Mask,
Components.Pipeline,
Components.Transform,
Components.Visible,
Render
],
initialize:
function Container (scene, x, y, children)
{
GameObject.call(this, scene, 'Container');
/**
* An array holding the children of this Container.
*
* @name Phaser.GameObjects.Container#list
* @type {Phaser.GameObjects.GameObject[]}
* @since 3.4.0
*/
this.list = [];
/**
* Does this Container exclusively manage its children?
*
* The default is `true` which means a child added to this Container cannot
* belong in another Container, which includes the Scene display list.
*
* If you disable this then this Container will no longer exclusively manage its children.
* This allows you to create all kinds of interesting graphical effects, such as replicating
* Game Objects without reparenting them all over the Scene.
* However, doing so will prevent children from receiving any kind of input event or have
* their physics bodies work by default, as they're no longer a single entity on the
* display list, but are being replicated where-ever this Container is.
*
* @name Phaser.GameObjects.Container#exclusive
* @type {boolean}
* @default true
* @since 3.4.0
*/
this.exclusive = true;
/**
* Containers can have an optional maximum size. If set to anything above 0 it
* will constrict the addition of new Game Objects into the Container, capping off
* the maximum limit the Container can grow in size to.
*
* @name Phaser.GameObjects.Container#maxSize
* @type {number}
* @default -1
* @since 3.4.0
*/
this.maxSize = -1;
/**
* The cursor position.
*
* @name Phaser.GameObjects.Container#position
* @type {number}
* @since 3.4.0
*/
this.position = 0;
/**
* Internal Transform Matrix used for local space conversion.
*
* @name Phaser.GameObjects.Container#localTransform
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.4.0
*/
this.localTransform = new Components.TransformMatrix();
/**
* Internal temporary Transform Matrix used to avoid object creation.
*
* @name Phaser.GameObjects.Container#tempTransformMatrix
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @private
* @since 3.4.0
*/
this.tempTransformMatrix = new Components.TransformMatrix();
/**
* The property key to sort by.
*
* @name Phaser.GameObjects.Container#_sortKey
* @type {string}
* @private
* @since 3.4.0
*/
this._sortKey = '';
/**
* A reference to the Scene Systems Event Emitter.
*
* @name Phaser.GameObjects.Container#_sysEvents
* @type {Phaser.Events.EventEmitter}
* @private
* @since 3.9.0
*/
this._sysEvents = scene.sys.events;
/**
* The horizontal scroll factor of this Container.
*
* The scroll factor controls the influence of the movement of a Camera upon this Container.
*
* When a camera scrolls it will change the location at which this Container is rendered on-screen.
* It does not change the Containers actual position values.
*
* For a Container, setting this value will only update the Container itself, not its children.
* If you wish to change the scrollFactor of the children as well, use the `setScrollFactor` method.
*
* A value of 1 means it will move exactly in sync with a camera.
* A value of 0 means it will not move at all, even if the camera moves.
* Other values control the degree to which the camera movement is mapped to this Container.
*
* Please be aware that scroll factor values other than 1 are not taken in to consideration when
* calculating physics collisions. Bodies always collide based on their world position, but changing
* the scroll factor is a visual adjustment to where the textures are rendered, which can offset
* them from physics bodies if not accounted for in your code.
*
* @name Phaser.GameObjects.Container#scrollFactorX
* @type {number}
* @default 1
* @since 3.4.0
*/
this.scrollFactorX = 1;
/**
* The vertical scroll factor of this Container.
*
* The scroll factor controls the influence of the movement of a Camera upon this Container.
*
* When a camera scrolls it will change the location at which this Container is rendered on-screen.
* It does not change the Containers actual position values.
*
* For a Container, setting this value will only update the Container itself, not its children.
* If you wish to change the scrollFactor of the children as well, use the `setScrollFactor` method.
*
* A value of 1 means it will move exactly in sync with a camera.
* A value of 0 means it will not move at all, even if the camera moves.
* Other values control the degree to which the camera movement is mapped to this Container.
*
* Please be aware that scroll factor values other than 1 are not taken in to consideration when
* calculating physics collisions. Bodies always collide based on their world position, but changing
* the scroll factor is a visual adjustment to where the textures are rendered, which can offset
* them from physics bodies if not accounted for in your code.
*
* @name Phaser.GameObjects.Container#scrollFactorY
* @type {number}
* @default 1
* @since 3.4.0
*/
this.scrollFactorY = 1;
this.initPipeline();
this.setPosition(x, y);
this.clearAlpha();
this.setBlendMode(BlendModes.SKIP_CHECK);
if (children)
{
this.add(children);
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#originX
* @type {number}
* @readonly
* @override
* @since 3.4.0
*/
originX: {
get: function ()
{
return 0.5;
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#originY
* @type {number}
* @readonly
* @override
* @since 3.4.0
*/
originY: {
get: function ()
{
return 0.5;
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#displayOriginX
* @type {number}
* @readonly
* @override
* @since 3.4.0
*/
displayOriginX: {
get: function ()
{
return this.width * 0.5;
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#displayOriginY
* @type {number}
* @readonly
* @override
* @since 3.4.0
*/
displayOriginY: {
get: function ()
{
return this.height * 0.5;
}
},
/**
* Does this Container exclusively manage its children?
*
* The default is `true` which means a child added to this Container cannot
* belong in another Container, which includes the Scene display list.
*
* If you disable this then this Container will no longer exclusively manage its children.
* This allows you to create all kinds of interesting graphical effects, such as replicating
* Game Objects without reparenting them all over the Scene.
* However, doing so will prevent children from receiving any kind of input event or have
* their physics bodies work by default, as they're no longer a single entity on the
* display list, but are being replicated where-ever this Container is.
*
* @method Phaser.GameObjects.Container#setExclusive
* @since 3.4.0
*
* @param {boolean} [value=true] - The exclusive state of this Container.
*
* @return {this} This Container.
*/
setExclusive: function (value)
{
if (value === undefined) { value = true; }
this.exclusive = value;
return this;
},
/**
* Gets the bounds of this Container. It works by iterating all children of the Container,
* getting their respective bounds, and then working out a min-max rectangle from that.
* It does not factor in if the children render or not, all are included.
*
* Some children are unable to return their bounds, such as Graphics objects, in which case
* they are skipped.
*
* Depending on the quantity of children in this Container it could be a really expensive call,
* so cache it and only poll it as needed.
*
* The values are stored and returned in a Rectangle object.
*
* @method Phaser.GameObjects.Container#getBounds
* @since 3.4.0
*
* @param {Phaser.Geom.Rectangle} [output] - A Geom.Rectangle object to store the values in. If not provided a new Rectangle will be created.
*
* @return {Phaser.Geom.Rectangle} The values stored in the output object.
*/
getBounds: function (output)
{
if (output === undefined) { output = new Rectangle(); }
output.setTo(this.x, this.y, 0, 0);
if (this.parentContainer)
{
var parentMatrix = this.parentContainer.getBoundsTransformMatrix();
var transformedPosition = parentMatrix.transformPoint(this.x, this.y);
output.setTo(transformedPosition.x, transformedPosition.y, 0, 0);
}
if (this.list.length > 0)
{
var children = this.list;
var tempRect = new Rectangle();
var hasSetFirst = false;
output.setEmpty();
for (var i = 0; i < children.length; i++)
{
var entry = children[i];
if (entry.getBounds)
{
entry.getBounds(tempRect);
if (!hasSetFirst)
{
output.setTo(tempRect.x, tempRect.y, tempRect.width, tempRect.height);
hasSetFirst = true;
}
else
{
Union(tempRect, output, output);
}
}
}
}
return output;
},
/**
* Internal add handler.
*
* @method Phaser.GameObjects.Container#addHandler
* @private
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that was just added to this Container.
*/
addHandler: function (gameObject)
{
gameObject.once(Events.DESTROY, this.remove, this);
if (this.exclusive)
{
if (gameObject.parentContainer)
{
gameObject.parentContainer.remove(gameObject);
}
gameObject.removeFromDisplayList();
gameObject.parentContainer = this;
}
},
/**
* Internal remove handler.
*
* @method Phaser.GameObjects.Container#removeHandler
* @private
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that was just removed from this Container.
*/
removeHandler: function (gameObject)
{
gameObject.off(Events.DESTROY, this.remove);
if (this.exclusive)
{
gameObject.parentContainer = null;
gameObject.addToDisplayList();
}
},
/**
* Takes a Point-like object, such as a Vector2, Geom.Point or object with public x and y properties,
* and transforms it into the space of this Container, then returns it in the output object.
*
* @method Phaser.GameObjects.Container#pointToContainer
* @since 3.4.0
*
* @param {(object|Phaser.Geom.Point|Phaser.Math.Vector2)} source - The Source Point to be transformed.
* @param {(object|Phaser.Geom.Point|Phaser.Math.Vector2)} [output] - A destination object to store the transformed point in. If none given a Vector2 will be created and returned.
*
* @return {(object|Phaser.Geom.Point|Phaser.Math.Vector2)} The transformed point.
*/
pointToContainer: function (source, output)
{
if (output === undefined) { output = new Vector2(); }
if (this.parentContainer)
{
this.parentContainer.pointToContainer(source, output);
}
else
{
output = new Vector2(source.x, source.y);
}
var tempMatrix = this.tempTransformMatrix;
// No need to loadIdentity because applyITRS overwrites every value anyway
tempMatrix.applyITRS(this.x, this.y, this.rotation, this.scaleX, this.scaleY);
tempMatrix.invert();
tempMatrix.transformPoint(source.x, source.y, output);
return output;
},
/**
* Returns the world transform matrix as used for Bounds checks.
*
* The returned matrix is temporal and shouldn't be stored.
*
* @method Phaser.GameObjects.Container#getBoundsTransformMatrix
* @since 3.4.0
*
* @return {Phaser.GameObjects.Components.TransformMatrix} The world transform matrix.
*/
getBoundsTransformMatrix: function ()
{
return this.getWorldTransformMatrix(this.tempTransformMatrix, this.localTransform);
},
/**
* Adds the given Game Object, or array of Game Objects, to this Container.
*
* Each Game Object must be unique within the Container.
*
* @method Phaser.GameObjects.Container#add
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[]} child - The Game Object, or array of Game Objects, to add to the Container.
*
* @return {this} This Container instance.
*/
add: function (child)
{
ArrayUtils.Add(this.list, child, this.maxSize, this.addHandler, this);
return this;
},
/**
* Adds the given Game Object, or array of Game Objects, to this Container at the specified position.
*
* Existing Game Objects in the Container are shifted up.
*
* Each Game Object must be unique within the Container.
*
* @method Phaser.GameObjects.Container#addAt
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[]} child - The Game Object, or array of Game Objects, to add to the Container.
* @param {number} [index=0] - The position to insert the Game Object/s at.
*
* @return {this} This Container instance.
*/
addAt: function (child, index)
{
ArrayUtils.AddAt(this.list, child, index, this.maxSize, this.addHandler, this);
return this;
},
/**
* Returns the Game Object at the given position in this Container.
*
* @method Phaser.GameObjects.Container#getAt
* @since 3.4.0
*
* @param {number} index - The position to get the Game Object from.
*
* @return {?Phaser.GameObjects.GameObject} The Game Object at the specified index, or `null` if none found.
*/
getAt: function (index)
{
return this.list[index];
},
/**
* Returns the index of the given Game Object in this Container.
*
* @method Phaser.GameObjects.Container#getIndex
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to search for in this Container.
*
* @return {number} The index of the Game Object in this Container, or -1 if not found.
*/
getIndex: function (child)
{
return this.list.indexOf(child);
},
/**
* Sort the contents of this Container so the items are in order based on the given property.
* For example: `sort('alpha')` would sort the elements based on the value of their `alpha` property.
*
* @method Phaser.GameObjects.Container#sort
* @since 3.4.0
*
* @param {string} property - The property to lexically sort by.
* @param {function} [handler] - Provide your own custom handler function. Will receive 2 children which it should compare and return a boolean.
*
* @return {this} This Container instance.
*/
sort: function (property, handler)
{
if (!property)
{
return this;
}
if (handler === undefined)
{
handler = function (childA, childB)
{
return childA[property] - childB[property];
};
}
ArrayUtils.StableSort(this.list, handler);
return this;
},
/**
* Searches for the first instance of a child with its `name` property matching the given argument.
* Should more than one child have the same name only the first is returned.
*
* @method Phaser.GameObjects.Container#getByName
* @since 3.4.0
*
* @param {string} name - The name to search for.
*
* @return {?Phaser.GameObjects.GameObject} The first child with a matching name, or `null` if none were found.
*/
getByName: function (name)
{
return ArrayUtils.GetFirst(this.list, 'name', name);
},
/**
* Returns a random Game Object from this Container.
*
* @method Phaser.GameObjects.Container#getRandom
* @since 3.4.0
*
* @param {number} [startIndex=0] - An optional start index.
* @param {number} [length] - An optional length, the total number of elements (from the startIndex) to choose from.
*
* @return {?Phaser.GameObjects.GameObject} A random child from the Container, or `null` if the Container is empty.
*/
getRandom: function (startIndex, length)
{
return ArrayUtils.GetRandom(this.list, startIndex, length);
},
/**
* Gets the first Game Object in this Container.
*
* You can also specify a property and value to search for, in which case it will return the first
* Game Object in this Container with a matching property and / or value.
*
* For example: `getFirst('visible', true)` would return the first Game Object that had its `visible` property set.
*
* You can limit the search to the `startIndex` - `endIndex` range.
*
* @method Phaser.GameObjects.Container#getFirst
* @since 3.4.0
*
* @param {string} property - The property to test on each Game Object in the Container.
* @param {*} value - The value to test the property against. Must pass a strict (`===`) comparison check.
* @param {number} [startIndex=0] - An optional start index to search from.
* @param {number} [endIndex=Container.length] - An optional end index to search up to (but not included)
*
* @return {?Phaser.GameObjects.GameObject} The first matching Game Object, or `null` if none was found.
*/
getFirst: function (property, value, startIndex, endIndex)
{
return ArrayUtils.GetFirst(this.list, property, value, startIndex, endIndex);
},
/**
* Returns all Game Objects in this Container.
*
* You can optionally specify a matching criteria using the `property` and `value` arguments.
*
* For example: `getAll('body')` would return only Game Objects that have a body property.
*
* You can also specify a value to compare the property to:
*
* `getAll('visible', true)` would return only Game Objects that have their visible property set to `true`.
*
* Optionally you can specify a start and end index. For example if this Container had 100 Game Objects,
* and you set `startIndex` to 0 and `endIndex` to 50, it would return matches from only
* the first 50 Game Objects.
*
* @method Phaser.GameObjects.Container#getAll
* @since 3.4.0
*
* @param {string} [property] - The property to test on each Game Object in the Container.
* @param {any} [value] - If property is set then the `property` must strictly equal this value to be included in the results.
* @param {number} [startIndex=0] - An optional start index to search from.
* @param {number} [endIndex=Container.length] - An optional end index to search up to (but not included)
*
* @return {Phaser.GameObjects.GameObject[]} An array of matching Game Objects from this Container.
*/
getAll: function (property, value, startIndex, endIndex)
{
return ArrayUtils.GetAll(this.list, property, value, startIndex, endIndex);
},
/**
* Returns the total number of Game Objects in this Container that have a property
* matching the given value.
*
* For example: `count('visible', true)` would count all the elements that have their visible property set.
*
* You can optionally limit the operation to the `startIndex` - `endIndex` range.
*
* @method Phaser.GameObjects.Container#count
* @since 3.4.0
*
* @param {string} property - The property to check.
* @param {any} value - The value to check.
* @param {number} [startIndex=0] - An optional start index to search from.
* @param {number} [endIndex=Container.length] - An optional end index to search up to (but not included)
*
* @return {number} The total number of Game Objects in this Container with a property matching the given value.
*/
count: function (property, value, startIndex, endIndex)
{
return ArrayUtils.CountAllMatching(this.list, property, value, startIndex, endIndex);
},
/**
* Swaps the position of two Game Objects in this Container.
* Both Game Objects must belong to this Container.
*
* @method Phaser.GameObjects.Container#swap
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child1 - The first Game Object to swap.
* @param {Phaser.GameObjects.GameObject} child2 - The second Game Object to swap.
*
* @return {this} This Container instance.
*/
swap: function (child1, child2)
{
ArrayUtils.Swap(this.list, child1, child2);
return this;
},
/**
* Moves a Game Object to a new position within this Container.
*
* The Game Object must already be a child of this Container.
*
* The Game Object is removed from its old position and inserted into the new one.
* Therefore the Container size does not change. Other children will change position accordingly.
*
* @method Phaser.GameObjects.Container#moveTo
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to move.
* @param {number} index - The new position of the Game Object in this Container.
*
* @return {this} This Container instance.
*/
moveTo: function (child, index)
{
ArrayUtils.MoveTo(this.list, child, index);
return this;
},
/**
* Removes the given Game Object, or array of Game Objects, from this Container.
*
* The Game Objects must already be children of this Container.
*
* You can also optionally call `destroy` on each Game Object that is removed from the Container.
*
* @method Phaser.GameObjects.Container#remove
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject|Phaser.GameObjects.GameObject[]} child - The Game Object, or array of Game Objects, to be removed from the Container.
* @param {boolean} [destroyChild=false] - Optionally call `destroy` on each child successfully removed from this Container.
*
* @return {this} This Container instance.
*/
remove: function (child, destroyChild)
{
var removed = ArrayUtils.Remove(this.list, child, this.removeHandler, this);
if (destroyChild && removed)
{
if (!Array.isArray(removed))
{
removed = [ removed ];
}
for (var i = 0; i < removed.length; i++)
{
removed[i].destroy();
}
}
return this;
},
/**
* Removes the Game Object at the given position in this Container.
*
* You can also optionally call `destroy` on the Game Object, if one is found.
*
* @method Phaser.GameObjects.Container#removeAt
* @since 3.4.0
*
* @param {number} index - The index of the Game Object to be removed.
* @param {boolean} [destroyChild=false] - Optionally call `destroy` on the Game Object if successfully removed from this Container.
*
* @return {this} This Container instance.
*/
removeAt: function (index, destroyChild)
{
var removed = ArrayUtils.RemoveAt(this.list, index, this.removeHandler, this);
if (destroyChild && removed)
{
removed.destroy();
}
return this;
},
/**
* Removes the Game Objects between the given positions in this Container.
*
* You can also optionally call `destroy` on each Game Object that is removed from the Container.
*
* @method Phaser.GameObjects.Container#removeBetween
* @since 3.4.0
*
* @param {number} [startIndex=0] - An optional start index to search from.
* @param {number} [endIndex=Container.length] - An optional end index to search up to (but not included)
* @param {boolean} [destroyChild=false] - Optionally call `destroy` on each Game Object successfully removed from this Container.
*
* @return {this} This Container instance.
*/
removeBetween: function (startIndex, endIndex, destroyChild)
{
var removed = ArrayUtils.RemoveBetween(this.list, startIndex, endIndex, this.removeHandler, this);
if (destroyChild)
{
for (var i = 0; i < removed.length; i++)
{
removed[i].destroy();
}
}
return this;
},
/**
* Removes all Game Objects from this Container.
*
* You can also optionally call `destroy` on each Game Object that is removed from the Container.
*
* @method Phaser.GameObjects.Container#removeAll
* @since 3.4.0
*
* @param {boolean} [destroyChild=false] - Optionally call `destroy` on each Game Object successfully removed from this Container.
*
* @return {this} This Container instance.
*/
removeAll: function (destroyChild)
{
var removed = ArrayUtils.RemoveBetween(this.list, 0, this.list.length, this.removeHandler, this);
if (destroyChild)
{
for (var i = 0; i < removed.length; i++)
{
removed[i].destroy();
}
}
return this;
},
/**
* Brings the given Game Object to the top of this Container.
* This will cause it to render on-top of any other objects in the Container.
*
* @method Phaser.GameObjects.Container#bringToTop
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to bring to the top of the Container.
*
* @return {this} This Container instance.
*/
bringToTop: function (child)
{
ArrayUtils.BringToTop(this.list, child);
return this;
},
/**
* Sends the given Game Object to the bottom of this Container.
* This will cause it to render below any other objects in the Container.
*
* @method Phaser.GameObjects.Container#sendToBack
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to send to the bottom of the Container.
*
* @return {this} This Container instance.
*/
sendToBack: function (child)
{
ArrayUtils.SendToBack(this.list, child);
return this;
},
/**
* Moves the given Game Object up one place in this Container, unless it's already at the top.
*
* @method Phaser.GameObjects.Container#moveUp
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to be moved in the Container.
*
* @return {this} This Container instance.
*/
moveUp: function (child)
{
ArrayUtils.MoveUp(this.list, child);
return this;
},
/**
* Moves the given Game Object down one place in this Container, unless it's already at the bottom.
*
* @method Phaser.GameObjects.Container#moveDown
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to be moved in the Container.
*
* @return {this} This Container instance.
*/
moveDown: function (child)
{
ArrayUtils.MoveDown(this.list, child);
return this;
},
/**
* Reverses the order of all Game Objects in this Container.
*
* @method Phaser.GameObjects.Container#reverse
* @since 3.4.0
*
* @return {this} This Container instance.
*/
reverse: function ()
{
this.list.reverse();
return this;
},
/**
* Shuffles the all Game Objects in this Container using the Fisher-Yates implementation.
*
* @method Phaser.GameObjects.Container#shuffle
* @since 3.4.0
*
* @return {this} This Container instance.
*/
shuffle: function ()
{
ArrayUtils.Shuffle(this.list);
return this;
},
/**
* Replaces a Game Object in this Container with the new Game Object.
* The new Game Object cannot already be a child of this Container.
*
* @method Phaser.GameObjects.Container#replace
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} oldChild - The Game Object in this Container that will be replaced.
* @param {Phaser.GameObjects.GameObject} newChild - The Game Object to be added to this Container.
* @param {boolean} [destroyChild=false] - Optionally call `destroy` on the Game Object if successfully removed from this Container.
*
* @return {this} This Container instance.
*/
replace: function (oldChild, newChild, destroyChild)
{
var moved = ArrayUtils.Replace(this.list, oldChild, newChild);
if (moved)
{
this.addHandler(newChild);
this.removeHandler(oldChild);
if (destroyChild)
{
oldChild.destroy();
}
}
return this;
},
/**
* Returns `true` if the given Game Object is a direct child of this Container.
*
* This check does not scan nested Containers.
*
* @method Phaser.GameObjects.Container#exists
* @since 3.4.0
*
* @param {Phaser.GameObjects.GameObject} child - The Game Object to check for within this Container.
*
* @return {boolean} True if the Game Object is an immediate child of this Container, otherwise false.
*/
exists: function (child)
{
return (this.list.indexOf(child) > -1);
},
/**
* Sets the property to the given value on all Game Objects in this Container.
*
* Optionally you can specify a start and end index. For example if this Container had 100 Game Objects,
* and you set `startIndex` to 0 and `endIndex` to 50, it would return matches from only
* the first 50 Game Objects.
*
* @method Phaser.GameObjects.Container#setAll
* @since 3.4.0
*
* @param {string} property - The property that must exist on the Game Object.
* @param {any} value - The value to get the property to.
* @param {number} [startIndex=0] - An optional start index to search from.
* @param {number} [endIndex=Container.length] - An optional end index to search up to (but not included)
*
* @return {this} This Container instance.
*/
setAll: function (property, value, startIndex, endIndex)
{
ArrayUtils.SetAll(this.list, property, value, startIndex, endIndex);
return this;
},
/**
* @callback EachContainerCallback
* @generic I - [item]
*
* @param {*} item - The child Game Object of the Container.
* @param {...*} [args] - Additional arguments that will be passed to the callback, after the child.
*/
/**
* Passes all Game Objects in this Container to the given callback.
*
* A copy of the Container is made before passing each entry to your callback.
* This protects against the callback itself modifying the Container.
*
* If you know for sure that the callback will not change the size of this Container
* then you can use the more performant `Container.iterate` method instead.
*
* @method Phaser.GameObjects.Container#each
* @since 3.4.0
*
* @param {function} callback - The function to call.
* @param {object} [context] - Value to use as `this` when executing callback.
* @param {...*} [args] - Additional arguments that will be passed to the callback, after the child.
*
* @return {this} This Container instance.
*/
each: function (callback, context)
{
var args = [ null ];
var i;
var temp = this.list.slice();
var len = temp.length;
for (i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
for (i = 0; i < len; i++)
{
args[0] = temp[i];
callback.apply(context, args);
}
return this;
},
/**
* Passes all Game Objects in this Container to the given callback.
*
* Only use this method when you absolutely know that the Container will not be modified during
* the iteration, i.e. by removing or adding to its contents.
*
* @method Phaser.GameObjects.Container#iterate
* @since 3.4.0
*
* @param {function} callback - The function to call.
* @param {object} [context] - Value to use as `this` when executing callback.
* @param {...*} [args] - Additional arguments that will be passed to the callback, after the child.
*
* @return {this} This Container instance.
*/
iterate: function (callback, context)
{
var args = [ null ];
var i;
for (i = 2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
for (i = 0; i < this.list.length; i++)
{
args[0] = this.list[i];
callback.apply(context, args);
}
return this;
},
/**
* Sets the scroll factor of this Container and optionally all of its children.
*
* The scroll factor controls the influence of the movement of a Camera upon this Game Object.
*
* When a camera scrolls it will change the location at which this Game Object is rendered on-screen.
* It does not change the Game Objects actual position values.
*
* A value of 1 means it will move exactly in sync with a camera.
* A value of 0 means it will not move at all, even if the camera moves.
* Other values control the degree to which the camera movement is mapped to this Game Object.
*
* Please be aware that scroll factor values other than 1 are not taken in to consideration when
* calculating physics collisions. Bodies always collide based on their world position, but changing
* the scroll factor is a visual adjustment to where the textures are rendered, which can offset
* them from physics bodies if not accounted for in your code.
*
* @method Phaser.GameObjects.Container#setScrollFactor
* @since 3.4.0
*
* @param {number} x - The horizontal scroll factor of this Game Object.
* @param {number} [y=x] - The vertical scroll factor of this Game Object. If not set it will use the `x` value.
* @param {boolean} [updateChildren=false] - Apply this scrollFactor to all Container children as well?
*
* @return {this} This Game Object instance.
*/
setScrollFactor: function (x, y, updateChildren)
{
if (y === undefined) { y = x; }
if (updateChildren === undefined) { updateChildren = false; }
this.scrollFactorX = x;
this.scrollFactorY = y;
if (updateChildren)
{
ArrayUtils.SetAll(this.list, 'scrollFactorX', x);
ArrayUtils.SetAll(this.list, 'scrollFactorY', y);
}
return this;
},
/**
* The number of Game Objects inside this Container.
*
* @name Phaser.GameObjects.Container#length
* @type {number}
* @readonly
* @since 3.4.0
*/
length: {
get: function ()
{
return this.list.length;
}
},
/**
* Returns the first Game Object within the Container, or `null` if it is empty.
*
* You can move the cursor by calling `Container.next` and `Container.previous`.
*
* @name Phaser.GameObjects.Container#first
* @type {?Phaser.GameObjects.GameObject}
* @readonly
* @since 3.4.0
*/
first: {
get: function ()
{
this.position = 0;
if (this.list.length > 0)
{
return this.list[0];
}
else
{
return null;
}
}
},
/**
* Returns the last Game Object within the Container, or `null` if it is empty.
*
* You can move the cursor by calling `Container.next` and `Container.previous`.
*
* @name Phaser.GameObjects.Container#last
* @type {?Phaser.GameObjects.GameObject}
* @readonly
* @since 3.4.0
*/
last: {
get: function ()
{
if (this.list.length > 0)
{
this.position = this.list.length - 1;
return this.list[this.position];
}
else
{
return null;
}
}
},
/**
* Returns the next Game Object within the Container, or `null` if it is empty.
*
* You can move the cursor by calling `Container.next` and `Container.previous`.
*
* @name Phaser.GameObjects.Container#next
* @type {?Phaser.GameObjects.GameObject}
* @readonly
* @since 3.4.0
*/
next: {
get: function ()
{
if (this.position < this.list.length)
{
this.position++;
return this.list[this.position];
}
else
{
return null;
}
}
},
/**
* Returns the previous Game Object within the Container, or `null` if it is empty.
*
* You can move the cursor by calling `Container.next` and `Container.previous`.
*
* @name Phaser.GameObjects.Container#previous
* @type {?Phaser.GameObjects.GameObject}
* @readonly
* @since 3.4.0
*/
previous: {
get: function ()
{
if (this.position > 0)
{
this.position--;
return this.list[this.position];
}
else
{
return null;
}
}
},
/**
* Internal destroy handler, called as part of the destroy process.
*
* @method Phaser.GameObjects.Container#preDestroy
* @protected
* @since 3.9.0
*/
preDestroy: function ()
{
this.removeAll(!!this.exclusive);
this.localTransform.destroy();
this.tempTransformMatrix.destroy();
this.list = [];
}
});
module.exports = Container;