Day 41 of 100 Days of VR: Creating a VR First Person Shooter IV – Game Over Panels

Josh Unity VR Development 1 Comment

We’re so close to finishing our First Person Shooter game! Today, it’s day 41 and today we’re going to put in the final finishing pieces of our game together.

Today our game plan is to:

  1. Put our Game Over and Victory panels back into the game
  2. Fixing our Animation
  3. Connect our UI with the rest of our script

After today, we will have everything our original simple FPS shooter has in VR!

Let’s get started!

Step 1: Getting our End State Panels to Show Up

The first thing we need to do is that we need to get our victory and game over panels to show up.

Before we had the panel drop down on our screen, however, because we’re in VR now, that wouldn’t be good for the users.

Instead, what we want to do is have the panel show up in front of our user, but as we turn around, the UI wouldn’t follow us.

I’m going to keep the panel the same as it was, we just need to change the scale so that the panel would fit on our screen.

I’ve experimented with what scale I want the panels to be by manually moving our Game Over panel (which conveniently also had a Canvas component) to in front of the player and playing around with the scale until we found something good.

Here’s what I did (optional):

  1. In Game Over under our HUD, I changed the Canvas to be World Space
  2. I manually made the rotation to the same as our Player game object
  3. Then I played with the scale and positioning a bit to get something that looks good. Don’t worry too much about positioning, we’ll write some code later that’ll make this appear perfectly every time. My ending scale value was (0.01, 0.01, 0.01)

Here’s what we see:

So great, now we have our Scale, it’s time to use our panel!

Step 1.1: Creating the Prefabs

At this point on, unless stated otherwise, anything we do with the Game Over panel, we do the same thing with the Victory panel.

So now, we have a general sense of the size we want our panels to be, Scale (0.01, 0.01, 0.01), it’s time for us to use it.

The best solution for us to create this is for us to instantiate our panels.

If you recall, if we were to call Unity’s instantiate function, we can create a new Game Object with our own pre-determined directions… like in front of our player! To do that, we need a prefab!

  1. Select GameOver in HUD
  2. Change the Scale to be (0.01, 0.01, 0.01)
  3. Drag GameOver into our Prefabs
  4. Delete GameOver from HUD

After we have done this with both GameOver and Victory, we can delete HUD. We don’t need it anymore.

Next, we need somewhere to Instantiate our prefabs.  Now where in our knows whether we won or lost… Right! In our Game Manager script attached to our Game Manager game object!

Step 1.2: Writing the Code to Show our Panels

Luckily our Game Manager script already takes in our game panels as objects, which means that we can re-purpose our existing script to accomplish what we want to do.

If we recall what we did with our original GameManager script, we take our Panels that we made invisible by changing the alpha of it to be 0.

However, because of our new limitations, we can’t just hide these panels in our overlay anymore, they have to be in World Space now.

To do this, we’re going to take in a prefab of our panels and then Instantiate them in front of our player when we need to display them.

Luckily for us, when we wrote our GameManager code, I want to say we organized the code well. We call a function to show both our game over and victory panel. We just have to re-purpose that function a bit to meet our needs.

Here are our changes to GameManager:

using UnityEngine;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    public GameObject GameOverPanel;
    public GameObject VictoryPanel;

    private GameObject _player;
    private SpawnManager _spawnManager;
    private ScoreManager _scoreManager;
    private SaveManager _saveManager;
    private Camera _camera;

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

    public void GameOver()
    {
        DisableGame();
        _spawnManager.DisableAllEnemies();
        ShowPanel(GameOverPanel, false);
    }

    public void Victory()
    {
        DisableGame();

        if (_scoreManager.GetScore() < _saveManager.LoadHighScore())
        {
            _saveManager.SaveHighScore(_scoreManager.GetScore());
        }
        ShowPanel(VictoryPanel, true);
    }

    private void ShowPanel(GameObject panel, bool didWin)
    {
        Vector3 frontOfPlayer = _camera.transform.position + _camera.transform.forward * 3;
        Quaternion playerRotation = _camera.transform.rotation;
        GameObject newPanel = Instantiate(panel, frontOfPlayer, playerRotation);
        newPanel.GetComponent<Animator>().SetBool("IsGameOver", true);
        newPanel.GetComponent<GameOverUIManager>().SetHighScoreText(ScoreManager.GetScoreFormatting(_saveManager.LoadHighScore()), didWin);
    }

    private void DisableGame()
    {
        PlayerShootingController shootingController = _player.GetComponentInChildren<PlayerShootingController>();
        shootingController.GameOver();
        shootingController.enabled = false;

        _scoreManager.GameOver();
    }
}

New Variable Used

In our code, we introduced a new variable, our Main Camera: _camera, the purpose of this variable is for us to get the direction the player is facing so we can show our panels in front of the player.

Walking Through the Code

We didn’t really have to change much of the code.

  1. In Start(), we initialized some our _camera variable.
  2. In ShowPanel(), we don’t just play the animation of our panel to have it show up. Now, we want to create a new instance of it that is directly in front of the player. We get this information from our camera and we move the panel in front of our player. It’s important to note is that our rotation has to be exactly the same as the player’s otherwise, the panel will be facing the wrong direction.
  3. Besides, that in DisableGame(), we did some cleanup work on the classes that we got rid of. The really great thing is how easy it is to do what we want with so little code change!

Step 1.3: Adding the Prefab to the GameManager script

However, before we’re done with this, we need to add panel prefabs to the GameManager script so we know which panel we should show.

To do this:

  1. Go to the GameManager game object in the hierarchy.
  2. In the GameManager script, we have 2 open slots: Game Over Panel and Victory Panel, what we want to do now, is that our GameOver and Victory panel to the appropriate slot.

We should have something like this when we’re done:

Now we play our game and when we win or lose, we experience a new problem: the panels don’t show up!

Step 1.4: Getting our Animation to Appear In Front of the Player

If we look at our hierarchy, we can see that our panel does get created, but when we see where it is:

We find that it’s in some far-off location.

Why does this happen? After some investigation, it turns out that this happened, because of our animation.

Specifically, it’s because of our animation that changed our Z anchor value. This became apparent when looking at the Position of our Panel. Only the Z position is 0 while everything else has a value.

What did we change when we were working on a flat UI again? That’s right, our Z position in our animation!

For simplicity, let’s just get rid of our position transformation.

What I found out is that if we want to change our animation clips, we have to first drag a game object that uses it to our hierarchy.

  1. In Assets > Prefab, drag our GameOver prefab into our hierarchy
  2. With our GameOver game object selected, go to the Animation Tab (Window > Animation)
  3. Delete our Z-anchor position animation

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

And the best part is because both our game over and victory panels use this same Game Over animation clip, both panels would receive this change.

  1. Remove our GameOver game object from our hierarchy

Now play the game and if we win or lose, we’ll have something like this:

So that’s great, however, our high score is missing! If we look at our error log, it’ll tell us where the problem is. It’s inside SetHighScoreText() that is part of our GameOverUIManager script that we attached to each of our panels.

After looking at the problem, it appears that we’re crashing when we’re trying to print our text in SetHighScoreText().

For some reason, our objects in Start() isn’t being instantiated.

To test this theory, I added a print statement into my Start() to see what happens. It turns out that the ordering of our function calls is:

  1. SetHighScoreText()
  2. Start()

I looked into this problem of instantiated prefabs not running start function and found out that Start() is called by the first Update().

For us, after we create our panel game object, we’re immediately calling our function. We don’t even have time to call Start().

There were a couple of ways we could have solved this:

  1. Instantiate our Text when we call SetHighScoreText()
  2. Change Start() to Awake()

From looking at our StackOverflow answer, the correct way of solving this problem is to not instantiate our game objects in Start(), rather we should do it inside Awake() which gets called immediately after our game object gets created.

Here’s our code now for GameOverUIManager:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameOverUIManager : MonoBehaviour
{
    private Button _button;
    private Text _text;

	void Awake () {
        _button = GetComponentInChildren<Button>();
        _button.onClick.AddListener(ClickPlayAgain);

	    _text = GetComponentInChildren<Text>();
        print("text in awake: " + _text.text);
	}

    public void ClickPlayAgain()
    {
        SceneManager.LoadScene("Main");
    }

    public void SetHighScoreText(string score, bool didWin)
    {
        print("before text: " + _text.text);
        if (didWin)
        {
            _text.text = "You Win! \n" +
                         "High Score: " + score;
        }
        else
        {
            _text.text = "Game Over! \n" +
                         "High Score: " + score;
        }
        print("after text: " + _text.text);
    }
}

Walking Through the Code

The only change we made here is that we changed our initializing code from Start() to Awake().

By doing this, change we actually initiate our code and behold what we get now when we play the game:

Step 1.5: Interacting with our Panels

Luckily for us, interacting with our UI is VR is not any different compared to what we’re doing now.

As of now, our game is already complete and we can shoot the GameOver/Victory panel to restart the game and the code would work flawlessly.

However, to re-iterate on what needs to be done:

  1. We need to go to the prefabs of our panels in Assets > Prefabs
  2. We need a Graphics Raycaster on our canvas so we can detect when the user is interacting with our UI
  3. Finally, we have 2 options, in our code, create an onClick command to call a specific function everytime the click action has been done. The other option is to do the exact same thing, except in the Button script component

With all of this done and plugged in (it already should be), we now have a full and complete game!

Conclusion

Phew, it’s been a long journey, but we finally did it! 41 days and we now have a fully functional game in VR!

Today we integrated everything that was needed for the UI for our game. Specifically, we made it so that we can display the GameOver/Victory UI in front of the player wherever they are and then interact with the panel when they’re done.

I think to start from Day 42, I’ll be looking to see what it takes to implement an actual arm device to move our weapon in the screen as opposed to using the cursor in the center as a crosshair.

It’s starting to get more interesting, I’ll see all tomorrow!

Day 40 | 100 Days of VR | Day 42

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.

Comments 1

Leave a Reply

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