- Home /
More Efficient Enemy Spawning Than This?
I'm making a bullet hell game (shoot-em-up with a TON of bullets), and I'm wondering if there's a more efficient way of spawning enemies over time than what I'm doing right now.
Basically I want to spawn various waves of enemies, like enemyOne every second for five seconds, enemyTwo every second for ten seconds, stuff like that.
What I'm doing right now is having a bunch of if statements and executing the code when the current time is between two values, but is there a more efficient way of doing that? It's just really tedious to have to redo all these if statements every time I want a new wave of enemies.
if (Time.time <= 5.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 1.0f;
GameObject enemy = Instantiate(enemyOne, new Vector3(-1400f, 0, -1500f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyOne>().setID(enemyOneCounter);
enemyOneCounter++;
}
}
if (Time.time <= 10.0f && Time.time >= 5.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 1.0f;
GameObject enemy = Instantiate(enemyOne, new Vector3(1400f, 0, -1500f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyOne>().setID(enemyOneCounter);
enemyOneCounter++;
}
}
if (Time.time <= 25.0f && Time.time >= 10.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 5.0f;
GameObject enemy = Instantiate(enemyTwo, new Vector3(0, 0, -1950f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyTwo>().setID(enemyTwoCounter);
enemyTwoCounter++;
}
}
if (Time.time <= 30.0f && Time.time >= 25.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 5.0f;
GameObject enemy = Instantiate(enemyTwo, new Vector3(0, 0, -1950f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyTwo>().setID(enemyTwoCounter);
enemyTwoCounter++;
GameObject enemya = Instantiate(enemyTwo, new Vector3(0, 0, -1950f), Quaternion.identity) as GameObject;
enemya.GetComponent<enemyTwo>().setID(enemyTwoCounter);
enemyTwoCounter++;
GameObject enemyb = Instantiate(enemyTwo, new Vector3(0, 0, -1950f), Quaternion.identity) as GameObject;
enemyb.GetComponent<enemyTwo>().setID(enemyTwoCounter);
enemyTwoCounter++;
}
}
if (Time.time <= 33.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 3.0f;
GameObject enemy = Instantiate(enemyThree, new Vector3(-1400f, 0, -2000f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyThree>().setID(enemyThreeCounter);
enemyThreeCounter++;
}
}
if (Time.time <= 36.0f && Time.time >= 33.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 3.0f;
GameObject enemy = Instantiate(enemyThree, new Vector3(1400f, 0, -2000f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyThree>().setID(enemyThreeCounter);
enemyThreeCounter++;
}
}
if (Time.time <= 39.0f && Time.time >= 36.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 3.0f;
GameObject enemy = Instantiate(enemyThree, new Vector3(0, 0, -2000f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyThree>().setID(enemyThreeCounter);
enemyThreeCounter++;
}
}
if (Time.time <= 45.0f && Time.time >= 39.0f) {
if (Time.time > nextSpawn) {
nextSpawn = Time.time + 6.0f;
GameObject enemy = Instantiate(enemyThree, new Vector3(-1400f, 0, -2000f), Quaternion.identity) as GameObject;
enemy.GetComponent<enemyThree>().setID(enemyThreeCounter);
enemyThreeCounter++;
GameObject enemya = Instantiate(enemyThree, new Vector3(1400f, 0, -2000f), Quaternion.identity) as GameObject;
enemya.GetComponent<enemyThree>().setID(enemyThreeCounter);
enemyThreeCounter++;
GameObject enemyb = Instantiate(enemyThree, new Vector3(0, 0, -2000f), Quaternion.identity) as GameObject;
enemyb.GetComponent<enemyThree>().setID(enemyThreeCounter);
enemyThreeCounter++;
}
}
Answer by iwaldrop · Jul 01, 2013 at 07:00 AM
From the perspectives of performance, and writing DRY code, we can certainly improve upon what you have here. DRY is an acronym for "Don't repeat yourself", and is a very good practice for novice programmers. Let's look at how you might be able to make this more efficient and maintainable.
First I'm assuming this is in an update loop. Bad idea. Every frame that this script is running it will check all of your conditionals. And because they're AND operations that's actually quite a lot of stuff to do every frame for (99.999% of the time) no reason. Sure, the computer can do it all in a fraction of a second, but why waste the time?
Secondly, you're hardcoding values that you should either declare as constants or expose in the inspector for tweaking.
I don't see what you're doing with your enemies, so I'm just providing a more efficient and maintainable version of the code you've posted, and kind of extrapolating out a couple of details. If you have questions related to my answer here, please update your question and leave a comment so I get an email letting me know.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class EnemySpawnManager : MonoBehaviour
{
enum EnemyType { One, Two, Three }
public class Enemy : MonoBehaviour { public virtual void KillNow(){} }
public class EnemyOne : Enemy {}
public class EnemyTwo : Enemy {}
public class EnemyThree : Enemy {}
#region Attributes
public GameObject[] enemyPrefabs;
public float[] waveTimers;
#endregion
#region Unity
void Awake()
{
// initialize dictionary and lists
activeEnemies = new Dictionary<EnemyType, List<GameObject>>();
activeEnemies[EnemyType.One] = new List<GameObject>();
activeEnemies[EnemyType.Two] = new List<GameObject>();
activeEnemies[EnemyType.Three] = new List<GameObject>();
// kick off spawning
EnemySpawnTimer();
}
#endregion
#region Public
public int GetEnemyId(Enemy enemy)
{
int index = -1;
GetEnemyTypeList(enemy, (list) =>
{
if (list != null)
index = list.IndexOf(enemy.gameObject);
});
return index;
}
public void KillEnemy(Enemy enemy)
{
GetEnemyTypeList(enemy, (list) => list.Remove(enemy.gameObject));
enemy.KillNow();
}
#endregion
#region Private
private Dictionary<EnemyType, List<GameObject>> activeEnemies;
private int waveCount;
private Dictionary <Type, Func<EnemyType>> switchType = new Dictionary<Type, Func<EnemyType>>()
{
{ typeof(EnemyOne), () => { return EnemyType.One; } },
{ typeof(EnemyTwo), () => { return EnemyType.Two; } },
{ typeof(EnemyThree), () => { return EnemyType.Three; } },
};
void EnemySpawnTimer()
{
EnemyType type = EnemyType.One;
int numberToSpawn = 0;
switch(waveCount)
{
case 0:
numberToSpawn = 1;
break;
case 1:
type = EnemyType.Two;
numberToSpawn = 1;
break;
case 2:
type = EnemyType.Two;
numberToSpawn = 2;
break;
case 3:
type = EnemyType.Three;
numberToSpawn = 3;
break;
}
for (int i = 0; i <= numberToSpawn; i++)
activeEnemies[type].Add(SpawnEnemy(type));
Invoke("EnemySpawnTimer", waveTimers[waveCount++]);
}
void GetEnemyTypeList(Enemy enemy, Action<List <GameObject>> action)
{
GameObject go = enemy.gameObject;
EnemyType type = switchType[enemy.GetType()]();
if (activeEnemies[type].Contains(go))
action(activeEnemies[type]);
}
GameObject SpawnEnemy(EnemyType enemyType)
{
return Instantiate(enemyPrefabs[(int)enemyType], new Vector3(-1400f, 0, -1500f), Quaternion.identity) as GameObject;
}
#endregion
}
This way you can just ask the EnemySpawnManager for an enemy's id, plus you can kill enemies indirectly as well (like if you're resetting a level or something). Even this could be more efficient, but at least we're not repeating ourselves any longer, and we've eliminated the update loop. I hate update loops, and you should too. :)
I actually just discovered coroutines, would that be an efficient solution too? Like just have one coroutine to generate the entire level, and start the coroutine in the Start() function?
An Invoke works, essentially, the same way as a Coroutine, except it's a bit more flexible, I$$anonymous$$O. In this case a Coroutine would have to repeat a bunch of code, whereas an Invoke runs the same code each time it's called. Your call, really, but I wouldn't use one here.
Answer by Immanuel-Scholz · Jul 01, 2013 at 07:20 AM
I don't share iwaldrop's hate for Update functions and recommend neither should you :P. IMHO, having lots of "global managers" around is definetely going to hurt you more.
Anyway, I'd also restructure stuff a bit to make it more friendly to tweak in the inspector. You can either assemble your data structures in Awake() (as iwaldrop did with the dictionary), but I'd just go with the original data, just to be carefull that you can serialize your data. If you aren't into Editor-writing, use the following rules of thumb:
Use arrays
Make simple classes (no structs!) with some plain public values
Mark your classes [System.Serializable]
What I mean is something like this (written without any testing, so fix my typos first ;)):
[System.Serializable]
class SpawnEntry
{
public float startSpawnTime, endSpawnTime, spawnInterval;
public GameObject enemyToSpawn;
public int amountPerSpawn;
public Vector3 spawnPos;
[HideInInspector]
public float nextSpawn; // timer counting to 0
[HideInInspector]
public int counter;
}
public SpawnEntry[] spawns;
void Update()
{
foreach (var e in spawns)
{
if (Time.time > e.startSpawnTime && Time.time < e.endSpawnTime)
{
if (e.nextSpawn > 0) e.nextSpawn -= Time.deltaTime;
else
{
for (int i = 0; i < e.amountPerSpawn; ++i)
{
e.nextSpawn += e.spawnInterval;
GameObject enemy = Instantiate(e.enemyToSpawn, e.spawnPos, Quaternion.identity) as GameObject;
enemy.GetComponent<enemyBase>().setID(e.counter);
e.counter++;
}
}
}
}
}
Be sure that you have a base class of enemyOne
and enemyTwo
that contains the setId() function so you can access it in a generic way..
Your answer
Follow this Question
Related Questions
random enemy spawn from day night cycle 2 Answers
c# Enemy's spawning over time, problem 2 Answers
More efficient way to spawn enemies in my game...? 2 Answers
Spawning game Object after a period of time 2 Answers
Help needed! 1 Answer