Yet another "Scene Loader delay" issue :D

Hello, fellas!

Trying to solve my problem for more than an hour, looking the internet and testing stuff, I am finally asking for help!

Initial situation, using Unity 2019.3.0f6 + JB Rider (dunno if it’s related but hey, computer stuff so, who knows ¯\ (ツ) /¯ )

The main problem is that, somehow, can’t use the FindObjectOfType();
Rider say “nope”, red skwigli (?) line under it.
Ok, let’s then give it a try by myself, it’s a good challenge to explore anotther path :

If the health goes down to 0, Die() is called. Die() then call GameOver() which hold a Coroutine, EndGame() which wait for x seconds and then SceneManager.LoadScene(2) is executed.
This is supposed to work, considering that everything happen BEFORE the destruction of the GameObject.

Problem, it is not : even tho the player get destroyed, no scene is loaded afterward.
Digging a bit, learning how to debug, I found out that when I set a breakpoint around Die() well, the result is … _firingCoroutine being null
AND I DON’T GET IT.

Why is this happening now ? Is this related to the Loading process created by the Coroutine ?
The mystery is tearing me appart :grimacing:

image

Player.cs

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

public class Player : MonoBehaviour
{
   
    private Camera _gameCamera;

    [SerializeField] private float moveSpeed = 10.0f;
    [SerializeField] private float padding = 0.5f;
    [SerializeField] private int health = 200;
    
    [SerializeField] private GameObject laserPrefab;
    [SerializeField] private float projectileSpeed = 10f;
    [SerializeField] private float projectileFiringPeriod = 0.01f;

    [SerializeField] private AudioClip deathSfx;
    [SerializeField] private AudioClip shootSoundSfx;
    [SerializeField] [Range(0,1)] private float deathSoundVolume;
    [SerializeField] [Range(0,1)] private float shootSoundVolume;
    
    private Coroutine _firingCoroutine;
    [SerializeField] private float delayInSeconds = 2f;
    
    private float _xMin;
    private float _xMax;
    private float _yMin;
    private float _yMax;

    private void Start()
    {
        _gameCamera = Camera.main;
        SetUpMoveBoundaries();
    }

    private void Update()
    {
        Move();
        Fire();
    }
    
    private void OnTriggerEnter2D(Collider2D other)
    {
        DamageDealer damageDealer = other.gameObject.GetComponent<DamageDealer>();
        if (!damageDealer)
        {
            return; 
        }
        ProcessHit(damageDealer);
    }
    
    private void ProcessHit(DamageDealer damageDealer)
    {
        health -= damageDealer.GetDamage();
        damageDealer.Hit();
        
        if (health <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        GameOver();
        if (_gameCamera == null) return;
        AudioSource.PlayClipAtPoint(deathSfx, _gameCamera.transform.position, deathSoundVolume);
        Destroy(gameObject);
    }
    
    private void GameOver()
    {
        StartCoroutine(EndGame());
    }

    private IEnumerator EndGame()
    {
        yield return new WaitForSeconds(delayInSeconds);
        SceneManager.LoadScene(2);
    }

    private void Fire()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            _firingCoroutine = StartCoroutine(FireContinuously());

        }
        else if (Input.GetButtonUp("Fire1"))
        {
            StopCoroutine(_firingCoroutine);
        }
            
    }

    private void Move()
    {
        // Horizontal movement (position + input on X axis)
        var deltaX = Input.GetAxis("Horizontal") * Time.deltaTime * moveSpeed;
        var newXPos = Mathf.Clamp(transform.position.x + deltaX, _xMin, _xMax);

        // Vertical movement (position + input on Y axis)
        var deltaY = Input.GetAxis("Vertical") * Time.deltaTime * moveSpeed;
        var newYPos = Mathf.Clamp(transform.position.y + deltaY, _yMin, _yMax);

        transform.position = new Vector2(newXPos, newYPos);
    }
    
    private void SetUpMoveBoundaries()
    {
       _xMin = _gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).x + padding;
       _xMax = _gameCamera.ViewportToWorldPoint(new Vector3(1, 0, 0)).x - padding;

       _yMin = _gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).y + padding;
       _yMax = _gameCamera.ViewportToWorldPoint(new Vector3(0, 1, 0)).y - padding;
    }
    
    private IEnumerator FireContinuously()
    {
        while (true)
        {
            GameObject laser = Instantiate(laserPrefab,
                transform.position + new Vector3(0, 0.57f, 0),
                Quaternion.identity) as GameObject;
            laser.GetComponent<Rigidbody2D>().velocity = new Vector2(0, projectileSpeed);
            
            AudioSource.PlayClipAtPoint(shootSoundSfx, _gameCamera.transform.position, shootSoundVolume);
            
            yield return new WaitForSeconds(projectileFiringPeriod);
        }
    }
}

If the script is attached to the player and the player gets destroyed, there is a good chance the player is being destroyed before the code can run. Comment out the Destroy(gameObject) and see what happens.

Since the Coroutine is technically attached to the instance of the Player, deleting the player will, in fact, destroy the coroutine (which is better than C++, which I believe would cause a rather fatal crash!).

There are several ways around this problem. The first is to not actually destroy the player, but instead to set a bool dead and test for this bool before doing any input… that’s actually a lot of refactoring…
Here’s my preferred solution:

public class GameRestarter: MonoBehavior
{
     public void GameOverMan(float delayInSeconds)
    {
         Invoke("EndGame", delayInSeconds);
    }

     void EndGame()
     {
           SceneManager.LoadScene(2);
     }
}

Then in player.cs:

private void GameOver()
{
     FindObjectOfType(GameRestarter).GameOverMan(delayInSeconds);
}

Then attach the GameRestarter to a blank gameobject in the scene.

Privacy & Terms