I have more than 100(close to 200) enemy game objects in my scene and 5-6 being spawned every second. I'm using object pooling and the objects are not being instantiated every time but only when needed. I'm trying to record the stats where I can show total enemies remaining and total enemies taken down. A simple mathematical function of addition and subtraction from respective game objects is not adding up to what it should be. For example: Total enemies spawned 200 (when I didn't kill a single one) I kill a lot of enemies in an instant but when I'm done killing them all, enemies remaining is not always zero.
I tried killing the enemies one by one as they spawn and the addition and subtraction adds up correctly.
So how can I add and subtract to a variable from multiple game objects without loosing the accuracy?
public void TookDownEnemy(int enemiesTookDown)
{
//enemiesAlive -= enemiesTookDown;
enemiesTakenDown += enemiesTookDown;
Debug.Log("stats, enemies took down: " + enemiesTakenDown);
UpdateStats();
}
public void SpawnedNewEnemy(int spawned)
{
enemiesAlive += spawned;
UpdateStats();
}
public void UpdateStats()
{
enemiesTakenDownText.text = ": " + enemiesTakenDown.ToString();
enemiesAliveText.text = ": " + (enemiesAlive - enemiesTakenDown).ToString();
}
I'm calling SpawnedNewEnemy from a an enemy spawning game object everytime it spawns a new enemy, TookDownEnemy gets called from the gameobject when they are spawned.
public void InstantiateNewEnemy()
{
StatsManager.Instance.SpawnedNewEnemy(1);
Enemy instance = ObjectPooler.DequeueObject<Enemy>("Enemy");
instance.Initialize(10.0f); // get this hp value from the experience manager
instance.SetThisAlive();
instance.gameObject.SetActive(true);
instance.transform.SetParent(enemiesHolder);
int randomIndex = Random.Range(0, enemySpawnPoints.childCount);
instance.transform.position = enemySpawnPoints.GetChild(randomIndex).position;
instance.transform.rotation = Quaternion.identity;
}
In a similar way when enemy is killed or dies
if(hp <= 0 && !isDead)
{
//Destroy(this.gameObject); // use pooling here
isDead = true;
StatsManager.Instance.TookDownEnemy(1);
this.GetComponent<Animator>().Play("playDead");
Invoke("EnqueueGameObject", 0.25f);
}
Instead of directly adding and subtracting from a variable I made three different variables.
where enemiesRemaining = enemiesSpawned - enemiesTookDown
Even this is having a discrepancy. I'm assuming there should be a way to queue the addition and subtraction operation rather than just doing it.
UPDATE: I tried doing it by queuing the addition and subtraction operations and execute those operations at a defined interval.
public void TookDownEnemy(int enemiesTookDown)
{
enemiesTookDownQueue.Enqueue(enemiesTookDown);
}
public void SpawnedNewEnemy(int spawned)
{
enemiesAliveQueue.Enqueue(spawned);
}
Now I've also limited the spawning of enemies to 260(based on time I'm spawning a total of 260 enemies). When using Queue to add and subtract I'm sumhow(pun intended) always ending up with a difference of 80. Where 80 enemies remaining and I took down 180.
Here's how I'm adding those numbers up every 2 second.
public void UpdateStats()
{
while(enemiesAliveQueue.Count > 0)
{
enemiesAlive += (int)enemiesAliveQueue.Dequeue();
}
while(enemiesTookDownQueue.Count > 0)
{
enemiesTakenDown += (int)enemiesTookDownQueue.Dequeue();
}
enemiesTakenDownText.text = ": " + enemiesTakenDown.ToString();
coinsCollectedText.text = ": " + coinsCollected.ToString();
enemiesAliveText.text = ": " + (enemiesAlive - enemiesTakenDown).ToString();
}
So I did manage to solve the issue but still don't know why the inconsistency was being caused.
Previously I was using my own implementation of Object Pool by following a YouTube video in which a generic class was created to create pools which can then be accessed as needed. It works as expected but it is more of a C# solution than a Unity solution to object pooling.
public static void EnqueueObject<T>(T item, string name) where T : Component
{
if (!item.gameObject.activeSelf)
{
return;
}
item.transform.position = Vector2.zero;
poolDictionary[name].Enqueue(item);
Debug.Log("item.GetType(): " + item.GetType());
item.gameObject.SetActive(false);
}
public static T DequeueObject<T>(string key) where T : Component
{
if (poolDictionary[key].TryDequeue(out var item))
{
return (T)item;
}
return (T)EnqueueNewInstance(poolLookup[key], key);
//return (T)poolDictionary[key].Dequeue();
}
public static T EnqueueNewInstance<T>(T item, string key) where T : Component
{
T newInstance = Object.Instantiate(item);
newInstance.gameObject.SetActive(false);
newInstance.transform.position = Vector2.zero;
poolDictionary[key].Enqueue(newInstance);
return newInstance;
}
Turns out the right way to pool objects in Unity is to use UnityEngine.Pool
instead. I followed this tutorial which was essentially doing the same thing but this time I don't have to worry about the queue.
I added these to my EnemySpawner.cs,
private void Start()
{
_enemyPool = new ObjectPool<Enemy>(() =>
{
return Instantiate(enemyPrefab);
}, enemy =>
{
enemy.gameObject.SetActive(true);
}, enemy =>
{
enemy.gameObject.SetActive(false);
}, enemy =>
{
Destroy(enemy.gameObject);
}, false, 10, 20);
}
public void KillEnemy(Enemy enemy)
{
_enemyPool.Release(enemy);
}
public void InstantiateNewEnemy()
{
StatsManager.Instance.SpawnedNewEnemy(1);
Enemy instance = _enemyPool.Get();
instance.Initialize(10.0f); // get this hp value from the experience manager
instance.SetThisAlive();
instance.gameObject.SetActive(true);
instance.transform.SetParent(enemiesHolder);
int randomIndex = Random.Range(0, enemySpawnPoints.childCount);
instance.transform.position = enemySpawnPoints.GetChild(randomIndex).position;
instance.transform.rotation = Quaternion.identity;
}
Then when an enemy is killed
public void KillMyself()
{
StatsManager.Instance.TookDownEnemy(1);
EnemySpawner.Instance.KillEnemy(this);
}
(originally posted as an edit to the question by OP, rather than as an answer)