3 Overview

Today we will work on event-based scripting, building Graphical User Interfaces (GUI), and connecting levels together to make a game. We will cover firing scripts from these types of events:

  • One-time when the level starts
  • Each time a frame is rendered
  • When the player enters a "trigger"
  • When the player is in proximity to an object using a "raycast"

3.1: Scripting on Level Load

Unity gives you multiple points on customization for scripting. The simplest is when the level loads.

  • Importing a sound file
  • Adding an Audio Source game object
  • Creating a script component
  • Understanding that the "Start" method is called on each Game Object when the level is loaded

Soundtracks are background music that add excitement to games.

  • Download your favorite music track from the album "Gaming Sessions" on FreeMusicArchive
  • Import it into the game by dragging and dropping it into the Assets area
  • Drag the audio from the Assets area into the hierarchy, creating a Game Object with an Audio Source
  • Try checking "play on awake" and "loop" then hitting play to hear the sound
  • Add a new script component to the audio game object, name it "PlayScript" and ensure it's a C# script
  • In the "Start" method, add code to play the audio file (EG: this.audio.Play())
using UnityEngine;
using System.Collections;

public class SoundtrackScript : MonoBehaviour {

    // Use this for initialization
    void Start () {
        this.audio.Play();
    }
    
    // Update is called once per frame
    void Update () {
    }
}
  • Run the game again to see if the music plays again

3.2: Scripting on Every Frame

Scripting in Unity is also able to be called on every frame, or individual rendering while playing the game. This is useful for providing any constant effects in the game, such as tracking an object with the camera.

In this exercise, we will make a camera that follows a game object.

  • Ensure you have an object with a Collider2D and Rigid Body and have it falling in the game (EG, the boulder from exercise 2.10.
  • In the Hierarchy, try moving the camera in the scene into the boulder. Hit play to find out what happens.
  • Move the camera back out of the boulder.
  • Add a new script to the camera, named it "CameraScript" and ensure it's a C# script
  • In the script, add a new "public" field of type "Transform" named "target"
  • In the script, add a new "public" field of type "int" named "distance" and set it to "-2"
  • In the script, in the "Update" method, add code that makes a "Vector3" that will copy of the the "target.transform.position" and adds the "distance" value to the "z" field of the object, then sets the "this.transform.position" to that Vector3 object. This will fire every frame. Code snippet below:
using UnityEngine;
using System.Collections;

public class CameraScript : MonoBehaviour {

    public Transform target;
    public int distance = -2;

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        Vector3 cameraPosition = target.transform.position;
        cameraPosition.z += distance;
        this.transform.position = cameraPosition;
    }
}
  • Back in the level editor, set the "target" field on the camera's script to equal the boulder in your level
  • Run it to see the camera track it

3.3: Scripting on Collision

One of the most common scenarios for scripting is doing it when two objects touch, such as the player and a hazard in the environment that will kill them. To this end we will learn:

  • How to turn colliders into triggers
  • How to nest a child trigger into another object that is physics enabled
  • How to reload the current level

To demonstrate performing an action when the player touches an object, we will start by popping bubbles in the game as the player touches them. To do:

  • Create a bubble sprite with something that looks appealing to touch (the water bubble, the gold, etc)
  • Add a 2D box collider to the sprite
  • Check the "Is Trigger" property on the collider component
  • Make a new script on the sprite and call it "PopScript", ensure it's in C#
  • Add the following code for PopScript (the important part is in "OnTriggerEnter2D")
using UnityEngine;
using System.Collections;

public class PopScript : MonoBehaviour {

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

    void OnTriggerEnter2D(Collider2D other) {
        Destroy (this.gameObject);
    }
}

3.4: Scripting Physics

Sometimes we want an interactive object to kill the player, like a boulder. To do that, we will:

  • Create a sprite looks dangerous and rollable (like the boulder sprite)
  • Add a 2D circle collider and a rigid body to make it roll around the level
  • Add an empty game object as a child of the boulder
  • Add a 2D circle collider to the new empty game object and make it slightly larger than the boulder, enabling "Is Trigger"
  • Add an existing script component, using "DeadlyScript" on it
  • Run the game and try running over to the boulder and touching it.
using UnityEngine;
using System.Collections;

public class EnableParentPhysicsScript : MonoBehaviour {

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

    void OnTriggerEnter2D(Collider2D other) {
        
        if (other.tag == "Player") 
        {
            // Pull out the parent' rigid bodies into an "array" of rigid bodies
            Rigidbody2D[] rigidBodies = this.GetComponentsInParent<Rigidbody2D>();
            // Take only the first one (arrays start counting at zero, NOT 1)
            Rigidbody2D parentRigidBody = rigidBodies[0];
            // Set it to be simulated
            parentRigidBody.isKinematic = false;
        }
    }
}

3.4 Challenge: Coin Counter

Now that you can fire events from triggers, you can use these techniques to make a coin counter that counts the number of items picked up in the game. This will require two pieces:

A coin counter class, that is attached to a single game object in the game (such as to the camera, to the robot, or just in an empty game object in the scene), that will accumulate the coin count and draw a GUI to display the number:

using UnityEngine;
using System.Collections;

public class CoinCounter : MonoBehaviour {
    
    public static int coinCount = 0;

    public static void AddCoin() {
        coinCount = coinCount + 1;
    }

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

        // Make the rectangle the full size of the screen
        Rect nameBoxRect = new Rect (0, 0, 100, 100);
        
        GUIStyle style = new GUIStyle();
        
        // Set text color to red
        style.normal.textColor = Color.yellow;
        
        // Set text size to very big
        style.fontSize = 24;
        
        // 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, coinCount.ToString(), style);
    }
}

A "coin" in the game, which is a game object with a sprite, trigger collider, and a script that makes it disappear then increment the coin count:

using UnityEngine;
using System.Collections;

public class CoinPickupScript : MonoBehaviour {
    
    // Use this for initialization
    void Start () {
    }
    
    // Update is called once per frame
    void Update () {
    }
    
    void OnTriggerEnter2D(Collider2D other) {

        // Destroy this coin
        Destroy (this.gameObject);

        // Increment the coin count
        CoinCounter.AddCoin ();
    }
}

3.5: Scripting on Keyboard Input

Scripting on keyboard input is very common in Unity. It is how the 2D character controller enables moving left and right, plus jumping.

We will start by making the visual representation of the character attacking an enemy.

  • Add a new sprite to the scene that looks like a threatening attack (like the knight's slash sprite), placing it in front of the character controller, slightly to the right
  • Animate the attack and ensure it's working in the scene by itself
  • Make the attack sprite a child of the top-most character controller and ensure it moves with the character when it moves
  • Make a new script on the attack sprite and call it "AttackScript"
  • Make it so the script will:
    • Hide the attack when the game starts
    • Every update, check if the F key is down and, if so, show the attack for a moment then hide it again
using UnityEngine;
using System.Collections;

public class AttackScript : MonoBehaviour 
{
    
    // Use this for initialization
    void Start () 
    {
        GetComponent<SpriteRenderer> ().enabled = false;
        // renderer.enabled = false;
    }
    
    // Update is called once per frame
    void Update () {
        if (Input.GetKeyDown (KeyCode.F)) 
        {
            StartCoroutine(Attack());
        }
    }
    
    IEnumerator Attack() {
        GetComponent<SpriteRenderer> ().enabled = true;
        yield return new WaitForSeconds(0.1F);
        GetComponent<SpriteRenderer> ().enabled = false;
    }
}

3.6: Scripting on Proximity

While 2D colliders acting as triggers are very useful for checking proximity for items, there is another technique that is very useful for situations like the attack action, called "raycasting". This involves "shooting" a line from one point to another point and checking what is hit along the way. Some of these techniques are discussed thoroughly in this video on raycasting.

We will be making an enemy that can be destroyed by getting attacked. We will:

  • Find the enemy you created back in 3.4
  • Select the enemy's sprite game object and in inspector, setup a new tag with the name "Enemy", assigning it to the enemy
  • Back in the "Attack" widget, open up the AttackScript and modify it to include casting a ray to see there are any enemies, then destroying their game object if found.
using UnityEngine;
using System.Collections;

// The AttackScript should be attached to a sprite
// It should looks like a weapon slash or similar
// That sprite should have two empty children position at the start and end of the slash
public class AttackScript : MonoBehaviour 
{
    private bool isAttacking = false;
    
    private Transform rayStart;
    private Transform rayEnd;
    
    // Use this for initialization
    void Start () 
    {
        // Hide the attack sprite when the level starts
        this.GetComponent<SpriteRenderer>().enabled = false;
        
        // Grab the start and end point for reference later
        rayStart = this.transform.FindChild("RayStart");
        rayEnd = this.transform.FindChild("RayEnd");
    }
    
    // Update is called once per frame
    // This will cause the attack to happen whenever the player presses "F"
    void Update () {
        
        // Check if the user is touching the "F" key
        if (Input.GetKeyDown (KeyCode.F)) 
        {
            // If so, then start the attack
            StartCoroutine(Attack());
        }
        
        // If we are currently attacking
        if (isAttacking) 
        {
            // Look between RayStart and RayEnd for an enemy
            RaycastHit2D hit = Physics2D.Linecast(transform.position, rayEnd.position);
            
            // If there is a hit
            if (hit.transform != null)
            {
                // the thing we hit was an enermy
                if(hit.transform.tag == "Enemy")
                {
                    // Then detroy that enemy game object
                    Destroy(hit.transform.gameObject);
                }
            }
        }
    }
    
    // Called when the attack starts
    // Defines the attack behavior
    IEnumerator Attack() {
        
        // Set attacking to true and show this attack sprite
        isAttacking = true;
        this.GetComponent<SpriteRenderer>().enabled = true;
        
        // Wait for 1/10th of a second
        yield return new WaitForSeconds(0.1F);
        
        // Stop showing the attack and indicate we are not longer attacking
        this.GetComponent<SpriteRenderer>().enabled = false;
        isAttacking = false;
    }
}

3.7: 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

3.8: 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

3.9: 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.

4.4 Hands-On: Title Screen

  • 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);
        }
    }
}

That was epic! You made it through the hardest part, just a little more to go...