- Home /
Finding inactive game objects in a pool
In order to avoid instantiate/destroy at runtime, I built a simple object pool that contains enemy scripts (all enemy classes are inherited from the same abstract class, so I can create a pool that holds all types of enemy in one list).
However, I don't know how to find them and reference them correctly. Here's what I did:
public List<GameObject> GetEnemy (int amount, string type) {
//amount: how many enemies are needed
//type: what type of enemy is needed
List<GameObject> go = new List<GameObject>();
//end ForEach loop if got enough enemy
//the Enemy.cs had a public string that indicates the type of enemy
enemyRef.ForEach(script => {if (go.Count > amount) return; else if( (!script.gameObject.activeInHierarchy) && (script.type == type) ) go.Add(script.gameObject);} );
return go;
}
Although it works, it allocates about 1 kb memory and cause a 2.5 ~ 3ms spike for 800 game objects. This doesn't really kill the frame rate, but there must be a better way.
Is there any better way to find and reference a collection of game objects under certain conditions?
Answer by sparkzbarca · Dec 28, 2013 at 01:11 AM
i just created an object pool here it is if you like.
public class ObjectPool : MonoBehaviour {
List<List<GameObject>> GameObjectPools;
List<GameObject> GameObjectPool;
GameObject Temp;
public int POOL_SIZE;
public int GROWTH_SIZE;
int Counter;
// Use this for initialization
void Start () {
Counter = 0;
GameObjectPools = new List<List<GameObject>> ();
}
void FixedUpdate()
{
}
public GameObject RetrieveObject(GameObject BaseObject)
{
foreach(List<GameObject> Item in GameObjectPools)
{
Temp = Item[0];
if((Temp.name == (BaseObject.name + "(Clone)") || Temp.name == BaseObject.name) && Item.Count > 1)
{
Item.RemoveAt(0);
Temp.SetActive(true);
Counter++;
return Temp;
}
}
throw new System.NullReferenceException("RetrieveObject(BaseObject) BaseObject is not pooled, null error");
}
public bool PoolObject(GameObject BaseObject)
{
foreach(List<GameObject> Item in GameObjectPools)
{
Temp = Item[0];
if(Temp.name == BaseObject.name)
{
BaseObject.SetActive(false);
Item.Add(BaseObject);
return true;
}
}
throw new System.NullReferenceException("PoolObject(BaseObject) BaseObject is not pooled, null error");
return false;
}
IEnumerator PrePoolHelper(List<GameObject> GameObjectPool, GameObject BaseObject)
{
for (int counter = 0; counter < POOL_SIZE; counter++)
{
Temp = Instantiate(BaseObject) as GameObject;
Temp.SetActive(false);
GameObjectPool.Add(Temp);
if(counter % GROWTH_SIZE == 0)
{
yield return new WaitForEndOfFrame();
}
}
GameObjectPools.Add(GameObjectPool);
}
public void PrePool(GameObject BaseObject)
{
GameObjectPool = new List<GameObject>();
StartCoroutine(PrePoolHelper(GameObjectPool, BaseObject));
}
}
the way it works basically you supply it with how many of each object you want to pool and how many you want to add per frame (this is basically for pre pooling so if you want 1,000 items you can set it to 10 and it'll spread out the pooling over a 100 frames to help prevent you from trashing your framerate with 1,000 objects being pooled at once, unity will also actually throw up errors if you try to instantiate thousands of objects in a single frame)
when you want to use it in game
you do
ObjectPool PoolComponent = new PoolComponent();
foreach(gameobject object in poolist)
{
PoolComponent.prepool(object);
}
when you want to instantiate a new object wtihout using instantiate
//its set up to work with clone word attached or not to the object name
PoolComponent.retrieveobject(object);
to send it back
PoolComponent.poolobject(object);
stores it again
the key i'd say with my thing is that i have a list of lists
each object you want to pool gets its own list so for example if you have 10 objects and 1,000 clones each
it only searches the first item in each of the 10 lists to see if it can fidn the object to pool it doesn't iterate through all 10,000 doing a comparison.
checking one object in a list ensures all the clones are removed from any chance at being looked at.
wow, I'm impressed by such robust answer! Thanks!
I think my design has serious problem, since I'm trying to find a certain amount of objects ins$$anonymous$$d of one. This should be done by the game controller, not the pool.
$$anonymous$$y last solution was very similar to yours, and it worked like a charm: find the first inactive item that return the reference. Here's what I did before:
instanceList.Find(script => {if (script.gameObject.activeInHierarchy && (script.type == type) )})
However, this will always refer to the same element if I don't active it immediately after this. Then I ran into a hole that I had to retrieve a list from the pool.
I'll definitely try to use remove/add ins$$anonymous$$d of checking activation, since it's probably GetComponent<> based property.
$$anonymous$$y only question is, since you consistently remove/add objects in the pool, do you keep the reference that retrieved somewhere else?
For example, do I need to do something like this:
public class EnemyController : ObjectPool {
List<GameObject> reference; // reference to the objects that are retrieved
void GetEnemies (int amount, GameObject type) {
// get clones of certain type of enemy
for (int i = 0; i < amount; i++)
{
reference.Add (RetrieveObject(type));
}
}
IEnumerator SetEnemy (int amount, GameObject type, Vector3 pos, float waitTime) {
// set the positions
for (int i = 0; i < amount; i++)
{
if (reference[i] == type.name)
{
reference[i].transform.position = pos;
reference[i].SetActive(true); // I'd prefer active after setting position
yield return new WaitForSeconds(waitTime);
}
}
yield return null;
}
// in state machine:
void GenerateEnemy () {
if (condition)
{
StartCoroutine(SetEnemy (amount,type,pos,waitTime));
}
}
}
if you'd like to return multiple copies of an object you can modify to retreive a list of X number of clones ins$$anonymous$$d of just one easy
public GameObject RetrieveObject(GameObject BaseObject)
{
foreach(List<GameObject> Item in GameObjectPools)
{
Temp = Item[0];
if((Temp.name == (BaseObject.name + "(Clone)") || Temp.name == BaseObject.name) && Item.Count > 1)
{
Item.RemoveAt(0);
Temp.SetActive(true);
Counter++;
return Temp;
}
}
throw new System.NullReferenceException("RetrieveObject(BaseObject) BaseObject is not pooled, null error");
}
thats the current code
this is the new function that would work for a list
//CHANGED RETURN TYPE, CHANGED NA$$anonymous$$E, ADDED COUNT PARA$$anonymous$$ETER
public list<GameObject> RetrieveList(GameObject BaseObject, int Count)
{
list<GameObject> ReturnList; //ADDED
foreach(List<GameObject> Item in GameObjectPools)
{
Temp = Item[0];
//$$anonymous$$ODIFIED item.Count > 0 to > Count to make sure enough clones
//were availabe
if((Temp.name == (BaseObject.name + "(Clone)") || Temp.name == BaseObject.name) && Item.Count > Count)
{
//this will get the first Count number of clones which is how many you passed you wanted
//and put them in the return list.
Returnlist.addrange(Item.getrange(0, Count));
//then we'll remove the whole range as opposed to just one
Temp.removerange(0,Count);
//then we'll set the whole range to active ins$$anonymous$$d of just one
foreach(GameObject ReturnItem in ReturnList)
{
ReturnItem.setactive(true);
}
Counter++;
//then we'll return the list ins$$anonymous$$d of just one
return ReturnList;
}
}
throw new System.NullReferenceException("RetrieveObject(BaseObject) BaseObject is not pooled, null error");
}
Thanks for updating! I just get up and merge the codes.
Actually, I think the old one is better. if I need 50 instances and there are only 49 available, old one would return 49 and throw 1 exception, but this one will just throw an exception.
I still have some problems about memory optimization, but I guess it's out of topic for this question.
There's another thing I'm wondering, here's qoute from the doc:
The foreach statement is used to iterate through the collection to get the information that you want, but can not be used to add or remove items from the source collection to avoid unpredictable side effects.
Source: link text
Since the we can simply get the length of the list by List.Count, how about using for loop to iterate through the list?
Your answer
Follow this Question
Related Questions
How do I efficiently maintain a constantly-changing list without triggering GC? 2 Answers
Is there a more efficient way to write a "Find" script? 2 Answers
Is there a more efficient way to writ a "Find" script? 0 Answers
How big is too big? Terrain Question. 1 Answer
Optimization Question about Singleton 2 Answers