/// Basic
s.
* NOTE: Although Group
extends Basic
, it will not automatically
* add itself to the global collisions quad tree, it will only add its members.
*
* @author Adam Atomic
* @author Richard Davey
*/
class Group extends Basic {
constructor(game: Game, MaxSize?: number = 0) {
super(game);
this.isGroup = true;
this.members = [];
this.length = 0;
this._maxSize = MaxSize;
this._marker = 0;
this._sortIndex = null;
}
/**
* Use with sort()
to sort in ascending order.
*/
public static ASCENDING: number = -1;
/**
* Use with sort()
to sort in descending order.
*/
public static DESCENDING: number = 1;
/**
* Array of all the Basic
s that exist in this group.
*/
public members: Basic[];
/**
* The number of entries in the members array.
* For performance and safety you should check this variable
* instead of members.length unless you really know what you're doing!
*/
public length: number;
/**
* Internal tracker for the maximum capacity of the group.
* Default is 0, or no max capacity.
*/
private _maxSize: number;
/**
* Internal helper variable for recycling objects a la FlxEmitter
.
*/
private _marker: number;
/**
* Helper for sort.
*/
private _sortIndex: string;
/**
* Helper for sort.
*/
private _sortOrder: number;
/**
* Override this function to handle any deleting or "shutdown" type operations you might need,
* such as removing traditional Flash children like Basic objects.
*/
public destroy() {
if (this.members != null)
{
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if (basic != null)
{
basic.destroy();
}
}
this.members.length = 0;
}
this._sortIndex = null;
}
/**
* Automatically goes through and calls update on everything you added.
*/
public update() {
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if ((basic != null) && basic.exists && basic.active)
{
basic.preUpdate();
basic.update();
basic.postUpdate();
}
}
}
/**
* Automatically goes through and calls render on everything you added.
*/
public render(camera:Camera, cameraOffsetX: number, cameraOffsetY: number) {
var basic:Basic;
var i:number = 0;
while (i < this.length)
{
basic = this.members[i++];
if ((basic != null) && basic.exists && basic.visible)
{
basic.render(camera, cameraOffsetX, cameraOffsetY);
}
}
}
/**
* The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow.
*/
public get maxSize(): number {
return this._maxSize;
}
/**
* @private
*/
public set maxSize(Size: number) {
this._maxSize = Size;
if (this._marker >= this._maxSize)
{
this._marker = 0;
}
if ((this._maxSize == 0) || (this.members == null) || (this._maxSize >= this.members.length))
{
return;
}
//If the max size has shrunk, we need to get rid of some objects
var basic: Basic;
var i: number = this._maxSize;
var l: number = this.members.length;
while (i < l)
{
basic = this.members[i++];
if (basic != null)
{
basic.destroy();
}
}
this.length = this.members.length = this._maxSize;
}
/**
* Adds a new Basic
subclass (Basic, FlxBasic, Enemy, etc) to the group.
* Group will try to replace a null member of the array first.
* Failing that, Group will add it to the end of the member array,
* assuming there is room for it, and doubling the size of the array if necessary.
*
*
WARNING: If the group has a maxSize that has already been met, * the object will NOT be added to the group!
* * @param Object The object you want to add to the group. * * @return The sameBasic
object that was passed in.
*/
public add(Object: Basic) {
//Don't bother adding an object twice.
if (this.members.indexOf(Object) >= 0)
{
return Object;
}
//First, look for a null entry where we can add the object.
var i: number = 0;
var l: number = this.members.length;
while (i < l)
{
if (this.members[i] == null)
{
this.members[i] = Object;
if (i >= this.length)
{
this.length = i + 1;
}
return Object;
}
i++;
}
//Failing that, expand the array (if we can) and add the object.
if (this._maxSize > 0)
{
if (this.members.length >= this._maxSize)
{
return Object;
}
else if (this.members.length * 2 <= this._maxSize)
{
this.members.length *= 2;
}
else
{
this.members.length = this._maxSize;
}
}
else
{
this.members.length *= 2;
}
//If we made it this far, then we successfully grew the group,
//and we can go ahead and add the object at the first open slot.
this.members[i] = Object;
this.length = i + 1;
return Object;
}
/**
* Recycling is designed to help you reuse game objects without always re-allocating or "newing" them.
*
* If you specified a maximum size for this group (like in Emitter), * then recycle will employ what we're calling "rotating" recycling. * Recycle() will first check to see if the group is at capacity yet. * If group is not yet at capacity, recycle() returns a new object. * If the group IS at capacity, then recycle() just returns the next object in line.
* *If you did NOT specify a maximum size for this group, * then recycle() will employ what we're calling "grow-style" recycling. * Recycle() will return either the first object with exists == false, * or, finding none, add a new object to the array, * doubling the size of the array if necessary.
* *WARNING: If this function needs to create a new object, * and no object class was provided, it will return null * instead of a valid object!
* * @param ObjectClass The class type you want to recycle (e.g. FlxBasic, EvilRobot, etc). Do NOT "new" the class in the parameter! * * @return A reference to the object that was created. Don't forget to cast it back to the Class you want (e.g. myObject = myGroup.recycle(myObjectClass) as myObjectClass;). */ public recycle(ObjectClass = null) { var basic; if (this._maxSize > 0) { if (this.length < this._maxSize) { if (ObjectClass == null) { return null; } return this.add(new ObjectClass()); } else { basic = this.members[this._marker++]; if (this._marker >= this._maxSize) { this._marker = 0; } return basic; } } else { basic = this.getFirstAvailable(ObjectClass); if (basic != null) { return basic; } if (ObjectClass == null) { return null; } return this.add(new ObjectClass()); } } /** * Removes an object from the group. * * @param Object TheBasic
you want to remove.
* @param Splice Whether the object should be cut from the array entirely or not.
*
* @return The removed object.
*/
public remove(Object: Basic, Splice: bool = false): Basic {
var index: number = this.members.indexOf(Object);
if ((index < 0) || (index >= this.members.length))
{
return null;
}
if (Splice)
{
this.members.splice(index, 1);
this.length--;
}
else
{
this.members[index] = null;
}
return Object;
}
/**
* Replaces an existing Basic
with a new one.
*
* @param OldObject The object you want to replace.
* @param NewObject The new object you want to use instead.
*
* @return The new object.
*/
public replace(OldObject: Basic, NewObject: Basic): Basic {
var index: number = this.members.indexOf(OldObject);
if ((index < 0) || (index >= this.members.length))
{
return null;
}
this.members[index] = NewObject;
return NewObject;
}
/**
* Call this function to sort the group according to a particular value and order.
* For example, to sort game objects for Zelda-style overlaps you might call
* myGroup.sort("y",Group.ASCENDING)
at the bottom of your
* FlxState.update()
override. To sort all existing objects after
* a big explosion or bomb attack, you might call myGroup.sort("exists",Group.DESCENDING)
.
*
* @param Index The string
name of the member variable you want to sort on. Default value is "y".
* @param Order A Group
constant that defines the sort order. Possible values are Group.ASCENDING
and Group.DESCENDING
. Default value is Group.ASCENDING
.
*/
public sort(Index: string = "y", Order: number = Group.ASCENDING) {
this._sortIndex = Index;
this._sortOrder = Order;
this.members.sort(this.sortHandler);
}
/**
* Go through and set the specified variable to the specified value on all members of the group.
*
* @param VariableName The string representation of the variable name you want to modify, for example "visible" or "scrollFactor".
* @param Value The value you want to assign to that variable.
* @param Recurse Default value is true, meaning if setAll()
encounters a member that is a group, it will call setAll()
on that group rather than modifying its variable.
*/
public setAll(VariableName: string, Value: Object, Recurse: bool = true) {
var basic: Basic;
var i: number = 0;
while (i < length)
{
basic = this.members[i++];
if (basic != null)
{
if (Recurse && (basic.isGroup == true))
{
callAll()
encounters a member that is a group, it will call callAll()
on that group rather than calling the group's function.
*/
public callAll(FunctionName: string, Recurse: bool = true) {
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if (basic != null)
{
if (Recurse && (basic.isGroup == true))
{
Basic
currently flagged as not existing.
*/
public getFirstAvailable(ObjectClass = null) {
var basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if ((basic != null) && !basic.exists && ((ObjectClass == null) || (typeof basic === ObjectClass)))
{
return basic;
}
}
return null;
}
/**
* Call this function to retrieve the first index set to 'null'.
* Returns -1 if no index stores a null object.
*
* @return An int
indicating the first null slot in the group.
*/
public getFirstNull(): number {
var basic: Basic;
var i: number = 0;
var l: number = this.members.length;
while (i < l)
{
if (this.members[i] == null)
{
return i;
}
else
{
i++;
}
}
return -1;
}
/**
* Call this function to retrieve the first object with exists == true in the group.
* This is handy for checking if everything's wiped out, or choosing a squad leader, etc.
*
* @return A Basic
currently flagged as existing.
*/
public getFirstExtant(): Basic {
var basic: Basic;
var i: number = 0;
while (i < length)
{
basic = this.members[i++];
if ((basic != null) && basic.exists)
{
return basic;
}
}
return null;
}
/**
* Call this function to retrieve the first object with dead == false in the group.
* This is handy for checking if everything's wiped out, or choosing a squad leader, etc.
*
* @return A Basic
currently flagged as not dead.
*/
public getFirstAlive(): Basic {
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if ((basic != null) && basic.exists && basic.alive)
{
return basic;
}
}
return null;
}
/**
* Call this function to retrieve the first object with dead == true in the group.
* This is handy for checking if everything's wiped out, or choosing a squad leader, etc.
*
* @return A Basic
currently flagged as dead.
*/
public getFirstDead(): Basic {
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if ((basic != null) && !basic.alive)
{
return basic;
}
}
return null;
}
/**
* Call this function to find out how many members of the group are not dead.
*
* @return The number of Basic
s flagged as not dead. Returns -1 if group is empty.
*/
public countLiving(): number {
var count: number = -1;
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if (basic != null)
{
if (count < 0)
{
count = 0;
}
if (basic.exists && basic.alive)
{
count++;
}
}
}
return count;
}
/**
* Call this function to find out how many members of the group are dead.
*
* @return The number of Basic
s flagged as dead. Returns -1 if group is empty.
*/
public countDead(): number {
var count: number = -1;
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if (basic != null)
{
if (count < 0)
{
count = 0;
}
if (!basic.alive)
{
count++;
}
}
}
return count;
}
/**
* Returns a member at random from the group.
*
* @param StartIndex Optional offset off the front of the array. Default value is 0, or the beginning of the array.
* @param Length Optional restriction on the number of values you want to randomly select from.
*
* @return A Basic
from the members list.
*/
public getRandom(StartIndex: number = 0, Length: number = 0): Basic {
if (Length == 0)
{
Length = this.length;
}
return this._game.math.getRandom(this.members, StartIndex, Length);
}
/**
* Remove all instances of Basic
subclass (FlxBasic, FlxBlock, etc) from the list.
* WARNING: does not destroy() or kill() any of these objects!
*/
public clear() {
this.length = this.members.length = 0;
}
/**
* Calls kill on the group's members and then on the group itself.
*/
public kill() {
var basic: Basic;
var i: number = 0;
while (i < this.length)
{
basic = this.members[i++];
if ((basic != null) && basic.exists)
{
basic.kill();
}
}
}
/**
* Helper function for the sort process.
*
* @param Obj1 The first object being sorted.
* @param Obj2 The second object being sorted.
*
* @return An integer value: -1 (Obj1 before Obj2), 0 (same), or 1 (Obj1 after Obj2).
*/
public sortHandler(Obj1: Basic, Obj2: Basic): number {
if (Obj1[this._sortIndex] < Obj2[this._sortIndex])
{
return this._sortOrder;
}
else if (Obj1[this._sortIndex] > Obj2[this._sortIndex])
{
return -this._sortOrder;
}
return 0;
}
}