/// /// /** * Phaser - Group * * This class is used for organising, updating and sorting game objects. * */ module Phaser { export 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 Basics 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 Emitter. */ 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, Basic, 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 same Basic 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. Basic, 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(this._game)); } 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(this._game)); } } /** * Removes an object from the group. * * @param object The Basic 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 * State.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)) { basic['setAll'](VariableName, Value, Recurse); } else { basic[VariableName] = Value; } } } } /** * Go through and call the specified function on all members of the group. * Currently only works on functions that have no required parameters. * * @param FunctionName The string representation of the function you want to call on each object, for example "kill()" or "init()". * @param Recurse Default value is true, meaning if 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['callAll'](FunctionName, Recurse); } else { basic[FunctionName](); } } } } public forEach(callback, recursive: bool = false) { var basic; var i: number = 0; while (i < this.length) { basic = this.members[i++]; if (basic != null) { if (recursive && (basic.isGroup == true)) { basic.forEach(callback, true); } else { callback.call(this, basic); } } } } public forEachAlive(context, callback, recursive: bool = false) { var basic; var i: number = 0; while (i < this.length) { basic = this.members[i++]; if (basic != null && basic.alive) { if (recursive && (basic.isGroup == true)) { basic.forEachAlive(context, callback, true); } else { callback.call(context, basic); } } } } /** * Call this function to retrieve the first object with exists == false in the group. * This is handy for recycling in general, e.g. respawning enemies. * * @param ObjectClass An optional parameter that lets you narrow the results to instances of this particular class. * * @return A 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 Basics 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 Basics 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 (Basic, Block, 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; } } }