Day 31 of 100 Days of VR: Stopping The Time Score System When The Game Is Over

Josh Unity VR Development Leave a Comment

Welcome back to day 31!

Yesterday, we created a simple time-based system that runs on our screen when the game plays. We only increased the time, but we never pause it when the game is over.

Today, we’re going to:

  1. Pause our time after we win or lose
  2. Stop our player’s shooting animation and sound effects when the game is over

Today’s going to be a relatively short day, but let’s get to it, shall we?

Step 1: Stopping the Timer at Game Over

To stop our game from showing updates to the timer, we need to have 2 things:

  1. Code to stop our ScoreManager from updating the string
  2. Some way for our ScoreManager to know that the game is over. Who’s in charge of that? Our GameManager!

Step 1.1: Stopping ScoreManager from Updating Code

The first thing we need to do is to get our ScoreManager to stop updating the time when the game is over.

Here are the changes we did:

using UnityEngine;
using UnityEngine.UI;

public class ScoreManager : MonoBehaviour
{
    public Text Score;

    private string _time;
    private bool _gameOver;

    void Start ()
    {
        _time = "";
        _gameOver = false;
    }

    void Update()
    {
        if (!_gameOver)
        {
            UpdateTime();
        }
    }

    private void UpdateTime()
    {
        int minutes = Mathf.FloorToInt(Time.time / 60);
        int seconds = Mathf.FloorToInt(Time.time % 60);
        float miliseconds = Time.time * 100;
        miliseconds = miliseconds % 100;
        _time = string.Format("{0:0}:{1:00}:{2:00}", minutes, seconds, miliseconds);
        Score.text = _time;
    }

    public void GameOver()
    {
        _gameOver = true;
    }
}

 New Variable Used

The goal is for us to stop the call to UpdateTime() to do that, we introduced a new bool called _gameOver to indicate that.

Walking Through the Code

The change that we’re making here is straightforward:

  1. In Start() we set instantiate _gameOver
  2. In Update() we update our time if we’re still playing otherwise we don’t do anything
  3. We created a public GameOver() so that something else (our GameManager) can tell us that the game is over and we can stop updating our score

Step 1.2: Updating our GameManager to call GameOver() in ScoreManager

Now that we have a way to stop our timer from going, the next thing we’re going to have to do is call it.

Luckily for us, we have a GameManager script that takes care of everything regarding the state of the game. Let’s add our code there!

Here are our changes:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    public Animator VictoryAnimator;

    private GameObject _player;
    private SpawnManager _spawnManager;
    private ScoreManager _scoreManager;

    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _spawnManager = GetComponentInChildren<SpawnManager>();
        _scoreManager = GetComponent<ScoreManager>();
    }

    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
        DisableGame();
        _spawnManager.DisableAllEnemies();
    }

    public void Victory()
    {
        VictoryAnimator.SetBool("IsGameOver", true);
        DisableGame();
    }

    private void DisableGame()
    {
        _player.GetComponent<PlayerController>().enabled = false;
        _player.GetComponentInChildren<MouseCameraContoller>().enabled = false;
        _player.GetComponentInChildren<PlayerShootingController>().enabled = false;
        Cursor.lockState = CursorLockMode.None;
        _scoreManager.GameOver();
    }
}

New Variable Used

Like what we said earlier, we introduced our ScoreManager object, _scoreManager so we can call GameOver().

Walking Through the Code

There aren’t any major or surprising changes that were done:

  1. In Start() we got our _scoreManager that was attached to our GameManager game object
  2. In DisableGame(), where we go and disable everything, we call GameOver() from our _scoreManager to pause the timer.

Step 2: Fixing the Player State After the Game Ends

The next thing we’re going to do is fix a problem that I’ve been talking about for the past 2 days or so. When we win, our player, specifically, the gun, is stuck in a perpetual shooting state and the shooting sound effect will keep playing until the game is over.

We’re going to fix that.

Most of the code is already in place, we just need to:

  1. Create a new state transition to stop shooting in our gun Animator
  2. Create a new game over function for our gun to stop everything when the game is over
  3. Call the game over function from our GameManager

Step 2.1: Create Transition to Stop Shooting

If you might recall from Day 16 where we added our weapon, we created a state machine that would transition from our shooting animation to our idle information (and vice-versa) whenever we activate our Shoot Trigger

We’re going to make a new GameOver state and create a new transition from Any State to GameOver.

Here’s what we’re going to do:

  1. Select Player > Main Camera > MachineGun_00 and open the Animator tab (Windows > Animator)
  2. Create a new parameter, it’ll be a trigger and call it GameOver
  3. Create a new state called GameOver
  4. Create a new transition from Any State to
  5. Select that transition and set the transition condition to be when GameOver is triggered

When you’re done, we should have something like this:

At this point, you might be wondering, if we don’t have any clip for our GameOver state, why not just re-use our default state?

The answer to that is, because this is our finished state where we’re done. We don’t want to accidentally trigger a new transition and move us from the default state to another state.

Step 2.2: Add GameOver in PlayerShootingController

Now that we have our new transition, it’s time to use it!

As we might recall, the script that controls our player’s shooting is PlayerShootingController.

We’re going to go in and make some slight changes:

using UnityEngine;
using System.Collections;

public class PlayerShootingController : MonoBehaviour
{
    public float Range = 100;
    public float ShootingDelay = 0.1f;
    public AudioClip ShotSfxClips;
    public Transform GunEndPoint;
    public float MaxAmmo = 10f;

    private Camera _camera;
    private ParticleSystem _particle;
    private LayerMask _shootableMask;
    private float _timer;
    private AudioSource _audioSource;
    private Animator _animator;
    private bool _isShooting;
    private bool _isReloading;
    private LineRenderer _lineRenderer;
    private float _currentAmmo;
    private ScreenManager _screenManager;


    void Start () {
        _camera = Camera.main;
        _particle = GetComponentInChildren<ParticleSystem>();
        Cursor.lockState = CursorLockMode.Locked;
        _shootableMask = LayerMask.GetMask("Shootable");
        _timer = 0;
        SetupSound();
        _animator = GetComponent<Animator>();
        _isShooting = false;
        _isReloading = false;
        _lineRenderer = GetComponent<LineRenderer>();
        _currentAmmo = MaxAmmo;
        _screenManager = GameObject.FindWithTag("ScreenManager").GetComponent<ScreenManager>();
    }
    
    void Update ()
    {
        _timer += Time.deltaTime;

        // Create a vector at the center of our camera's viewport
        Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));

        // Draw a line in the Scene View  from the point lineOrigin in the direction of fpsCam.transform.forward * weaponRange, using the color green
        Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);

        if (Input.GetMouseButton(0) && _timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
        {
            Shoot();
            if (!_isShooting)
            {
                TriggerShootingAnimation();
            }
        }
        else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
        {
            StopShooting();
            if (_isShooting)
            {
                TriggerShootingAnimation();
            }
        }

        if (Input.GetKeyDown(KeyCode.R))
        {
            StartReloading();
        }
    }

    private void StartReloading()
    {
        _animator.SetTrigger("DoReload");
        StopShooting();
        _isShooting = false;
        _isReloading = true;
    }

    private void TriggerShootingAnimation()
    {
        _isShooting = !_isShooting;
        _animator.SetTrigger("Shoot");
    }

    private void StopShooting()
    {
        _audioSource.Stop();
        _particle.Stop();
    }

    private void Shoot()
    {
        _timer = 0;
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit = new RaycastHit();
        _audioSource.Play();
        _particle.Play();
        _currentAmmo--;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);

        _lineRenderer.SetPosition(0, GunEndPoint.position);
        StartCoroutine(FireLine());

        if (Physics.Raycast(ray, out hit, Range, _shootableMask))
        {
            print("hit " + hit.collider.gameObject);
            _lineRenderer.SetPosition(1, hit.point);
            EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
            EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
            print(health);
            if (enemyMovement != null)
            {
                enemyMovement.KnockBack();
            }
            if (health != null)
            {
                health.TakeDamage(1);
            }
        }
        else
        {
            _lineRenderer.SetPosition(1, ray.GetPoint(Range));
        }
    }

    private IEnumerator FireLine()
    {
        _lineRenderer.enabled = true;

        yield return ShootingDelay - 0.05f;

        _lineRenderer.enabled = false;
    }

    // called from the animation finished
    public void ReloadFinish()
    {
        _isReloading = false;
        _currentAmmo = MaxAmmo;
        _screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
    }

    private void SetupSound()
    {
        _audioSource = gameObject.AddComponent<AudioSource>();
        _audioSource.volume = 0.2f;
        _audioSource.clip = ShotSfxClips;
    }

    public void GameOver()
    {
        _animator.SetTrigger("GameOver");
        StopShooting();
    }
}

New Variabled Used

None

Walking Through the Code

For our code change this time, we only added a new public function: GameOver().

We do 2 things:

  1. We call our GameOver trigger to stop our shooting state
  2. We re-use StopShooting() to stop the particle and sound effects.

GameOver() is a public function so that means it can be called from somewhere external, in this case, our GameManager script.

Step 2.3: Calling GameOver() from PlayerShootingController in GameManager

Now that we have the code to stop our player from shooting when the game is over, it’s time to use it in our GameManager.

Here are our changes:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public Animator GameOverAnimator;
    public Animator VictoryAnimator;

    private GameObject _player;
    private SpawnManager _spawnManager;
    private ScoreManager _scoreManager;

    void Start()
    {
        _player = GameObject.FindGameObjectWithTag("Player");
        _spawnManager = GetComponentInChildren<SpawnManager>();
        _scoreManager = GetComponent<ScoreManager>();
    }

    public void GameOver()
    {
        GameOverAnimator.SetBool("IsGameOver", true);
        DisableGame();
        _spawnManager.DisableAllEnemies();
    }

    public void Victory()
    {
        VictoryAnimator.SetBool("IsGameOver", true);
        DisableGame();
    }

    private void DisableGame()
    {
        _player.GetComponent<PlayerController>().enabled = false;
        _player.GetComponentInChildren<MouseCameraContoller>().enabled = false;

        PlayerShootingController shootingController = _player.GetComponentInChildren<PlayerShootingController>();
        shootingController.GameOver();
        shootingController.enabled = false;

        Cursor.lockState = CursorLockMode.None;
        _scoreManager.GameOver();
    }
}

New Variables Used

None

Walking Through the Code

Everything is already in place, the only thing we want to change is inside DisableGame().

Specifically, instead of just disabling our PlayerShootingController that we already had, we made sure that we called GameOver() and then we disable it to prevent any new user inputs/

Conclusion

With all of this in place, our game is 1 step closer to completion!

Today we made it so that we would stop counting our time when the game is over and we solved an annoying bug that would have the player constantly firing when the game is over.

That’s it for day 31! I think for day 32, we’ll look into creating a high score system and displaying that in our game!

Look forward to it!

Day 30 | 100 Days of VR | Day 32

Home

Subscribe To Our Weekly Newsletter!
Like these coding articles? Join my mailing list for the latest updates and influence the code that I write!
We hate spam. Your email address will not be sold or shared with anyone else.

Leave a Reply

Your email address will not be published. Required fields are marked *