Ending the game

Tutorial

·

Beginner

·

+10XP

·

30 mins

·

(23)

Unity Technologies

Ending the game

In this tutorial, you’ll define the win and lose conditions for your game and set up UI elements to display when the game ends.

1. Overview

In this tutorial, you’ll define the endpoint of your game by setting up both win and lose conditions. This creates a complete game loop, giving the player a clear objective and a sense of progression. The win condition will trigger when the player character successfully escapes the level by reaching the exit room. The lose condition will activate if the player character is caught or seen by an enemy.

By completing this tutorial, you’ll close the gameplay loop, introducing risk, reward, and finality to your level. This structure not only enhances the player experience, but it also sets the foundation for adding additional polish like UI feedback.

2. Create a GameEnding Trigger

Now that your game needs an endpoint, you’ll set up a system to detect when the player character escapes the haunted house. You will create a Collider and configure it as a Trigger to designate the finish zone. When the player character walks through this area, the game will recognize it and be ready to display the win screen.

1. Create the GameEnding GameObject:

  • Right-click in the Hierarchy window and select Create Empty.
  • Rename the empty GameObject “GameEnding”.
  • In the Inspector window, select the cube icon in the upper-left corner and add a color label to the GameEnding GameObject so it's easier to see in the Scene view, just like you did for the waypoints.

2. Move the GameEnding GameObject to the exit area:

  • Using the Move tool, click and drag the GameEnding GameObject to the exit point of your level (where the player character needs to reach to finish the game.) Even though the GameEnding GameObject is an empty GameObject, make sure to place it in the center of the room.

Note: If you’re using one of the premade levels, we’ve included a designated Finish Room inside each of them; this is our recommended exit location. To find it, use the foldout (triangle) to expand the Level_[Variation] GameObject, locate the Finish Room prefab inside the Finish Room GameObject, press F to focus on it in the Scene view, and place your GameEnding GameObject there!

3. Add a Box Collider component Trigger:

  • In the Inspector window, with the GameEnding GameObject selected, select the Add Component button.
  • Search for and add the Box Collider component (not 2D).
  • In the Box Collider component, enable Is Trigger.

Now the player character will be able to pass through this space, and the game will recognize when this happens.

4. Resize the collider to match the exit:

  • Select the Edit Collider button in the Box Collider component.
  • Use the handles in the Scene view to stretch and position the Box Collider component to fill the exit area, ensuring that the Player GameObject will trigger it once it arrives in the final room.

Note: For better visibility, you can switch between top-down and side views by clicking the Y, X and Z axis on the Scene Gizmo, located at the top-right corner of the Scene view.

  • When you’re done, select the Edit Collider button again to exit edit mode.

Your trigger is ready! In the next step, you’ll set up a simple UI to give feedback once the player character is caught or manages to escape.

3. Set up the UI with UI Toolkit

Unity’s UI Toolkit is a powerful, modern alternative to the older Unity UI (uGUI) system. Designed with performance and scalability in mind, UI Toolkit is built to support complex interfaces in a more efficient way. It’s based on familiar web technologies like XML/HTML and CSS, which means it might feel a bit different if you're used to traditional uGUI workflows, but it’s absolutely worth learning, especially with Unity 6 and beyond prioritizing this system.

In this tutorial, you’ll use UI Toolkit to display a simple image when the player wins or loses the game. We’ve already provided a setup UI Document named MainUI to keep things simple, but we encourage you to explore Unity’s UI Toolkit documentation and the Getting started with UI toolkit tutorial to deepen your understanding.

1. Create a UI Document:

  • Right-click in the Hierarchy window and select UI Toolkit > UI Document.
  • Rename the new GameObject “FaderCanvas”.

Note: When you create a UI Document, Unity also automatically creates a Panel Settings asset and a UI Toolkit folder in the Project window. The UI Document GameObject is where the layout lives, while the Panel Settings asset defines how the UI is rendered on screen.

2. Assign the provided UI Document:

  • In the Hierarchy window, select the FaderCanvas GameObject.
  • In the Inspector window, locate the UI Document component.
  • Select the Source Asset picker (⊙) and assign the MainUI.uxml asset.

3. (Optional) Explore the UI Document:

  • If you’d like to see how the UI is structured, double-click the MainUI.uxml file to open it in the UI Builder window.
  • If you look in the Hierarchy window, you’ll notice the MainUI.uxml file contains two main Visual Elements: one for the Win screen and one for the Lose screen.
  • Currently both of these visual elements are disabled, that’s why they aren’t visible in the UI Builder Viewport pane. You’ll activate the correct one in the next step via code, depending on the game outcome.

You’ve now set up your end-game UI using UI Toolkit. In the next step, you’ll connect the GameEnding trigger to a script that controls when the game ends and which UI screen is shown.

4. Create the GameEnding script

Now that you've set up the GameEnding GameObject and the UI to show win/lose states, you’ll implement a basic game loop: a structure that governs the flow of gameplay. In most games, the loop consists of the player starting a level, playing until a win or lose condition is met, then either restarting or moving to another level.

In this step, you’ll add a script that defines your game loop: the game will restart the level when the lose condition is triggered and end the game once the win condition is triggered.

To make things easier, we've provided the complete script for you. If you're new to coding, this means you can keep moving forward with your project without getting stuck.

If you're curious to understand how the script works, check out Step 7 , where we explain the key parts in more detail. If you already have some experience, you’re welcome to write your own version of the GameEnding script and customize it as you like!

1. Create a new script:

  • In the Project window, navigate to _3DStealthGame > Scripts.
  • Right-click on the Scripts folder.
  • Select Create > MonoBehaviour Script.
  • Rename the script “GameEnding” and press Enter.

2. Add the script to your GameEnding trigger:

  • In the Hierarchy window, select the GameEnding GameObject.
  • In the Inspector window, select the Add Component button.
  • Search for “GameEnding” and press Enter.

3. Open the script:

  • Double-click the newly created GameEnding script.
  • This will open the script in the code editor or IDE you've set up (such as Visual Studio).

4. Copy the provided code:

  • Once the script is open, delete any pre-filled content.
  • Copy and paste the following code and save the script:

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;

public class GameEnding : MonoBehaviour
{
public float fadeDuration = 1f;
public float displayImageDuration = 1f;
public GameObject player;
public UIDocument uiDocument;

bool m_IsPlayerAtExit;
bool m_IsPlayerCaught;
float m_Timer;


private VisualElement m_EndScreen;
private VisualElement m_CaughtScreen;

void Start()
{
m_EndScreen = uiDocument.rootVisualElement.Q<VisualElement>("EndScreen");
m_CaughtScreen = uiDocument.rootVisualElement.Q<VisualElement>("CaughtScreen");
}

void OnTriggerEnter (Collider other)
{
if (other.gameObject == player)
{
m_IsPlayerAtExit = true;
}
}

public void CaughtPlayer ()
{
m_IsPlayerCaught = true;
}

void Update ()
{
if (m_IsPlayerAtExit)
{
EndLevel (m_EndScreen, false);
}
else if (m_IsPlayerCaught)
{
EndLevel (m_CaughtScreen, true);
}
}

void EndLevel (VisualElement element, bool doRestart)
{
m_Timer += Time.deltaTime;
element.style.opacity = m_Timer / fadeDuration;

if (m_Timer > fadeDuration + displayImageDuration)
{
if (doRestart)
{
SceneManager.LoadScene ("Main");
}
else
{
Application.Quit ();
Time.timeScale = 0;
}
}
}
}

At this point, your GameEnding script has been assigned to the GameEnding GameObject. However, if you test the scene now, you’ll see some errors in the Console window. In the next step, you’ll assign any missing references to make sure everything works smoothly.

5. Assign GameEnding references in the Editor

In your GameEnding script, you created several public variables. These are meant to be configured in the Unity Editor; that way, you can easily customize the behavior of your game without modifying the code.

Now it’s time to assign those variables so the script can do its job properly and the game can detect when the player wins or loses and display the appropriate screen.

1. Assign the public variables:

  • In the Hierarchy window, select the GameEnding GameObject to view its components in the Inspector window.
    • Click and drag the Player GameObject from the Hierarchy window onto the Player property box in the GameEnding component.
    • Click and drag the FaderCanvas GameObject from the Hierarchy window onto the UI Document property box in the GameEnding component.

2. Test the script behavior:

  • Select the Play button to enter Play mode.
  • Move the Player GameObject into the end game zone. You should see the win screen appear!

Right now, if you walk into an enemy, nothing will happen; that’s because the enemies aren’t connected to the GameEnding script yet. In the next step, you’ll fix that by calling the function you created in the Observer script.

6. Trigger game over from the Observer script

You might have noticed that the GameEnding script contains a public function called CaughtPlayer(). We’ve made it public so it can be accessed from other scripts; this helps centralize all the logic for ending the game in one place.

Right now, when an enemy spots the player character, nothing really happens; just a message appears in the Console window. Let’s change that. Instead of logging a message, you’ll call the CaughtPlayer() function directly from the Observer script. This way, the game ends properly and the correct UI (the lose screen) is activated when the player character is caught.

1. Open the Observer script:

  • In the Project window, locate and double-click the Observer script to open it in your code editor.

2. Add a reference to GameEnding:

  • Just after the class declaration at the top of the script, add the following line of code:

public GameEnding gameEnding;

This creates a public variable that you can assign in the Inspector window to connect the Observer script to the GameEnding script.

3. Modify the Player detection code:

  • In the Update method, find the if statement that checks if the raycast hit the Player GameObject:

if (raycastHit.collider.transform == player)
{
Debug.Log("Player was caught!");
}

  • Replace the Debug.Log line with the following line of code:

gameEnding.CaughtPlayer();

Now, when an enemy sees the player character, it will trigger the CaughtPlayer() function and begin the game-ending sequence.

  • Save the changes in your editor.

4. Connect the references in Unity:

  • Go back to the Unity Editor.
  • In the Hierarchy window, use the search box to find all PointOfView GameObjects. Select the first one, then hold Shift and select the last to highlight them all.
  • Clear the search box — the selection will remain.
  • In the Inspector window, locate the Observer component. You’ll now see a new property labeled Game Ending.
  • Drag the GameEnding GameObject from the Hierarchy window into the Game Ending property box.

5. Test your game loop:

  • Press Ctrl+S (macOS: Cmd+S) to save your progress.
  • Enter Play mode, move the player character around and get caught by an enemy.
  • For faster testing, use the Move tool in the Scene view to move the Player GameObject into the final room to trigger the end of the game.

Note: Make sure the UI screens appear when the player character is caught or reaches the exit, and check that the game restarts automatically every time the player character gets caught.

You’ve now completed your full game loop! Your game can detect a win or loss and respond appropriately. In the next tutorial, you’ll take things one step further and add sound effects and background music to enhance the overall experience of your game.

7. Review the GameEnding script

This step is completely optional, but if you’re curious about how the GameEnding script works, here are a few parts of the code you might find especially interesting.

1. Trigger detection:

void OnTriggerEnter (Collider other)
{
if (other.GameObject == player)
{
m_IsPlayerAtExit = true;
}
}

  • Runs when another collider enters this GameObject's trigger collider.
  • Checks if the collider belongs to the Player GameObject.
  • If true, sets m_IsPlayerAtExit to true.

void Update ()
{
if (m_IsPlayerAtExit)
{
EndLevel (m_EndScreen, false);
}
else if (m_IsPlayerCaught)
{
EndLevel (m_CaughtScreen, true);
}
}

  • If the Player GameObject is at the exit: Calls EndLevel() with the m_EndScreen.
  • If the Player GameObject was caught: Calls EndLevel() with m_CaughtScreen.

3. End level logic:

void EndLevel (VisualElement element, bool doRestart)
{
m_Timer += Time.deltaTime;
element.style.opacity = m_Timer / fadeDuration;

if (m_Timer > fadeDuration + displayImageDuration)
{
if (doRestart)
{
SceneManager.LoadScene ("Main");
}
else
{
Application.Quit();
Time.timeScale = 0;
}
}
}

  • Uses m_Timer to store how long the image has stayed on screen.
  • Once the displayImageDuration time has passed, it checks the value of doRestart.
  • If doRestart is true (when the player character is caught), the game restarts.
  • If doRestart is false (when the player character escapes), the application quits and the time stops (everything on scene stops working).

If you’d like to dive deeper into programming, we recommend checking out the Junior Programmer Pathway, where you’ll learn the fundamentals of coding in Unity.

8. Next steps

With these steps completed, you’ve defined the basic game loop for the game.

In the next tutorial, you’ll add the final required feature—audio—to immerse the player in your game.

Complete this tutorial