package org.flixel; import java.util.Comparator; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.reflect.ClassReflection; /** * This is an organizational class that can update and render a bunch of <code>FlxBasic</code>s. * NOTE: Although <code>FlxGroup</code> extends <code>FlxBasic</code>, it will not automatically * add itself to the global collisions quad tree, it will only add its members. * * @author Ka Wing Chin * @author Thomas Weston */ public class FlxGroup extends FlxBasic { /** * Use with <code>sort()</code> to sort in ascending order. */ static public final int ASCENDING = -1; /** * Use with <code>sort()</code> to sort in descending order. */ static public final int DESCENDING = 1; /** * Array of all the <code>FlxBasic</code>s that exist in this group. */ public Array<FlxBasic> members; /** * 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 int length; /** * Internal tracker for the maximum capacity of the group. * Default is 0, or no max capacity. */ protected int _maxSize; /** * Internal helper variable for recycling objects a la <code>FlxEmitter</code>. */ protected int _marker; /** * Helper for sort. */ protected String _sortIndex; /** * Helper for sort. */ protected int _sortOrder; /** * Constructor */ public FlxGroup(int MaxSize) { super(); members = new Array<FlxBasic>(MaxSize); length = 0; _maxSize = MaxSize; _marker = 0; _sortIndex = null; } /** * Constructor */ public FlxGroup() { this(0); } /** * Override this function to handle any deleting or "shutdown" type operations you might need, * such as removing traditional Flash children like Sprite objects. */ @Override public void destroy() { if(members != null) { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if(basic != null) basic.destroy(); } members.clear(); members = null; } _sortIndex = null; } /** * Just making sure we don't increment the active objects count. */ @Override public void preUpdate() { } /** * Automatically goes through and calls update on everything you added. */ @Override public void update() { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if((basic != null) && basic.exists && basic.active) { basic.preUpdate(); basic.update(); basic.postUpdate(); } } } /** * Automatically goes through and calls render on everything you added. */ @Override public void draw() { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if((basic != null) && basic.exists && basic.visible) basic.draw(); } } /** * The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow. */ public int getMaxSize() { return _maxSize; } /** * The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow. */ public void setMaxSize(int Size) { _maxSize = Size; if(_marker >= _maxSize) _marker = 0; if((_maxSize == 0) || (members == null) || (_maxSize >= members.size)) return; //If the max size has shrunk, we need to get rid of some objects FlxBasic basic; int i = _maxSize; int l = members.size; while(i < l) { basic = members.get(i++); if(basic != null) basic.destroy(); } members.truncate(length = _maxSize); } /** * Adds a new <code>FlxBasic</code> subclass (FlxBasic, FlxSprite, Enemy, etc) to the group. * FlxGroup will try to replace a null member of the array first. * Failing that, FlxGroup 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. * * <p>WARNING: If the group has a maxSize that has already been met, * the object will NOT be added to the group!</p> * * @param Object The object you want to add to the group. * * @return The same <code>FlxBasic</code> object that was passed in. */ public FlxBasic add(FlxBasic Object) { if(Object == null) { FlxG.log("WARNING: Cannot add 'null' object to a FlxGroup."); return null; } //Don't bother adding an object twice. if(members.indexOf(Object, true) >= 0) return Object; //First, look for a null entry where we can add the object. int i = 0; int l = members.size; while(i < l) { if(members.get(i) == null) { members.set(i, Object); if(i >= length) length = i+1; return Object; } i++; } //Failing that, expand the array (if we can) and add the object. if(_maxSize > 0) { if(members.size >= _maxSize) return Object; } //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. members.add(Object); length = i+1; return Object; } /** * Recycling is designed to help you reuse game objects without always re-allocating or "newing" them. * * <p>If you specified a maximum size for this group (like in FlxEmitter), * 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.</p> * * <p>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.</p> * * <p>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!</p> * * @param ObjectClass The class type you want to recycle (e.g. FlxSprite, 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 = (myObjectClass) myGroup.recycle(myObjectClass);). */ public FlxBasic recycle(Class<? extends FlxBasic> ObjectClass) { FlxBasic basic; if(_maxSize > 0) { if(length < _maxSize) { if(ObjectClass == null) return null; try { return add(ClassReflection.newInstance(ObjectClass)); } catch(Exception e) { throw new RuntimeException(e); } } else { basic = members.get(_marker++); if(_marker >= _maxSize) _marker = 0; return basic; } } else { basic = getFirstAvailable(ObjectClass); if(basic != null) return basic; if(ObjectClass == null) return null; try { return add(ClassReflection.newInstance(ObjectClass)); } catch(Exception e) { throw new RuntimeException(e); } } } /** * Recycling is designed to help you reuse game objects without always re-allocating or "newing" them. * * <p>If you specified a maximum size for this group (like in FlxEmitter), * 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.</p> * * <p>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.</p> * * <p>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!</p> * * @param ObjectClass The class type you want to recycle (e.g. FlxSprite, 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 = (myObjectClass) myGroup.recycle(myObjectClass);). */ public FlxBasic recycle() { return recycle(null); } /** * Removes an object from the group. * * @param Object The <code>FlxBasic</code> you want to remove. * @param Splice Whether the object should be cut from the array entirely or not. * * @return The removed object. */ public FlxBasic remove(FlxBasic Object,boolean Splice) { int index = members.indexOf(Object, true); if((index < 0) || (index >= members.size)) return null; if(Splice) { members.removeIndex(index); length--; } else members.set(index, null); return Object; } /** * Removes an object from the group. * * @param Object The <code>FlxBasic</code> you want to remove. * * @return The removed object. */ public FlxBasic remove(FlxBasic Object) { return remove(Object,false); } /** * Replaces an existing <code>FlxBasic</code> 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 FlxBasic replace(FlxBasic OldObject,FlxBasic NewObject) { int index = members.indexOf(OldObject, true); if((index < 0) || (index >= members.size)) return null; members.set(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 * <code>myGroup.sort("y",ASCENDING)</code> at the bottom of your * <code>FlxState.update()</code> override. To sort all existing objects after * a big explosion or bomb attack, you might call <code>myGroup.sort("exists",DESCENDING)</code>. * * @param Index The <code>String</code> name of the member variable you want to sort on. Default value is "y". * @param Order A <code>FlxGroup</code> constant that defines the sort order. Possible values are <code>ASCENDING</code> and <code>DESCENDING</code>. Default value is <code>ASCENDING</code>. */ public void sort(String Index,int Order) { _sortIndex = Index; _sortOrder = Order; members.sort(sortHandler); } /** * 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 * <code>myGroup.sort("y",ASCENDING)</code> at the bottom of your * <code>FlxState.update()</code> override. To sort all existing objects after * a big explosion or bomb attack, you might call <code>myGroup.sort("exists",DESCENDING)</code>. * * @param Index The <code>String</code> name of the member variable you want to sort on. Default value is "y". */ public void sort(String Index) { sort(Index,ASCENDING); } /** * 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 * <code>myGroup.sort("y",ASCENDING)</code> at the bottom of your * <code>FlxState.update()</code> override. To sort all existing objects after * a big explosion or bomb attack, you might call <code>myGroup.sort("exists",DESCENDING)</code>. * */ public void sort() { sort("y",ASCENDING); } /** * 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 <code>setAll()</code> encounters a member that is a group, it will call <code>setAll()</code> on that group rather than modifying its variable. */ public void setAll(String VariableName,Object Value,boolean Recurse) { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if(basic != null) { if(Recurse && (basic instanceof FlxGroup)) ((FlxGroup) basic).setAll(VariableName,Value,Recurse); else { try { ClassReflection.getField(basic.getClass(), VariableName).set(basic, Value); } catch(Exception e) { throw new RuntimeException(e); } } } } } /** * 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. */ public void setAll(String VariableName,Object Value) { setAll(VariableName,Value,true); } /** * 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 <code>callAll()</code> encounters a member that is a group, it will call <code>callAll()</code> on that group rather than calling the group's function. */ public void callAll(String FunctionName,boolean Recurse) { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if(basic != null) { if(Recurse && (basic instanceof FlxGroup)) ((FlxGroup) basic).callAll(FunctionName, Recurse); else { try { ClassReflection.getMethod(basic.getClass(), FunctionName).invoke(basic); } catch(Exception e) { throw new RuntimeException(e); } } } } } /** * 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()". */ public void callAll(String FunctionName) { callAll(FunctionName,true); } /** * 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 <code>FlxBasic</code> currently flagged as not existing. */ public FlxBasic getFirstAvailable(Class<? extends FlxBasic> ObjectClass) { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if((basic != null) && !basic.exists && ((ObjectClass == null) || (ClassReflection.isInstance(ObjectClass, basic)))) return basic; } return null; } /** * 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. * * @return A <code>FlxBasic</code> currently flagged as not existing. */ public FlxBasic getFirstAvailable() { return getFirstAvailable(null); } /** * Call this function to retrieve the first index set to 'null'. * Returns -1 if no index stores a null object. * * @return An <code>int</code> indicating the first null slot in the group. */ public int getFirstNull() { int i = 0; int l = members.size; while(i < l) { if(members.get(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 <code>FlxBasic</code> currently flagged as existing. */ public FlxBasic getFirstExtant() { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if((basic != null) && basic.exists) return basic; } return null; } /** * Call this function to retrieve the first object with alive == true in the group. * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. * * @return A <code>FlxBasic</code> currently flagged as not dead. */ public FlxBasic getFirstAlive() { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if((basic != null) && basic.exists && basic.alive) return basic; } return null; } /** * Call this function to retrieve the first object with alive == false in the group. * This is handy for checking if everything's wiped out, or choosing a squad leader, etc. * * @return A <code>FlxBasic</code> currently flagged as dead. */ public FlxBasic getFirstDead() { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(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 <code>FlxBasic</code>s flagged as not dead. Returns -1 if group is empty. */ public int countLiving() { int count = -1; FlxBasic basic; int i = 0; while(i < length) { basic = members.get(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 <code>FlxBasic</code>s flagged as dead. Returns -1 if group is empty. */ public int countDead() { int count = -1; FlxBasic basic; int i = 0; while(i < length) { basic = members.get(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 <code>FlxBasic</code> from the members list. */ public FlxBasic getRandom(int StartIndex,int Length) { if(Length == 0) Length = length; return FlxU.getRandom(members,StartIndex,Length); } /** * 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. * * @return A <code>FlxBasic</code> from the members list. */ public FlxBasic getRandom(int StartIndex) { return getRandom(StartIndex,0); } /** * Returns a member at random from the group. * * @return A <code>FlxBasic</code> from the members list. */ public FlxBasic getRandom() { return getRandom(0,0); } /** * Remove all instances of <code>FlxBasic</code> subclass (FlxSprite, FlxBlock, etc) from the list. * WARNING: does not destroy() or kill() any of these objects! */ public void clear() { length = 0; members.clear(); } /** * Calls kill on the group's members and then on the group itself. */ @Override public void kill() { FlxBasic basic; int i = 0; while(i < length) { basic = members.get(i++); if((basic != null) && basic.exists) basic.kill(); } super.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). */ // TODO: sortHandler only works with floats protected Comparator<FlxBasic> sortHandler = new Comparator<FlxBasic>() { @Override public int compare(FlxBasic Obj1,FlxBasic Obj2) { try { if((Float) ClassReflection.getField(Obj1.getClass(), _sortIndex).get(Obj1) < (Float) ClassReflection.getField(Obj2.getClass(), _sortIndex).get(Obj2)) return _sortOrder; else if((Float) ClassReflection.getField(Obj1.getClass(), _sortIndex).get(Obj1) > (Float) ClassReflection.getField(Obj2.getClass(), _sortIndex).get(Obj2)) return -_sortOrder; } catch(Exception e) { throw new RuntimeException(e); } return 0; } }; }