Game Camp: Day 2

2: Overview

Today is about learning the basic concepts of code in Unity 3D. We will build the basic interactions for a medieval 2D platforming game that uses the  We will introduce:

  • Working with 2D Images (AKA Sprites)
  • Introduction to the C# scripting language
  • Soundtracks & Sound Effects
  • Triggers for detecting collisions and firing behavior based on it
  • Using rays to interact in the scene
  • Using scripting to make enemies in the game

2.1: Sprites

Thus far, we have used only standard built-in assets that use only boring white. Now, we can start to branch out into using our own assets to start building our own world. This will cover:

  • Importing a new asset
  • Z Ordering in the game - The "Z" axis is the "depth" in your game. The higher the value, the further back in your game the asset will be positioned.
  • Sprites - 2D images used in the game to represent characters, items, and backgrounds in the game

To get started, you will need to:

  1. Download the package of example assets from this website.
  2. Unzip the package into a directory on your computer
  3. In the assets view, make a new directory under "Assets" to hold your imported assets. It's best to call it something like "My Assets".
  4. In the new directory, right click in the empty assets area and choose "Import New Asset"
  5. In the dialog, pick something from the example asset pack, like the "brick" image from the "Backgrounds" folder, then click "OK"
  6. Now that the new asset is in the editor, trying dragging and dropping it on the editor!
  • What other assets can you import?
  • Can you make them stack one-on-top-of-the-other?
  • Can you change a sprite's position?
  • Can you change a sprite's size?
  • Can you change a sprite's rotation?

2.2: Slicing a Sprite

A sprite often has more than one distinct image on it. This is to save memory and enable techniques like animation.  To do this, we will have to learn:

  • Sprite Slicing - Separating a single image in Unity into multiple sprites.

We're start by separating some of the background sprites into single pieces, to make decorating our level a lot easier:

  1. As in lesson 1.8, import one or more of the sprites in the "Background" folder of the example assets pack that have a multi-part background. For instance, "crypt" or "castle_toleset_part3_0".
  2. Once the sprite is in the assets folder, click on it to select it
  3. Look in the Inspector View for the "Sprite Mode" property
  4. Change "Sprite Mode" from "Single" to "Multiple"
  5. Under the properties for the sprite, click on the "Sprite Editor" button to launch the sprite editor
  6. In the editor, you can right-click to move the view around
  7. In the editor, you can hold down "ctrl" and scroll to zoom the view
  8. In the editor, slice out each unique piece of the image by left-clicking and dragging a box around the element.
  9. In the editor, you can left-click on existing boxes to selected them
    1. Selected boxes can be resized by dragging the blue dots in the corners
    2. Selected boxes can be deleted by pressing the "Delete" button
  10. Once you're done slicing, click the "Apply" button in the top-right of the editor
  11. Then close the editor using the "X" button in the top-right of the editor
  12. In the Asset View, you can now see each of the sprites within the image by pressing the little right-arrow on the asset
  13. You can drag & drop these sliced sprites into your level, moving and resizing them as you like to make a background
  • What other sprites can you slice?

2.3: Animating a Sprite

One use for a sprite having more than one image in it is to make an animated object in the game. To do this, we'll have to understand:

  • Sprite Slicing by Grid

To start, we'll need to select an image that is intended for animation, slice it properly, then drop it in the scene:

  1. As in Lesson 1.8, import one of the sprites from the standard asset pack, this time selecting one intended for animation, such as the "Diamond_strip7" image from the "Items" folder
  2. As in Lesson 1.9, set the sprite to multiple mode and open the sprite editor
  3. In the top-left of the sprite editor, click on the "Slice" button to bring up the slice menu
  4. In the slice menu, change the type to "Grid"
  5. In the slice menu, try different X and Y settings until the grid cuts each piece of the image evenly (if you used the Diamond_strip7", then X and Y are each 32), clicking the "Slice" button each time to see the grid change over the image
  6. When you're done, click the "Apply" button to apply changes to the sprite's slicing, then click the top-right "X" button to close
  7. From the Assets View, drag & drop the diamond strip onto the level (not a piece of it, the whole asset)
  8. Try hitting play to see if the sprite animates
  • What other sprites can you slice and animate?

2.4: Colliders & Rigid Bodies

Now that we have a working understanding of how to show game objects, let's give them interaction and simulation via physics. For those following along at home, watch this overview of 2D physics in Unity and 2D colliders. We will learn:

  • Box Collider
  • Circle Collider
  • Polygon Collider
  • Rigid Bodies
  • Controlling simulated weight and other properties

The first interaction in the castle will be making a set of walkways and ramps, then rolling a boulder down them. Using the example assets:

  1. Make a sprite for a flat walkway that has a box collider
  2. Make a sprite for a slanted ramp that has a polygon collider
  3. Connect them in some interesting configuration of ramps and steps, starting with a ramp at the top
  4. Make a sprite for a rolling boulder (recommend the "rock" image under the "Items" folder) that has a circle collider and a rigid body
  5. Position the rolling boulder such that it rolls down the ramp and through your castle

2.5: Scripting

In this exercise, we'll be introduced to MonoDevelop, which is an editor for code that can build and run programs. Such a tool is often called an Integrated Development Environment (IDE). It is installed as part of Unity to enable editing and building C# scripts.

It is traditional for programmers to start their careers by making a program that just outputs the words "Hello World". It is a simple program that just prints the words "Hello World" to the screen.

 For those following along at home, try this video about building Hello World. To get started:

  • Launch MonoDevelop
  • Make a new C# "Console" project
  • It should be created with the code below:
Console.WriteLine ("Hello World!");
  • Click "Build > Build All" then "Run > Start Without Debugging" to see you first app

2.6: Better Camera Tracking

The basic camera tracking works well enough, but most games don't completely lock the camera onto the character. They smoothly transition around the character's movements. To make a better camera, we'll have to learn about:

  • Adding components to game objects
  • Using a script as a component
  • Setting the argument of a script using the picker

The standard assets pack includes a better script for animating the camera around the character controller. To add it:

  1. In the Hierarchy View, select the "Main Camera" and drag it back out to being on the root level of the scene (undo what you did in 1.4 Hands-On).
  2. With the "Main Camera" selected, click the "Add Component" button in the inspector view
  3. In the popup, choose "Scripts" then choose "UnityStandardAssets._2D" then choose "Camera2DFollow"
  4. In the new block of properties in Inspector View, set the "Target" of the camera follow script to be the "CharacterRobotBoy" 
  5. Try running the game again and seeing how the camera follows the character now

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

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

2.8: 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 Hands-On: Deadly Boulder

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.

3.4 Hands-On: Deadly Boulder Trap

Since levels will be long, we don't always want boulders to start dropping the moment players start the level, only once the player is near. To do that:

  • Make a copy of the deadly boulder from the exercise above
  • On the deadly boulder sprite itself, find the rigid body connected to it and check the "Is Kinematic" flag to true
  • Add another empty child object to the sprite of the deadly boulder
  • Add a 2D circle collider to the empty child, making it very large (ideally about one screen width in diameter), enabling "Is Trigger"
  • Add a new script to to the new huge collider, calling it "EnableParentPhysicsScript"
  • In the script when colliding with the player, find the parent game object's rigid body and set the "isKinematic" field to false.
 
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 Hands-On: Deadly Enemy

Finally, we will make a deadly enemy that is animated and deadly to the touch

  • Import a sprite for a formidable enemy (such as the goblin script)
  • Animate the enemy using it's idle animation
  • Add a polygon collider to the enemy sprite
  • Add a rigid body to the enemy sprite, checking the "fixed angle" flag
  • Make the enemy "deadly", just like the boulder
    • Just like with the deadly boulder, add an empty child to the enemy
    • Add to the empty child of the enemy a 2D box collider, make sure it's only slightly larger than the enemy's sprite collider, and ensure "Is Trigger" is checked
    • Add to the empty child of the enemy the "DeadlyScript" use on the boulder
    • Run the game to double-check that when you touch them, you die

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.

3.5 Hands-On: Attack!

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 () 
  {
    renderer.enabled = false;
  }

  // Update is called once per frame
  void Update () {
    if (Input.GetKeyDown (KeyCode.F)) 
    {
        StartCoroutine(Attack());
    }
  }

  IEnumerator Attack() {
    renderer.enabled = true;
    yield return new WaitForSeconds(0.1F);
    renderer.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.

3.6 Hands-On: Attack with Damage

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
        renderer.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;
        renderer.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
        renderer.enabled = false;
        isAttacking = false;
    }
}

Your Rock! You're well on your way to being a pro developer now. Ready for the next challenge?