Search code examples
c#unity-game-enginecoroutine

Coroutine causing weird spawning issue in unity


I know this is bad code (it is to be tidied up later)

I am trying to modify a tutorial by adding in a special move. Without the code for the special move the game works fine. When the enemy count is 0 a new wave of enemies is spawned. But if I use the special move it spawns about 20 waves

I'm guessing it's to do with the Coroutine and delay? My log shows enemyCount.Length goes up to a number but then to 0 for no reason which adds to the wave until it stops at around 18

I can't see why. Help please?

Player code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float playerSpeed = 5;
    private Rigidbody playerRb;         //to access it for movement
    private GameObject focalPoint;      //to pan camera around
    private int playerHealth = 10;  

    //powerup vars
    public bool hasPowerUp;                 //to allow player to hit harder with it
    public float powerUpStrength = 15f;     //so we can adjust how hard the powerup hits

    //special move vars
    public bool specialMove = false;
    public Vector3 jump;
    public float jumpForce = 2.0f;
    public Vector3 drop;
    public float dropForce = 2.0f;
    public bool smashCam = false;            //to get camera to shake and set enemy flying
    public float flingStrength = 40f;       //so we can adjust how hard the special move hits

    void Start()
    {
        //set up the rigidbody component to be used for movement
        playerRb = GetComponent<Rigidbody>();
        focalPoint = GameObject.Find("FocalPoint");

        jump = new Vector3(0.0f, 2.0f, 0.0f);       //jump for powerup move
        drop = new Vector3(0.0f, -2.0f, 0.0f);       //drop for powerup move
    }

    void Update()
    {
        //moves player
        float forwardInput = Input.GetAxis("Vertical");
        playerRb.AddForce(focalPoint.transform.forward * playerSpeed * forwardInput);

        if (Input.GetKeyDown(KeyCode.Space)) {
            SpecialMove();
        }

        //special move jumping stuff
        if (specialMove == true && gameObject.transform.position.y > 0.8)
        {
            StartCoroutine(SpecialMoveDelay());
        }
    }
    IEnumerator SpecialMoveDelay()
    {
        playerRb.constraints = RigidbodyConstraints.FreezePositionY;        //freeze in air
        smashCam = true;                                                    //flag for camera spin

        yield return new WaitForSeconds(1);

        specialMove = false;                 //to set enemies moving again
        smashCam = false;                    //turn off the smash cam

        //smash player into ground
        playerRb.constraints = RigidbodyConstraints.None;           //release xy constraints from earlier
        playerRb.AddForce(drop * dropForce, ForceMode.Impulse);     //smash to ground

        //*******Fling enemies away*******************
        var enemyCount = FindObjectsByType<EnemyClass>(FindObjectsSortMode.None);   //get all the enemies

        for (int i = 0; i < enemyCount.Length; i++)     //for every enemy
        {
            //get the rigidbody of the enemy so we can fling it
            Rigidbody enemyRb = enemyCount[i].gameObject.GetComponent<Rigidbody>();

            //set the force that will fling enemies away
            Vector3 awayFromPlayer = (enemyCount[i].gameObject.transform.position - transform.position);

            //output something and apply the force to the enemy
            enemyRb.AddForce(awayFromPlayer * flingStrength, ForceMode.Impulse);
        }      
    }

    //method to kill the player if health is below 0
    public void CheckHealth()
    {
        if (playerHealth <= 0)
        {
            gameObject.transform.SetPositionAndRotation(new Vector3(0,10,0), transform.rotation);
        }
    }

    //method to take player damage
    public int TakeDamage(int damage)                   //gets given damage from other methods
    {
        playerHealth = playerHealth - damage;           //reduce the player health
        CheckHealth();                                  //calls the check health method
        Debug.Log("Health is: " + playerHealth);        //displays player health (unless player dies in checkHealth method
        return damage;                                  //return the damage (not used but we need to return something!)
    }

    public void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("PowerUp") )
        {
            hasPowerUp = true;                  //set the powerup trigger for the collission method
            Destroy(other.gameObject);          //destroy the power up
        }

        if (other.CompareTag("Health"))
        {
            playerHealth += 5;                          //add 5 to player's health
            Debug.Log("Health = " + playerHealth);      //output current health
            Destroy(other.gameObject);                  //destroy the health object
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Enemy" && hasPowerUp)
        {
            //get the rigidbody of the enemy so we can fling it
            Rigidbody enemyRb = collision.gameObject.GetComponent<Rigidbody>();

            //set the force that we will fling it
            Vector3 awayFromPlayer = (collision.gameObject.transform.position - transform.position);

            //output something and apply the force to the enemy
            enemyRb.AddForce(awayFromPlayer * powerUpStrength, ForceMode.Impulse);
        }
    }

    public void SpecialMove()
    {
        Debug.Log("SM");
        specialMove = true;
        
        //jump player up but only on Y pos
        playerRb.AddForce(jump * jumpForce, ForceMode.Impulse);

        //freeze X & z to stop momentum
        playerRb.constraints = RigidbodyConstraints.FreezePositionX;
        playerRb.constraints = RigidbodyConstraints.FreezePositionZ;
    }
}

Spawner code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnManagerScript : MonoBehaviour
{
    // vars to store game objects - set in editor
    public GameObject enemyPrefab;
    public GameObject powerUpPrefab;
    public GameObject bombPrefab;
    public GameObject trapPrefab;
    public GameObject healthPrefab;

    private float spawnRange = 9;       //var for co-ordinate rage to spawn objects
    public int waveNumber = 1;          //to keep track of level / number of enemies

    private GameObject player;      //to use for movement direction
    private PlayerController playerController;

    void Start()
    {
        player = GameObject.Find("Player");
        playerController = player.GetComponent<PlayerController>();

        SpawnEnemyWave(waveNumber);       //calls the first wave
        Instantiate(powerUpPrefab, GenerateSpawnPoint(), powerUpPrefab.transform.rotation);         //spawns first powerup
        Debug.Log("Level 0");           //outputs current level
    }

    //method to spawn enemies, bombs and traps
    void SpawnEnemyWave(int enemiesToSpawn)
    {

        Debug.Log("Spawning = " + enemiesToSpawn);

        for (int i = 0; i < enemiesToSpawn; i++)
        {
            Instantiate(enemyPrefab, GenerateSpawnPoint(), enemyPrefab.transform.rotation);
        }

        //spawn bombs if on level 4
        if (enemiesToSpawn > 3)
        {
            for (int i = 0; i < enemiesToSpawn-2; i++)
            {
                Instantiate(bombPrefab, GenerateBombSpawnPoint(), bombPrefab.transform.rotation);
            }
        }

        //spawn traps if on level 6
        if (enemiesToSpawn > 5)
        {
            //delete existing raps
            Destroy(GameObject.FindWithTag("Trap"));

            //create new traps
            for (int i = 0; i < enemiesToSpawn-3; i++)
            {
                Instantiate(trapPrefab, GenerateTrapSpawnPoint(), trapPrefab.transform.rotation);
            }
        }
    }

    //method to randomize spawn points for enemies
    private Vector3 GenerateSpawnPoint()
    {
        float spawnPosX = Random.Range(-spawnRange, spawnRange);
        float spawnPosZ = Random.Range(-spawnRange, spawnRange);
        Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);

        return randomPos;
    }

    //method to randomize spawn points for bombs
    private Vector3 GenerateBombSpawnPoint()
    {
        float spawnPosX = Random.Range(-spawnRange, spawnRange);
        float spawnPosZ = Random.Range(-spawnRange, spawnRange);
        Vector3 randomPos = new Vector3(spawnPosX, 10, spawnPosZ);

        return randomPos;
    }

    //method to randomize spawn points for traps
    private Vector3 GenerateTrapSpawnPoint()
    {
        float spawnPosX = Random.Range(-spawnRange, spawnRange);
        float spawnPosZ = Random.Range(-spawnRange, spawnRange);
        Vector3 randomPos = new Vector3(spawnPosX, -0.8f, spawnPosZ);

        return randomPos;
    }

    void FixedUpdate()
    {
        //enemyCount = FindObjectsOfType<EnemyClass>().Length; < Depreciated
        var enemyCount = FindObjectsByType<EnemyClass>(FindObjectsSortMode.None);
        
        Debug.Log("enemies = " + enemyCount.Length);

        if (enemyCount.Length == 0  && playerController.smashCam == false)
        {
            waveNumber++;
            Debug.Log("Wave Num = " + waveNumber);
            SpawnEnemyWave(waveNumber);

            Instantiate(powerUpPrefab, GenerateSpawnPoint(), powerUpPrefab.transform.rotation);

            if (waveNumber > 3)
            {
                Instantiate(healthPrefab, GenerateSpawnPoint(), powerUpPrefab.transform.rotation);
            }
        }
    }
}

Enemy code:

public class EnemyClass : MonoBehaviour { private Rigidbody enemyRb; //to use for movement private GameObject player; //to use for movement direction private PlayerController playerController;

public int enemyHealth = 10;    //so we can damage the enemey
public int enemySpeed = 3;     //speed of enemy

void Start()
{
    enemyRb = GetComponent<Rigidbody>();
    player = GameObject.Find("Player");
    playerController = player.GetComponent<PlayerController>();
}

void Update()
{
    EnemyMove();            //move the enemey
    CheckHealth();          //check health in case of death
    CheckFallen();          //check if they've fallen off the edge
}

public void EnemyMove()
{
    if (playerController.specialMove == false)
    {
        enemyRb.constraints = RigidbodyConstraints.None;        //unfreeze constraints
        
        //move player
        Vector3 lookDirection = (player.transform.position - transform.position).normalized;
        enemyRb.AddForce(lookDirection * enemySpeed);
    }
    else
    {
        enemyRb.constraints = RigidbodyConstraints.FreezeAll;
    }
}

public void CheckHealth()
{
    if (enemyHealth <= 0)
    {
        Debug.Log("Enemy died");
        Destroy(gameObject);    
    }
}

public void CheckFallen()
{
    if (transform.position.y < -10)         //if they have fallen -10 in y pos destroy it
    {
        Destroy(gameObject);
    }
    if (transform.position.x < -30 || transform.position.x > 30 || transform.position.z > 30 || transform.position.z > 30)         //if they have gone past 100 in x pos destroy it
    {
        Destroy(gameObject);
    }
}

//method to take damage
public int TakeDamage(int damage)
{
    enemyHealth = enemyHealth - damage;
    Debug.Log("Enemy hit by bomb");
    CheckHealth();
    return damage;
}

}

enter image description here

Somehow it's being set to 0 before anything is being destroyed to reset it

Thanks for any help


Solution

  • your problem is here

    if (Input.GetKeyDown(KeyCode.Space)) 
    {
        SpecialMove();
    }
    

    This sets specialMove to true the frame space is pressed, but doesn't ever set it back to false.

    This means that this code here

    if(specialMove == true && gameObject.transform.position.y > 0.8)
    {
        StartCoroutine(SpecialMoveDelay());
    } 
    

    will be called every frame your position is above 0.8 (instead of just the first frame), creating many many delayed spawns.

    This effect won't be instant, but each frame will begin a new coroutine execution.

    To fix this, add a second case to your if statement.

    if (Input.GetKeyDown(KeyCode.Space)) 
    {
        SpecialMove();
    }
    else
    {
        specialMove = false;
    }