Hi everyone, im currently making an adventure game, so I need a function to instantiate different enemies with a limit and random time, but when i kill one or more of them the function validate that its possible to instantiate another enemy (in the Update function). The problem that i have is that the update function instantiate almost 5 enemies per frame (dont wait for the time) and dont validate the limit of enemies.
using UnityEngine;
using System.Collections;
public class EnemiesGenerator : MonoBehaviour {
public float enemiesLimit; //is the limit of enemies
public GameObject[] enemies; //the collection of enemies that gonna be instantiate
public int timeMin; // minimum and maximum time to instantiate
public int timeMax;
void Start () {
actualEnemies = new ArrayList ();
}
void Update () {
if (pecesGenerados.Count <= maximoPeces) {
Invoke("addEnemy", Random.Range (tiempoMin, tiempoMax));
}
}
void addEnemy(){
GameObject enemy = enemies[Random.Range(0,enemies.Length)];
Instantiate(enemy, new Vector3(5,5,0), Quaternion.identity);
actualEnemies.Add(enemy);
}
public static void deleteEnemy(){
actualEnemies.RemoveAt (0);
}
}
I actually made something like this somewhat recently for a test environment- I place a few empty GameObjects around the area tagged “EnemySpawnPoint”, and then toss this script on some random manager object in the scene:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Lysander
{
public class MonsterRespawner : MonoBehaviour
{
/// <summary>
/// A list of enemy types that can spawn from this respawner.
/// </summary>
public List<GameObject> enemyList;
/// <summary>
/// The maximum number of each TYPE of enemy that can be
/// spawned simultaneously.
/// </summary>
public int simultaneousByType;
/// <summary>
/// The maximum number of total spawns from this respawner,
/// regardless of type.
/// </summary>
public int simultaneousTotal;
/// <summary>
/// The tag for the spawn points associated with this respawner.
/// </summary>
public string pointName = "EnemySpawnPoint";
/// <summary>
/// The delay period between spawns.
/// </summary>
public float spawnDelay;
/// <summary>
/// The max random distance from the spawn points to spawn the monsters.
/// </summary>
public float randomPoint;
private GameObject[] spawnPoints;
private List<GameObject> currentSpawns = new List<GameObject> ();
private float lastSpawnTime = 0f;
public void Start ()
{
spawnPoints = GameObject.FindGameObjectsWithTag (pointName);
}
public void Update ()
{
//remove any deleted spawns from the list
currentSpawns.RemoveAll (p => p == null);
if (currentSpawns.Count < simultaneousTotal && //monster count less than total max
currentSpawns.Count < (simultaneousByType * enemyList.Count) && //monster count less than practical max
Time.time > (lastSpawnTime + spawnDelay)) { //after delay
SpawnEnemy ();
}
}
public void SpawnEnemy ()
{
//first check that there's actually a place for them to spawn
if (spawnPoints.Length > 0) {
//maybe we can replace the enemy list with a custom class
//that contains spawn percentage chances and other such things
//but for now let's keep this really simple and give everything
//an equal chance
List<int> possibleSpawnIndices = new List<int> ();
//narrow down the prefabs to possible-spawns only (based on simultaneousByType and current spawns)
foreach (GameObject prefab in enemyList) {
List<GameObject> instances = currentSpawns.FindAll (p => p.name == prefab.name);
if (instances.Count < simultaneousByType)
possibleSpawnIndices.Add (enemyList.IndexOf (prefab));
}
//select a random prefab out of the ones that fit the criteria
GameObject selectedPrefab = enemyList [possibleSpawnIndices [Random.Range (0, possibleSpawnIndices.Count - 1)]];
//select a random spawn point out of the ones we've found based on tags
Transform spawnpoint = spawnPoints [Random.Range (0, spawnPoints.Length)].transform;
//slightly randomize the spawn location around the selected spawn point
Vector3 ranPos = spawnpoint.position;
ranPos.x += Random.Range (0.0f, randomPoint);
ranPos.z += Random.Range (0.0f, randomPoint);
//make the new spawn, based on the prefab and spawn point
GameObject newSpawn = Instantiate (selectedPrefab, ranPos, spawnpoint.rotation) as GameObject;
//set the name to the prefab name, to keep a correlation we can track
newSpawn.name = selectedPrefab.name;
//add the new spawn to the list of spawns
currentSpawns.Add (newSpawn);
//set the timer
lastSpawnTime = Time.time;
}
}
}
}
It’s pre-commented since I made it as an example. It shouldn’t hard to see how you can make the interval semi-random if you wanted, too.
In your original solution, you are checking for the enemy count based on the already existing count of enemies in the list. However, think about how you create enemies. You invoke the function that creates enemies with a timed offset. This means, the enemy gets created some time after you ordered its creation and only then is it added to your list of active enemies and considered when checking for the current enemy count. This means, you could order the creation of hundreds of thousands of enemies and they are actually created, because only the active enemies are considered when checking how many enemies exist.
There are many many ways to fix this. Since you mix different languages in your original code, I am unable to exactly determinate which members are doing what, otherwise I could provide a solution for your exact code. The easiest way is probably to introduce a member that represents the maximum number of enemies that can be created. Then, in your Update(), check against that limit. If an additional enemy is allowed, increment another member that keeps track of how many enemies were created.
public class EnemiesGenerator : MonoBehaviour {
public int maximumEnemies; // maximum number of enemies
public int currentEnemies; // number of enemies being created through this generator
//... other members
void Update () {
if (currentEnemies < maximumEnemies) {
++currentEnemies;
// invoke enemy creation
}
}
}