- Home /
NavMesh and WaveSpawner dont work together!
Hello! I have a question about my NavMeshAgent and my WaveSpawnerScript.
My NavMeshAgent has an empty GameObject named ''target'' as destination. This target is linked to my character. This all works fine!
I also have a WaveSpawnerScript. For this I have set Enemy1 as the SpawningObject. Enemy1 is in my world as a normal object. It is also the NavMeshAgent. My WaveSpawner detects if the object Enemy is missing. Then it waits a short time and spawns new ones. But if I delete/destroy Enemy 1 (shoot at him), it doesn't know what to spawn, because Enemy 1 doesn't exist anymore. Normally I would have solved this by making Enemy1 as prefab. But the prefab: Enemy1 also needs an Prefab as a target. But if I make the target a prefab and then make it Enemy1s destination, the agent only runs to the point 0/0/0 because the prefab target is normally stored there. Even when i put the target-prefab as a child of my character the Spawning Enemy1 isnt going to go to me. How do I do that now?
Answer by privatecontractor · Jan 22 at 06:55 PM
Hi EinfachFinn,
Just make prefab of whole GameObject enemy (with hierarchy that you already have).... If you have prefabs in prefab (not suposed to affect, but if you having some problems with it try to unpack it, and act as: whole enemy consist also of target). If you will have still some problems with it. Please atache prtscr of hierarchy.
Then consider: stop using update for checking if enemy 1... enemy 2... enemy 3... exist instead let WaveSpawner to Instantiate the prefab of whole enemy. Then acces new spawned enemy script and assign things you need (instance of parent spawner, instance of player, etc). Add to enemy script void which when enemy dies, make one of have two options (1) Destroy enemy and create new one (you will create more Garbage in memory) (2) Reuse enemy by faking destroying (hiding, reseting, relocating and showing).
So spawner will know what to spawn, also you will gain some cpu power (not running update on spawner).
Hope will help. Maybe look for some information about object pooling (believe could be very usefully).
Some Code Help below: Probably tons of bug but try to catch the main Idea... also you using well to much public xxx, instead of private...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaveSpawner : MonoBehaviour
{
[System.Serializable]
public class Wave
{
public string m_nameOfWave = "Easy"; // Sugestion: m_nameOfWave
public Enemy_AI m_PrefabOfEnemy; // Sugestion: m_PrefabOfEnemy, changed Transform to Enemy_AI // Which prefab should be spawned: Enemy
public int m_AmountOfEnemysToSpawn = 6; // Sugestion: m_AmountOfEnemysToSpawn // How much enemys should be spawned per wave: 6
public float m_SpawnDelayInSeconds = 3; // Sugestion: m_SpawnDelayInSeconds // How often the count of enemies should spawn
}
public Wave[] waves;
public float timeBetweenWaves = 5f; // Time between waves: 5f = 5 seconds
public int m_CurrentWave = 0; // Sugestion: m_CurrentWave // Time Counting down to next wave: 3... 2... 1...
//Few things is rewriten :)
WaitForSecondsRealtime m_SpawnerDelay_BetwenEnemys; //Due to the fact that will use coorutines, grabing some memory for them to not collect garbage...
WaitForSecondsRealtime m_SpawnerDelay_BetwenWaves; //Due to the fact that will use coorutines, grabing some memory for them to not collect garbage...
Enemy_AI m_lastSpawnedEnemy_Buffor; //Same as above...
[SerializeField] Transform m_playerTransform; //Here will assign Player Transform, so enemys could pretend that don't see him, but having him already :)
int m_amountOfRemainingEnemysBeforeNextWave; //will use it to counting when spawn new wave...
//Few new things...
Queue<Enemy_AI> m_enemy_AIs_Quene = new Queue<Enemy_AI>();
private void Start()
{
m_SpawnerDelay_BetwenWaves = new WaitForSecondsRealtime(timeBetweenWaves);
if (waves.Length >= 0) spawNextWave();
else victory();
}
void spawNextWave()
{
//state = SpawnState.Spawning; //now we don't need this state
m_SpawnerDelay_BetwenWaves = new WaitForSecondsRealtime(waves[m_CurrentWave].m_SpawnDelayInSeconds);
StartCoroutine(DelayBetwen_Enemys());
}
IEnumerator DelayBetwen_Enemys()
{
m_amountOfRemainingEnemysBeforeNextWave = 0;
for (int i = 0; i < waves[m_CurrentWave].m_AmountOfEnemysToSpawn; i++)
{
m_lastSpawnedEnemy_Buffor = grabNewEnemy();
m_lastSpawnedEnemy_Buffor.Setup(this, m_playerTransform);
m_amountOfRemainingEnemysBeforeNextWave++;
yield return m_SpawnerDelay_BetwenWaves;
}
}
IEnumerator DelayBetwen_Waves()
{
yield return m_SpawnerDelay_BetwenWaves;
spawNextWave();
}
// This ReturnToQuene void will be called from Enemy_AI script when Enemy will be "killed".... so it will get back here for reuseing...
public void ReturnToQuene(Enemy_AI _enemy_AI)
{
m_enemy_AIs_Quene.Enqueue(_enemy_AI);
_enemy_AI.HideEnemy();
m_amountOfRemainingEnemysBeforeNextWave--;
if (m_amountOfRemainingEnemysBeforeNextWave <= 0)
{
m_CurrentWave++;
if (m_CurrentWave < waves.Length)
{
StartCoroutine(DelayBetwen_Waves());
}else
{
victory();
}
}
}
Enemy_AI grabNewEnemy()
{
if (m_enemy_AIs_Quene.Count >0)
{
return m_enemy_AIs_Quene.Dequeue();
}else
{
return Instantiate(waves[0].m_PrefabOfEnemy, this.transform); //You use: transform.position, transform.rotation.... if you need ok, I suggest setting parent (In order to not having mess in hierarchy)
}
}
void victory()
{
Debug.Log("All Waves Completed. Level finished.");
}
}
public class Enemy_AI : MonoBehaviour
{
[SerializeField] NavMeshAgent m_navMeshAgent;
[SerializeField] Transform m_target; //So wavlue will be setup by you in prefab....
WaveSpawner m_waveSpawner;
public void Setup(WaveSpawner _waveSpawner, Transform _playerTransform)
{
m_waveSpawner = _waveSpawner;
m_target = _playerTransform; //TODO: using Target position for movement NAvMesh + Update...
}
void showEnemy()
{
//TODO: call void to reset health...
//TODO: call void to reset healthbar...
//TODO: enable all MeshRenders or SpriteRenders...
//TODO: enable colliders/ awake rigidbody etc...
}
public void HideEnemy() => hideEnemy();
void hideEnemy()
{
//TODO: disable all MeshRenders or SpriteRenders...
//TODO: dsabele colliders/ sleap rigidbody etc...
}
void returnToQuene()
{
m_waveSpawner.ReturnToQuene(this);
}
}
@EinfachFinn, so: consider two scenarios... (1) enemy when spawned goins directly for player (no need for GameObject Target... in Enemy prefab cause it will be palyer) thats how its writen above by me. (2) or eneny when spawned will fo to some place (GameObject taget in his prefab) then you can move this (and only) GaneObject to make him wondering around/ making patrol routines etc...
Probably at thw end you will make something betwen. Good job! Keep pushing.
Answer by EinfachFinn · Jan 22 at 07:11 PM
@privatecontractor I didn't think I would get a response so quickly. Thank you! I am relatively new when it comes to programming. I mix a lot of tutorials together and learn as I go. How would you do that with this script? using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;
public class WaveSpawner : MonoBehaviour {
public enum SpawnState { Spawning, Waiting, Counting, Finished } // State of the wave
[System.Serializable]
public class Wave
{
public string name = "Easy";
public Transform enemy = null; // Which prefab should be spawned: Enemy
public int count = 6; // How much enemys should be spawned per wave: 6
public float rate = 3; // How often the count of enemies should spawn
}
public Wave[] waves = null;
public float timeBetweenWaves = 5f; // Time between waves: 5f = 5 seconds
public int countdownFrom = 3; // The number which countdown starts from
public int waveCountdown = 0; // Time Counting down to next wave: 3... 2... 1...
public int waveIndex = 0;
private int enemyIndex = 0;
private float timer = 0;
public SpawnState state = SpawnState.Counting;
private string enemyTag = "Enemy";
private void Start()
{
waveIndex = 0;
enemyIndex = 0;
state = SpawnState.Waiting;
waveCountdown = 0;
timer = 0;
if (waves == null || waves.Length == 0)
{
state = SpawnState.Finished;
}
}
void Update()
{
if (state == SpawnState.Finished)
{
return;
}
switch (state)
{
case SpawnState.Spawning:
if (enemyIndex >= waves[waveIndex].count)
{
if (!EnemyIsAlive())
{
enemyIndex = 0;
waveIndex++; state = SpawnState.Waiting;
if (waveIndex < waves.Length)
{
Debug.Log("Waiting for the next wave.");
}
else
{
Debug.Log("That was the last wave.");
}
}
}
else
{
if (timer >= waves[waveIndex].rate)
{
timer = 0; waveCountdown = countdownFrom;
enemyIndex++;
SpawnEnemy(waves[waveIndex].enemy);
}
else
{
}
}
break;
case SpawnState.Waiting:
if (waveIndex >= waves.Length)
{
if (!EnemyIsAlive())
{
state = SpawnState.Finished;
Debug.Log("All Waves Completed. Level finished.");
}
}
else
{
if (timer >= timeBetweenWaves)
{
timer = 0;
waveCountdown = countdownFrom;
state = SpawnState.Counting;
Debug.Log("Countdown started.");
}
else
{
timer += Time.deltaTime;
}
}
break;
case SpawnState.Counting:
if (timer >= countdownFrom)
{
timer = 0;
waveCountdown = 0;
state = SpawnState.Spawning;
Debug.Log("Countdown finished. Spawning new wave.");
}
else
{
timer += Time.deltaTime;
waveCountdown = countdownFrom - Mathf.FloorToInt(timer);
}
break;
}
}
private bool EnemyIsAlive() // Method to search for living enemies
{
GameObject[] objects = GameObject.FindGameObjectsWithTag(enemyTag);
return objects != null && objects.Length > 0;
}
private void SpawnEnemy(Transform _enemy) // Spawn enemy Method
{
Transform enemy = Instantiate(_enemy, transform.position, transform.rotation);
enemy.tag = enemyTag;
Debug.Log("Enemy spawned: " + _enemy.name);
}
}