4 Overview

Today we will work on building Graphical User Interfaces (GUI) and connecting levels together to make a game


4.1 Challenge: Invincibility Power Up

Some games have "power ups" that enable the hero character to ignore and destroy enemies, like the magic star in Mario. To make that effect in a Unity game, you need to make a set of scripts and objects in your game that will:

  • Show a power-up particle effect on the character
  • Show a spectacular death particle effect on the enemy
  • A script on a "power up" item that causes the power up to activated
  • A script on the hero that, once the power up is activated, shows the effect

To start:

  1. Add a new particle effect, as a child to your hero (recommend the "Afterburner" effect)
  2. Add an enemy to the level (sprite animated goblin), with a trigger
  3. Add a power up item to the level (sprite) with a trigger
  4. Add the following scripts:

On the hero character, add the following script as a component:

using UnityEngine;
using System.Collections;

public class PowerUpScript : MonoBehaviour {

    private Transform powerupParticles;

    static bool isActivated = false;

    public static void Activate() {
        isActivated = true;
    }

    public static bool IsActivated() {
        return isActivated;
    }

    // Use this for initialization
    void Start () {
        foreach (ParticleSystem system in this.GetComponentsInChildren<ParticleSystem>()) {
            system.Stop();
        }
    }
    
    // Update is called once per frame
    void Update () {
        if (isActivated) {
            foreach (ParticleSystem system in this.GetComponentsInChildren<ParticleSystem>()) {
                system.Play();
            }
        }
    }
}

On the power-up item, add the following script as a component:

using UnityEngine;
using System.Collections;

public class PowerScript : MonoBehaviour {

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }

    void OnTriggerEnter2D(Collider2D other) {

        Destroy (this.gameObject);

        PowerUpScript.Activate();

    }
}

On the enemy, add the following script as a component:

using UnityEngine;
using System.Collections;

public class DeadlyScript : MonoBehaviour 
{

    // Use this for initialization
    void Start () {
        foreach (ParticleSystem system in this.GetComponentsInChildren<ParticleSystem>()) {
            system.Stop();
        }
    }
    
    // Update is called once per frame
    void Update () {
    
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Player")
        {
            if(PowerUpScript.IsActivated()) 
            {
                Die ();
            } else 
            {
                Application.LoadLevel(Application.loadedLevelName);
            }
        }
    }

    void Die() {
        this.GetComponent<SpriteRenderer> ().enabled = false;

        foreach (ParticleSystem system in this.GetComponentsInChildren<ParticleSystem>()) {
            system.Play();
        }
    }
}

4.1: Graphical User Interface - Text

The Graphical User Interface (or "GUI" - pronounced "goo-ee") is the layer of text and images drawn over the scene that adds relevant information to the user's view. It is used to draw things like the total number of points and the current level. It is created using only C# code.

We will add the level name to a currently existing level as a simple start to making GUIs:

  • Find your camera in the scene and add a new C# script named "GuiScript". This will contain all instructions for making your GUI
  • Set the script to this:
using UnityEngine;
using System.Collections;

public class GuiScript : MonoBehaviour {

    public string levelName = "Level 1 - Goblins!";

    private int screnWidth = Screen.width;

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }

    void OnGUI () {

        // Setup the dimensions of a rectangle on screen
        int positionX = 10;
        int positionY = 10;
        int width = this.screnWidth;
        int height = 20;
        Rect nameBoxRect = new Rect (positionX, positionY, width, height);

        // Draw a box with the level name in it
        GUI.Box(nameBoxRect, levelName);
    }
}
  • Find the camera in the scene again and set the new field "Level Name" in the GuiScript component to a custom value of your choosing

4.1 Challenge: Objective Waypoints

Games usually offer the player instructions on how to proceed in the game. A simple way to express this in the game is to use a technique like the display text exercise above. In this exercise, we will:

  1. Change the script from exercise 4.1 to include settable text
  2. Create a new game object with a 2D trigger to fire a new script that changes the text

Change the GuiScript to follow this:

using UnityEngine;
using System.Collections;

public class GuiScript : MonoBehaviour {

    public string startingText = "Level 1 - Start!";
    public static string text = "Level 1 - Start!";

    public static void SetDisplayText(string newText)
    {
        text = newText;
    }
    
    private int screnWidth = Screen.width;
    
    // Use this for initialization
    void Start () {
        text = startingText;
    }
    
    // Update is called once per frame
    void Update () {
        
    }
    
    void OnGUI () {
        
        // Setup the dimensions of a rectangle on screen
        int positionX = 10;
        int positionY = 10;
        int width = this.screnWidth;
        int height = 20;
        Rect nameBoxRect = new Rect (positionX, positionY, width, height);
        
        // Draw a box with the level name in it
        GUI.Box(nameBoxRect, text);
    }
}

Add this script to a new 2D trigger in your level, then set the text value in the editor to match your game:

using UnityEngine;
using System.Collections;

public class WaypointScript : MonoBehaviour {

    public string displayText = "";

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Player")
        {
            GuiScript.SetDisplayText(displayText);
        }
    }
}

4.2: GUI From Game Events

Often GUIs are shown to the player to indicate certain events have happened or are about to happen. For instance, when the player days, we probably want to tell them that they're dead before resetting the level.

We will modify the DeadlyScript to tell the player they died by making the following changes:

  • Open the script "DeadlyScript" in MonoDevelop by finding it in the assets area and double-clicking it
  • Modify the script to include a display of "You Died!" by changing it to the following:
using UnityEngine;
using System.Collections;

public class DeadScript : MonoBehaviour {

    public string deathMessage = "You Died!";

    private int screenWidth = Screen.width;
    private int screenHeight = Screen.height;

    private bool isDead = false;

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }
    
    IEnumerator Die(Collider2D other) {

        isDead = true;

        // Wait for 1 second
        yield return new WaitForSeconds(1);

        // Reload the level
        Application.LoadLevel (Application.loadedLevel);
    }

    void OnTriggerEnter2D(Collider2D other) {

        // If we touched the player, then kill them
        if (other.tag == "Player") 
        {
            StartCoroutine(Die(other));
        }
    }

    void OnGUI () {

        if (isDead) 
        {           
            // Determine screen height and width
            int screenWidth = this.screenWidth;
            int screenHeight = this.screenHeight;
            
            // Make the rectangle the full size of the screen
            Rect nameBoxRect = new Rect (0, 0, screenWidth, screenHeight);

            GUIStyle style = new GUIStyle();

            // Set text color to red
            style.normal.textColor = Color.red;

            // Set text size to very big
            style.fontSize = 64;

            // Set alignment to be in the middle and center of the box
            style.alignment = TextAnchor.MiddleCenter;

            // Draw a box with the level name in it
            GUI.Box(nameBoxRect, deathMessage, style);
        }
    }
}
  • Run the game and die to make sure it's correct
  • You can now change the "Death Message" field for every deadly object in your level

4.3: Buttons

Buttons enable the user to click with the mouse and have some action happen in the game, such as from the title screen of the game.

  • Save the current scene (File > Save Scene)
  • Make a new scene for your game (File > New Scene)
  • Drop the custom title sprite you made onto the level
  • Center the camera on it and position it such that the title is taking up the whole screen
  • Add a new script to the camera called "StartScript"
  • Open the script in MonoDevelop by double-clicking it and set the code to this:
using UnityEngine;
using System.Collections;

public class StartScript : MonoBehaviour {

    public string levelName;

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }
    
    void OnGUI () {
        
        // Setup the dimensions of a rectangle on screen
        int positionX = 200;
        int positionY = 200;
        int width = 200;
        int height = 50;
        
        Rect buttonRect = new Rect (positionX, positionY, width, height);
        
        // Draw a box with the level name in it
        if (GUI.Button (buttonRect, "Start")) 
        {
            Application.LoadLevel(levelName);
        }
    }
}

4.4: Changing Levels

Most games have a succession of levels that change when the user completes certain tasks.

To make a door that automatically changes level:

  • Add the currently open level to the "Build Settings" of the game (File > Build Settings, on the dialog click "Add Current")
  • Create a new level in your game.
  • Add this newly created level to the "Build Settings" of the game (File > Build Settings, on the dialog click "Add Current")
  • Creates a new object that is a tempting visual for changing levels (such as a door).
  • Add to that new object a 2D box collider and set it to be a trigger
  • Add a new script to the object called "ChangeLevelScript".
  • Set the text of the script to:
using UnityEngine;
using System.Collections;

public class ChangeLevelScript : MonoBehaviour {

    public string levelName;

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }

    void OnTriggerEnter2D(Collider2D other) {

        if (other.tag == "Player") 
        {
            Application.LoadLevel(levelName);
        }
    }
}
  • In the scene view, select the newly created door. In the "Level Name" field, set the name of the other level you created.
  • To make a reusable object (or "prefab") for the portal door, drag it from the hierarchy to the project area. Name it "Portal Door".
  • Open the other scene and drag the portal door prefab into the scene, setting its "Level Name" field to the target scene.
  • Hit play and ensure that your game now has an infinite circuit of two levels, each ending with a portal door to the next.

4.5: Celebrating Victory

When the player finishes the level, there should be something fun and exciting celebrating their victory.

  • Create a new sprite, near the end of your level, that is a tempting item for the player to touch before ending the level (such as a gold pile).
  • Add a polygon collider to the item and check the "Is Trigger" flag
  • Import the particles package into the project (Assets > Import Package > Particles)
  • Add a new instance of the fireworks particle (Standard Assets > Particles > Misc > Fireworks), making it a child of the gold pile
  • Look at the new fireworks object in inspector, unchecking the "emit" checkbox under the "Ellipsoid Particle Emitter" component
  • Copy & paste several copies of the fireworks particle and spread them around the level
  • Add a new script component to the gold pile, naming it "ActivateParticlesScript"
using UnityEngine;
using System.Collections;

public class ActivateParticlesScript : MonoBehaviour {

    void OnTriggerEnter2D(Collider2D other) {
        
        // If we touched the player, then kill them
        if (other.tag == "Player") 
        {           
            // Show all child particles
            ParticleSystem[] particles = this.transform.GetComponentsInChildren<ParticleSystem>();
            foreach(ParticleSystem p in particles)
            {
                p.Play();
            }
        }
    }
}

4.6: You Win!

To further prove to the player that their efforts resulted in absolute and total victory, let's enhance things yet further by displaying the text "You Win!" to the player.

  • Create a new empty game object
  • Add a 2D collider, setting it to act as a trigger
  • Add a script component to the collider, called "WinScript"
  • Copy and paste the snippet below into it
using UnityEngine;
using System.Collections;

public class WinScript : MonoBehaviour {
    
    public string winMessage = "You Win!";
    
    private int screenWidth = Screen.width;
    private int screenHeight = Screen.height;
    
    private bool isWin = false;
    
    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
    }
    void OnTriggerEnter2D(Collider2D other) {
        
        // If we touched the player, then kill them
        if (other.tag == "Player") 
        {
            isWin = true;
        }
    }
    
    void OnTriggerExit2D(Collider2D other) {
        // If we touched the player, then kill them
        if (other.tag == "Player") 
        {
            isWin = false;
        }
    }
    
    void OnGUI () {
        
        if (isWin) 
        { 
            // Determine screen height and width
            int screenWidth = this.screenWidth;
            int screenHeight = this.screenHeight;
            
            // Make the rectangle the full size of the screen
            Rect nameBoxRect = new Rect (0, 0, screenWidth, screenHeight);
            
            GUIStyle style = new GUIStyle();
            
            // Set text color to red
            style.normal.textColor = Color.green;
            
            // Set text size to very big
            style.fontSize = 50;
            
            // Set alignment to be in the middle and center of the box
            style.alignment = TextAnchor.MiddleCenter;
            
            // Draw a box with the level name in it
            GUI.Box(nameBoxRect, winMessage, style);
        }
    }
}
  • Load the game and touch the player character to the area covered by the new trigger
  • You should see the words "You Win!" appear

4.7 Every Win

If you want to, in one scripts, make a door that:

  • Fires off cool particles
  • Shows a "You Win!" text
  • And loads the next level

Then try:

  1. Add a door sprite
  2. Add a collider 2D trigger to the sprite
  3. Add a set of particles of your choice as children of the sprite
  4. Uncheck the "Play on Awake" check from the particle systems
  5. Add the following script:
using UnityEngine;
using System.Collections;

public class EveryWin : MonoBehaviour {
    
    public string winMessage = "You Win!";
    public string levelName = "";
    
    private int screenWidth = Screen.width;
    private int screenHeight = Screen.height;
    
    private bool isWin = false;
    
    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
    }
    void OnTriggerEnter2D(Collider2D other) {
        
        // If we touched the player, then kill them
        if (other.tag == "Player") 
        {
            StartCoroutine(Win ());
        }
    }

    IEnumerator Win() {

        // Show all child particles
        ParticleSystem[] particles = this.transform.GetComponentsInChildren<ParticleSystem>();
        foreach(ParticleSystem p in particles)
        {
            p.Play();
        }

        // Set win
        isWin = true;

        // Wait for 3 seconds
        yield return new WaitForSeconds(3);

        // Change the level
        Application.LoadLevel(levelName);
    }
    
    void OnGUI () {
        
        if (isWin) 
        { 
            // Determine screen height and width
            int screenWidth = this.screenWidth;
            int screenHeight = this.screenHeight;
            
            // Make the rectangle the full size of the screen
            Rect nameBoxRect = new Rect (0, 0, screenWidth, screenHeight);
            
            GUIStyle style = new GUIStyle();
            
            // Set text color to red
            style.normal.textColor = Color.green;
            
            // Set text size to very big
            style.fontSize = 50;
            
            // Set alignment to be in the middle and center of the box
            style.alignment = TextAnchor.MiddleCenter;
            
            // Draw a box with the level name in it
            GUI.Box(nameBoxRect, winMessage, style);
        }
    }
}

4.8 Fall Delay Platform

To make a time-delayed platform that falls only after the player lands on it, try the following:

  1. Add a new sprite that looks like a platform
  2. Add a box collider 2D to the platform to hold the player
  3. Add a rigid body 2D to the platform to make it dynamic
  4. Check the "Is Kinematic" box on the rigid body
  5. Add another box collider 2D and make it a trigger
  6. Increase the size of the trigger and position it such that it will trigger when the player lands on the platform
  7. Add the following script to the platform:
using UnityEngine;
using System.Collections;

public class FallDelay : MonoBehaviour {

    public float waitInSeconds = 2;

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Player") 
        {
            StartCoroutine (WaitToFall ());
        }
    }

    IEnumerator WaitToFall() {

        yield return new WaitForSeconds(waitInSeconds);
        
        this.GetComponent<Rigidbody2D> ().isKinematic = false;
    }
}

Now that you know how to win, it's time to finish the last day and taste sweet victory!