Unity3D Workshop – 05 – A platform for platformers.

(Note: This workshop is not 100% complete)

Welcome to the fifth in installment of the Unity3D workshops, in these workshops we will take a closer look at game development by taking a game concept and making a simple version of it in the Unity3D engine (version 4.1.1 at the time of writing).  This time in the workshop the game type that we will be examining will be one of the most common game types, the platformer.

Rayman Origins, Sonic the Hedgehog, Super Mario Bros.

The platformer has a lot of variety to them, but typically they have the player scrolling from left to right avoiding enemies and collecting powerups.  Much of the work that went into the infinite runner is applicable here also, if you have yet to run Workshop 02 it’s recommended that you go there first.

The major difference between the infinite runner and a typical side scroller is that the latter will have a greater care done to make specific levels that are unique and different.  Much like how the UI was crafted in Workshop 04, creating the levels in the editor while possible is often much more difficult and cumbersome than needed.  To facilitate this we will use a custom level editor, this editor is not default to Unity and details on how it works are available in the associate appendix. (!pending!)

The asset package for this project can be found here.  Create a new project, and bring in the assets package, tou may need to correct the material references after loading the assets.   Once setup, bring up the ‘XML Map Editor’ window found from the ‘Window’ pulldown menu.  Load the map and tile files provided and your window should look just like this.

Step 01: A map of nowhere.

As you can see this map is pretty unimpressive, so let’s resize it to something we can work with, 32×4.  Then enter a texture for the current tile, ‘Bricks’ is the one we want, and make sure to check ‘Collision’ so the player will be blocked by the brick blocks.  Take a minute here to draw out a map, left click will add a tile of the type selected holding ‘shift’ will clear the tile.  Here is an example of what you should see.

Step 02: Time to level with you

Make sure to save both the level and the tiles after making you level.  This will update the files in the ‘XML’ folder, and now that we have a level we can take it from XML to the game and see it in action.  Create a empty game object named ‘Level’ and place on it the ‘MapLoader’ script, and let then open that script up in MonoDevelop so we can take a look at what is going on.  Make sure to position the new ‘Level’ object at the origin (0,0,0) and the rotation (0, 180, 0).

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Xml;

public class MapLoader : MonoBehaviour
{
    // Local Variables
    public List<GameObject> m_aMapObjs = new List<GameObject>();
    public Dictionary<int, XMLObject> m_dMapTiles = new Dictionary<int, XMLObject>();

    void Start()
    {
        // TEMP - Update this!!
        LoadMap("Assets/XML/ExampleMap.xml", "Assets/XML/ExampleTiles.xml");
    }

    public void LoadMap(string strMapFile, string strTileSetFile)
    {
        // Read our XML document
        XMLObject pMapXML;
        XMLObject pTileSetXML;

        // Load our map
        XmlDocument pDoc = new XmlDocument();
        pDoc.Load(strMapFile);
        XmlNodeReader pNodeReader = new XmlNodeReader(pDoc);
        {
            pNodeReader.MoveToContent();
            pMapXML = XMLObject.ParseXML(pNodeReader);
        }
        pNodeReader.Close();

        // Load our Tileset
        pDoc.Load(strTileSetFile);
        pNodeReader = new XmlNodeReader(pDoc);
        {
            pNodeReader.MoveToContent();
            pTileSetXML = XMLObject.ParseXML(pNodeReader);
        }
        pNodeReader.Close();
        ReadTiles(pTileSetXML);

        // Walk through our map and build a tile for each node in the XML
        XMLObject pRowObj = null;
        XMLObject pColumnObj = null;
        XMLObject pTileObj = null;
        string strDictTemp = "";
        // We build bottom up
        for(int nRowIter = 0; nRowIter < pMapXML.m_aChildren.Count; ++nRowIter)
        {
            // Column by row
            pRowObj = pMapXML.m_aChildren[nRowIter];
            for(int nColumnIter = 0; nColumnIter < pRowObj.m_aChildren.Count; ++nColumnIter)
            {
                pColumnObj = pRowObj.m_aChildren[nColumnIter];
                // If there is no idea we don't need to create a block at this location
                if(pColumnObj.m_dAttributes.TryGetValue("TileID", out strDictTemp) && strDictTemp.Length > 0)
                {
                    // Try to find the tile being referenced
                    if(!m_dMapTiles.TryGetValue(int.Parse(strDictTemp), out pTileObj))
                        continue;

                    // Finally create the new map object and keep a reference to it
                    m_aMapObjs.Add(CreateMapObject(nRowIter, nColumnIter, pTileObj));
                }
            }
        }
    }

    // This is where we create a block of the map, we use the tile refernce to fill in the data
    GameObject CreateMapObject(int nRow, int nColumn, XMLObject pTileXML)
    {
        GameObject pTemp = GameObject.CreatePrimitive(PrimitiveType.Cube);
        pTemp.name = "Block_" + nRow + "_" + nColumn;
        pTemp.transform.position = new Vector3(nColumn, nRow, 0.0f);
        pTemp.transform.parent = gameObject.transform;

        // Grab the texture if there is one
        string strTemp;
        if(pTileXML.m_dAttributes.TryGetValue("Texture", out strTemp) && strTemp.Length > 0)
        {
            pTemp.renderer.material = (Material)Resources.Load("Materials/" + strTemp) as Material;
        }
        else
        {
            pTemp.renderer.enabled = false;
        }

        // Set the collision status
        if(pTileXML.m_dAttributes.TryGetValue("Collision", out strTemp) && strTemp.Length > 0)
        {
            pTemp.collider.isTrigger = !bool.Parse(strTemp);
        }

        // Give the block an extra script
        Basic_Block pBlock;
        if(pTileXML.m_dAttributes.TryGetValue("BlockType", out strTemp) && strTemp.Length > 0)
        {
            pBlock = (Basic_Block)pTemp.AddComponent(System.Type.GetType(strTemp + "_Block"));
        }
        else
        {
            pBlock = pTemp.AddComponent<Basic_Block>();    
        }

        // Set the collision callback
        if(pTileXML.m_dAttributes.TryGetValue("Trigger", out strTemp) && strTemp.Length > 0)
        {
            pBlock.m_strTriggerEvent = strTemp;
        }

        return pTemp;
    }

    // Build our tile list
    void ReadTiles(XMLObject pTileSetXML)
    {
        // Safety Check
        if(pTileSetXML == null)
            return;

        // Run every child
        string strTemp = "";
        for(int nChildIter = 0; nChildIter < pTileSetXML.m_aChildren.Count; ++nChildIter)
        {
            // Ignore any children that don't have an ID at all
            if(pTileSetXML.m_aChildren[nChildIter].m_dAttributes.TryGetValue("TileID", out strTemp))
            {
                // Put each tile into the map for faster reference
                m_dMapTiles.Add(int.Parse(strTemp), pTileSetXML.m_aChildren[nChildIter]);
            }
        }
    }
}

(Note: File loading here was roughed out, level selection to be added)

As with the XML files from Workshop 04 we are now reading over what was saved by the level editor and creating the level geometry based on that info.  We won’t worry about what exactly an ‘XMLObject’ is here, but we will go over how the map is built.  The first thing we do is build a ‘Dictionary’ of all our tiles inside of the ‘ReadTiles’ function, doing this saves a lot of work each time we need to associate a tile index from the map to the tile file.
The question is of course then, why do use an index from the map to the tiles instead of just simply storing all the tile data in the map.  The reason for this is that we will likely come back and need to change a tile on the map at a latter point, by referencing the tile by index any bulk changes are done in the tile definitions and work across the entire map.Inside of ‘LoadMap’ you can see map is build tile by tile row by column.  Each tile or ‘MapObject’ is created by the function ‘CreateMapObject’ and stored in a list of the level this list exists because when changing maps having a quick way to remove all the previous items would be a big help.  Let’s take a closer look at ‘CreateMapObject’.

    // This is where we create a block of the map, we use the tile refernce to fill in the data
    GameObject CreateMapObject(int nRow, int nColumn, XMLObject pTileXML)
    {
        GameObject pTemp = GameObject.CreatePrimitive(PrimitiveType.Cube);
        pTemp.name = "Block_" + nRow + "_" + nColumn;
        pTemp.transform.position = new Vector3(nColumn, nRow, 0.0f);
        pTemp.transform.parent = gameObject.transform;
        
        // Grab the texture if there is one
        string strTemp;
        if(pTileXML.m_dAttributes.TryGetValue("Texture", out strTemp) && strTemp.Length > 0)
        {
            pTemp.renderer.material = (Material)Resources.Load("Materials/" + strTemp) as Material;
        }
        else
        {
            pTemp.renderer.enabled = false;
        }
        
        // Set the collision status
        if(pTileXML.m_dAttributes.TryGetValue("Collision", out strTemp) && strTemp.Length > 0)
        {
            pTemp.collider.isTrigger = !bool.Parse(strTemp);
        }
        
        // Give the block an extra script
        Basic_Block pBlock;
        if(pTileXML.m_dAttributes.TryGetValue("BlockType", out strTemp) && strTemp.Length > 0)
        {
            pBlock = (Basic_Block)pTemp.AddComponent(System.Type.GetType(strTemp + "_Block"));
        }
        else
        {
            pBlock = pTemp.AddComponent<Basic_Block>();    
        }
        
        // Set the collision callback
        if(pTileXML.m_dAttributes.TryGetValue("Trigger", out strTemp) && strTemp.Length > 0)
        {
            pBlock.m_strTriggerEvent = strTemp;
        }
        
        return pTemp;
    }

This is the first time ‘transform.parent’ is being used, making each new game object a child of the level’s transform is a quality of life choice, doing this will collect the objects under the ‘Level’ object in the editors object hierarchy to keep the editor nice and clean while running.

After that basic setup the block is positioned based on it’s row/column which is also used to generate the name for the block.  Then the common parameters are set, notice that objects without a texture have their renderer disabled.  Also the ‘BlockType’ is some that will be important later.  Before getting to that, take a moment to run the project and if everything is setup right your map should load up.

Step 03: Level up!

Nice to see the level in the editor, yet without a player there really is not much going here.  Take a brief moment to setup the camera as before, so the blocks are nice and bright against a dark background to make them stand out well.

Step 04: Lights, Camera, …

The good old running man from Workshop 02 is back, create a player object and drop the ‘PlayerSpriteSheet’ material onto the object along with the ‘PlayerMovement’ script.  Now is also a good time to set the rotation (0, 180, 0) and a position where the player won’t start inside the a wall of the map, for the example used the position (1, 2, 0) was fine.

Step 05: A head on collision.

Before going to the script, there were a few modifications made here, first the ‘Box Collider’ was replaced with a sphere collider.  Doing this makes it easier for the player to land on ledges and control the character because the player is more a sphere than it is a box.  Secondly, inside the rigid body, freezing the ‘z’ axis and all of the object rotation there won’t be any problem with the player bouncing off the map that was created.  Now onto the player movement script:

using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Rigidbody))]
public class PlayerMovement : MonoBehaviour
{
    // Local Variables
    public float m_fGroundSpeed = 5.0f;
    public float m_fRunSpeedBoost = 2.5f;
    public float m_fAcceleration = 10.0f;
    public float m_fJumpHeight = 5.0f;
    bool m_bTryJump = false;

    // Use this for initialization
    void Start ()
    {
        rigidbody.freezeRotation = true;

        renderer.material.SetTextureScale("_MainTex", new Vector2(0.25f, 1.0f));
    }

    // Update is called once per render frame
    void Update()
    {
        // Did we want to jump
        m_bTryJump |= Input.GetButtonDown("Fire1");
        if(m_bTryJump)
            rigidbody.WakeUp();
    }

    // FixedUpdate at a regulated rate
    void FixedUpdate()
    {
        // See if we're pushing enough to move
        float fInput = Input.GetAxis("Horizontal");
        bool bRunning = Input.GetButton("Fire2");
        if(fInput != 0.0f && Mathf.Abs(rigidbody.velocity.x) < (m_fGroundSpeed + (bRunning ? m_fRunSpeedBoost : 0.0f)))
        {
            rigidbody.AddForce(fInput * (m_fAcceleration + (bRunning ? m_fRunSpeedBoost : 0.0f)) * Time.deltaTime, 0.0f, 0.0f, ForceMode.VelocityChange);
        }

        // Flip our facing if we are moving left
        if(Mathf.Abs(rigidbody.velocity.x) > 0.1f)
        {
            renderer.material.SetTextureOffset("_MainTex", new Vector2(rigidbody.velocity.x > 0.0f ? 0.0f : -0.25f, 1.0f));
            renderer.material.SetTextureScale("_MainTex", new Vector2(rigidbody.velocity.x > 0.0f ? 0.25f : -0.25f, 1.0f));
        }

        // We had a jump press since the last physics update
        if(m_bTryJump && rigidbody.velocity.y < 0.1f)
        {
            RaycastHit pHit;
            Physics.Raycast(transform.position, -Vector3.up, out pHit);
            if(pHit.distance < 1.0f)
            {
                rigidbody.AddForce(0.0f, m_fJumpHeight, 0.0f, ForceMode.VelocityChange);
            }
        }
        m_bTryJump = false;
    }

    // React to taking damage
    public virtual void TakeDamage() { }

    //
    void OnCollisionStay(Collision pCollision)
    {
        if(pCollision.contacts[0].normal.y == 1.0f)
        {
            Vector3 vTransform = transform.position;
            vTransform.y = pCollision.transform.position.y + 1;
            transform.position = vTransform;
        }
    }
}

Much of this is the same as before from other Workshops, however new type of update has join the ranks of ‘Update’ and ‘OnGUI’ that being ‘FixedUpdate’.  The unique part to ‘FixedUpdate’ is that it is called every frame, instead it happens outside of the frame processing and is called at a locked rate right before the physics is processed.  Using this function is necessary to make sure that all physics reactions get that same processing frequency.  Below is an image showing how the variable frame rate might be compared to a fixed update.

The downside of the fixed update is that the Input checks that are true based on ‘Up’ or ‘Down’ maybe be consumed between fixed updates by the normal update.  To compensate for this, the ‘Jump’ input is being noted as a request in the ‘Update’ function then processed in the ‘FixedUpdate’.  Looking at the spot where that is done you should also see the ‘rigidbody.WakeUp();’ call, for performance reasons the rigidbodies are put in a low cost ‘sleep’ mode, if we don’t wake it up when jump is pressed, the input could be delayed for a significant time.

// Flip our facing if we are moving left
if(Mathf.Abs(rigidbody.velocity.x) > 0.1f)
{
     renderer.material.SetTextureOffset("_MainTex", new Vector2(rigidbody.velocity.x > 0.0f ? 0.0f : -0.25f, 1.0f));
     renderer.material.SetTextureScale("_MainTex", new Vector2(rigidbody.velocity.x > 0.0f ? 0.25f : -0.25f, 1.0f));
}

The above block of code is a bit of fancy math to flip the frame of the player object to face the way it is moving.  Now is a good time to go and play the project and explore the map you made, depending on how you created it you might need to tweak the jump heights or movement speeds.

After spending a little time with your level, the lack of ‘scrolling’ for your side scroller becomes very apparent.  To fix that you will create your first script for the camera, ‘FollowCamera’, this script will keep the player object within a certain bounds of the camera space.  Drop that script onto the ‘Main Camera’ and make sure to put the play in as the ‘Follow Target’.  A quick glance at the script to see what is going on behind the camera:

using UnityEngine;
using System.Collections;

public class FollowCamera : MonoBehaviour
{
    // Member Variables
    public GameObject m_pFollowTarget;
    public Vector2 m_vBounds = new Vector2(5.0f, 5.0f);

    // Update is called once per frame
    void Update ()
    {
        // Safety check
        if(m_pFollowTarget == null)
            return;

        // We want to keep the target within the bounds
        Vector3 vDetaPos =  transform.position - m_pFollowTarget.transform.position;
        // Check our x bounding area
        if(vDetaPos.x < -m_vBounds.x)
            vDetaPos.x += m_vBounds.x;
        else if(vDetaPos.x > m_vBounds.x)
            vDetaPos.x -= m_vBounds.x;    
        else
            vDetaPos.x = 0;    

        // Check our y bounding ara
        if(vDetaPos.y < -m_vBounds.y)
            vDetaPos.y += m_vBounds.y;
        else if(vDetaPos.y > m_vBounds.y)
            vDetaPos.y -= m_vBounds.y;    
        else
            vDetaPos.y = 0;    

        // Leave our z offset alone
        vDetaPos.z = 0;

        // Make the adjustment to the position
        transform.position -= vDetaPos;
    }
}

In this case, the camera script works just like any other script for a game object.  Now if you run your project the camera should follow along when the player gets near the edge of the visible space as defined by the camera bounds.

At this point the project might be a nice little maze if the level is setup just right, but there is a lot more that can be done to a side scroller.  Looping back to the editor we can add some special blocks.  We will add a new block that will tigger ‘EnterPit’ and set the bricks to be ‘Breakable’.

Step 06: Chip off the ol’ block

When adding the the blocks that are invisible the map editor just shows the number for the ‘Tile ID’ so you can easily see what blocks are there.  This is a good time to open up the

using UnityEngine;
using System.Collections;

public class Basic_Block : MonoBehaviour
{
    public string m_strTriggerEvent = "";

    void OnTriggerEnter(Collider pOther)
    {
        // Send the tigger event to the object we collided with if there is one
        if(m_strTriggerEvent.Length > 0)
            pOther.BroadcastMessage(m_strTriggerEvent);
    }
}

public class Breakable_Block : Basic_Block
{
    // Something collided with us
    void OnCollisionEnter(Collision pOther)
    {
        // From below
        if(pOther.contacts[0].normal.y > 0.9f)
            Destroy(gameObject);
    }
}

Two tiny little scripts here, but very flexible.  The ‘Basic_Block’ will broadcast the trigger to the player, in this case ‘EnterPit’ was chosen.  In the case of ‘Breakable_Block’s when there is a physics collision we check the ‘Normal‘ which tells direction the object that collided with the block came from.  In this case if it came from below then we destroy this block.

What happens when the player recieves the ‘EnterPit’ message?

public class PlayerEvents : MonoBehaviour
{
    // We fell in a pit!
    void EnterPit()
    {
        // We die!
        Destroy(gameObject);
    }
}

Nothing good!  Make sure to add the ‘PlayerEvents’ script to the player object and then try out the project again.

(Note: More pending!)

Unity3D Workshop – 04 – Putting U & I Together

Welcome to the fourth in installment of the Unity3D workshops, in these workshops we will take a closer look at game development by taking a game concept and making a simple version of it in the Unity3D engine (version 4.1.1 at the time of writing).  For this workshop, we will be investigating game ‘UI’, an element that all games have in common.  However, there is a style of game that is run primarily using simple UI and that is a interactive story.

Hatoful Boyfriend, Fire Emblem, Harvest Moon

All of these games use ‘Talking Heads’ to communicate to the player plot or other details.  In additional to simple conversations we will also setup a basic menu and scrolling text intro screen.

The asset package can be downloaded: here.

Getting started we open up a new Unity3D project and drop in our asset package, right off the bat you will notice a couple of new things in the file structure.

Step 01: Covering our assets

This comes with a few scripts in the ‘Editor’ folder, this is some extra code use to process the ‘XML’ files found in the aptly named ‘XML’ folder.  We will go over how this works in the associated appendix but for now it is not something to dive into due to it being very code heavy and would bog down this workshop.

The first thing we want to do for our UI is create a custom “GUI Skin” this can be done by using the ‘Create’ pull down and selecting “GUI Skin” and name it ‘Primary’ (case sensitive).  This object is what Unity uses to control what the UI looks like.  As with the Input object we have looked at before this one also has a lot of predefined elements in it.

Step 02: The skinny on GUI Skin

There are a lot of bells and whistles in here, that can be adjusted to make the UI look exactly how you would like.  There is even a ‘Custom Styles’ category that allows you to make a new style that fits any special need.  In this demo, all that there is to do is set the default font to ‘lucon’ and it’s size to ’18’ (a size of ‘0’ is the default size for the font).  When dealing with fonts there are lots of options, ‘lucon’ is a default windows system font.

When dealing with games, fonts come in a few different flavors, there are the commonly seen ‘True Type‘ fonts which is what you are reading now and used by text editors.  There are also ‘Font Strips’ which are graphical images of every character used in a font, created almost the exact same way sprite sheets were made in workshop #2.  There is not enough time to dive deep into ‘Typography‘ in the scope of this workshop but a lot of effort goes into something that does not get much praise.  A common theme with UI work.

The next step is creating an empty game object to be named ‘UI_Manager’ and placing the script of the same name onto it.  Then place the skin you made into the field for it inside the inspector for the new object.  If you have trouble selecting the skin from the inspector picker you can also drag it from the assets folder.

Step 03: Not empty for long.

Now it’s time to take a look at the script here for the ‘UI_Manager’

using UnityEngine;
using System.Collections;

public abstract class UI_Child : MonoBehaviour
{
    public abstract void OnGUIChild();
    public virtual bool ParseXML() { return false; }
}

public class UI_Manager : MonoBehaviour 
{
    // Local Members    
    public GUISkin m_pGUISKin;
    public UI_Child m_pMainMenu;
    public UI_Child m_pIntro;
    public UI_Child m_pConversation;

    // What state our game is in
    enum UIState
    {
        enMainMenu,
        enIntro,
        enConversation,
        enBattle
    }
    UIState m_enUIState = UIState.enMainMenu;

    // Update is called once per frame
    void OnGUI()
    {
        // Set our skin
        GUI.skin = m_pGUISKin;

        // We want to draw the main menu
        if(m_enUIState == UIState.enMainMenu && m_pMainMenu != null)
            m_pMainMenu.OnGUIChild();
        else if(m_enUIState == UIState.enIntro && m_pIntro != null)
            m_pIntro.OnGUIChild();
        else if(m_enUIState == UIState.enConversation && m_pConversation != null)
            m_pConversation.OnGUIChild();
    }

    // Callback when we start a new game
    void UI_NewGame()
    {
        m_enUIState = UIState.enIntro;
    }
    // Call back when intro has ended
    void UI_IntroEnd()
    {
        m_enUIState = UIState.enConversation;
    }
    // Call back when the conversation has ended
    void UI_ConversationEnd()
    {
        m_enUIState = UIState.enMainMenu;
    }
}

At the top of the file there is a abstract class being created, this is the same as what was being done for the enemies in the third workshop, however here it is being used to create a way to reference another script on the object.  Additionally it gives it two base functions that each implementation of ‘UI_Child’ will use.

public abstract class UI_Child : MonoBehaviour
{
    public abstract void OnGUIChild();
    public virtual bool ParseXML() { return false; }
}

This script is very similar to what you have already worked with before.  The UI manager has a simple ‘state machine’ for controlling what is displayed just like the player did in the second workshop.

    // What state our game is in
    enum UIState
    {
        enMainMenu,
        enIntro,
        enConversation,
        enBattle
    }
    UIState m_enUIState = UIState.enMainMenu;

One big difference on this behavior instead of any of the previous ones you have seen is the ‘OnGUI’ function.  Here it is being used instead of the ‘Update’ function, this is for code timing, ‘OnGUI’ is always called after all of the ‘Update’ functions have finished.  This allows the UI to have the most recent data.

    // Update is called once per frame
    void OnGUI()
    {
        // Set our skin
        GUI.skin = m_pGUISKin;

        // We want to draw the main menu
        if(m_enUIState == UIState.enMainMenu && m_pMainMenu != null)
            m_pMainMenu.OnGUIChild();
        else if(m_enUIState == UIState.enIntro && m_pIntro != null)
            m_pIntro.OnGUIChild();
        else if(m_enUIState == UIState.enConversation && m_pConversation != null)
            m_pConversation.OnGUIChild();
    }

You should notice the ‘GUI.skin’ variable where the skin chosen from the editor is being set as the active skin.  As with ‘Input’ or ‘Time’, ‘GUI’ is another shared global system variable changes made here will effect any following GUI interactions.  Then the function will draw more GUI elements based on what state the UI_Manager is in.

Add the ‘MainMenu’ scrip to the ‘UI_Manager’ object and then from inside the object inspector drag a reference of the ‘MainMenu’ script onto the ‘UI_Manager’ script.  Finally use the ‘LoadXML’ button to select the ‘MainMenu.xml’ file.

Step 04: Script nesting and XML loading

Note: The ‘Load XML’ button is not a standard inspector element found in the Unity editor.  It is a custom one created by the ‘UI_Child_Inspector’ script, the creation of this will be covered in the associated appendix.

Let’s take a look at the script for ‘UI_MainMenu’ the ‘OnGUIChild’ to be exact, there is some important stuff going on in the ‘ParseXML’ functions but that is something that will be covered later.

    // Do our part of the GUI only if the manager wants it
    public override void OnGUIChild()
    {
        // Draw our background
        GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), m_pBackground);

        // Create a horizontal group
        GUILayout.BeginHorizontal();
        {
            // Push in from the left
            GUILayout.Label(" ", GUILayout.MaxWidth(Screen.width));
            // Create a vertical group
            GUILayout.BeginVertical();
            {
                // Push down from the top
                GUILayout.Label(" ", GUILayout.MaxHeight(Screen.height));
                // Create all of our buttons
                XMLObject pChild;
                string strTemp;
                for(int nIter = 0; nIter < m_pXMLObject.m_aChildren.Count; ++nIter)
                {
                    // Create the button
                    pChild = (XMLObject)m_pXMLObject.m_aChildren[nIter];
                    if(pChild.m_dAttributes.TryGetValue("FriendlyName", out strTemp) && strTemp.Length > 0)
                    {
                        // If it was pressed, broadcast that to the rest of the object
                        if(GUILayout.Button(strTemp, GUILayout.MaxWidth(200)) && 
                           pChild.m_dAttributes.TryGetValue("Action", out strTemp))
                        {
                            gameObject.BroadcastMessage(strTemp);
                        }
                        GUILayout.Space(20);
                    }
                }
                // Push up from the bottom
                GUILayout.Label(" ", GUILayout.MaxHeight(Screen.height));
            }
            GUILayout.EndVertical();
            // Push in from the right
            GUILayout.Label(" ", GUILayout.MaxWidth(Screen.width));
        }
        GUILayout.EndHorizontal();
    }

A lot of new ‘GUI’ and ‘GUILayout’ call here there are few types of calls we will focus on the ‘Formatting’ and ‘Content’ types.  Formatting functions are the ‘Begin’ and ‘End’ pairs such as ‘BeginHorizontal’ or ‘Space’ these allow you to organize the UI by adding structure.  The content functions including ‘Label’ or ‘Button’ are generally what the user sees and interacts with.

The content creating functions often use additional layout options like ‘GUILayout.MaxWidth(Screen.width)’ these come in three basic flavors ‘Min’, ‘Max’, and ‘Fixed’ just like the ‘Min’ and ‘Max’ functions for numbers used previously these additional options add more formatting to the UI layout.

            // Push in from the left
            GUILayout.Label(" ", GUILayout.MaxWidth(Screen.width));

This blank label has a max with as wide as the screen so it will try to take up as much space as possible, however since it’s in the same horizontal group as the button’s vertical group and it’s partner blank label the space is evened out.  Run the project and you will see what is happening.

Step 05: Grouped groups

By using groups and white space we can position the button exactly where we want it.  The reason this is important is that using hard fixed numbers limits the type of display the project can work on.  Try grabbing the corner of the preview window and change the size arbitrarily, the button will stay in the center the screen in any situation even the untenable situation of the screen being too small for the button.  Speaking of that button, how about a close look.

                        // If it was pressed, broadcast that to the rest of the object
                        if(GUILayout.Button(strTemp, GUILayout.MaxWidth(200)) && 
                           pChild.m_dAttributes.TryGetValue("Action", out strTemp))
                        {
                            gameObject.BroadcastMessage(strTemp);
                        }

The UI for buttons and other controls not only draw but they also report any input associated with them.  Here the function that draws the button will return true if it was pressed during that update.  Since the press state of a button is something that you definitely want to know about a button combing the actions is very handy.  If the button was pressed our old friend the object broadcaster does it job and it just so happens that the ‘UI_Manager’ was listening for this event.

However since the other states of the UI_Manager have yet to be set pressing the button clears the screen, that’s not any fun so lets add the next scripts.  After the next state after main menu is the ‘Intro’ so repeat the process for adding a script and selecting an XML file for ‘UI_Intro’

    // Do our part of the GUI only if the manager wants it
    public override void OnGUIChild()
    {
        // Draw our background
        GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), m_pBackground);

        // Early out if the user presses the 'Skip' button
        Vector2 vSize = GUI.skin.button.CalcSize(new GUIContent("Skip"));
        Rect vPos = new Rect(10, 10, vSize.x, vSize.y);

        // Create area for the skip button
        GUILayout.BeginArea(vPos);
        {
            if(GUILayout.Button("Skip"))
            {
                gameObject.BroadcastMessage("UI_IntroEnd");
                GUILayout.EndArea();
                return;
            }
        }
        GUILayout.EndArea();

        // Keep track of our scrolling
        m_fScrollPosition += Time.deltaTime * m_fScrollSpeed;
        vPos.Set(0, Screen.height - m_fScrollPosition, Screen.width, m_pXMLObject.m_aChildren.Count * 100.0f);
        // Create an area for the text scroll
        GUILayout.BeginArea(vPos);
        {
            // Loop through all our lines of text
            XMLObject pChild;
            // Start a horizontal group
            GUILayout.BeginHorizontal();
            {
                GUILayout.Label(" ", GUILayout.MaxWidth(Screen.width));
                // Start a verticle group
                GUILayout.BeginVertical();
                {
                    // Walk all the children objects
                    for(int nIter = 0; nIter < m_pXMLObject.m_aChildren.Count; ++nIter)
                    {
                        // We use the space to find out where we are to adjust the alpha
                        GUILayout.Space(20);
                        m_textColor.a = GetTextAlpha((vPos.y + GUILayoutUtility.GetLastRect().y) / Screen.height);
                        GUI.color = m_textColor;

                        // Grab the child
                        pChild = (XMLObject)m_pXMLObject.m_aChildren[nIter];
                        GUILayout.Label(pChild.m_strValue, GUILayout.Width(Screen.width * 0.8f));
                    }
                }
                GUILayout.EndVertical();
                GUILayout.Label(" ", GUILayout.MaxWidth(Screen.width));
            }
            GUILayout.EndHorizontal();

            // If our final line of text was off the top of the screen, the intro is over
            if(GUILayoutUtility.GetLastRect().y < 0.0f)
            {
                gameObject.BroadcastMessage("UI_IntroEnd");
            }
        }
        GUILayout.EndArea();
    }

We will take a look at the ‘OnGUIChild()’ function again, much of this is the same as you saw in the ‘UI_MainMenu’ but a few new elements have show up.

        Vector2 vSize = GUI.skin.button.CalcSize(new GUIContent("Skip"));
        Rect vPos = new Rect(10, 10, vSize.x, vSize.y);

The ‘CalcSize’ function is a great little buddy to have around it will tell you how much space you need to create the element you pass in.  This let’s us know exactly how much space to set the area to so the ‘Skip’ button fits perfectly.

// Keep track of our scrolling
m_fScrollPosition += Time.deltaTime * m_fScrollSpeed;
vPos.Set(0, Screen.height - m_fScrollPosition, Screen.width, m_pXMLObject.m_aChildren.Count * 100.0f);
// Create an area for the text scroll
GUILayout.BeginArea(vPos);

This snippet here shows the ‘BeginArea’ slowly being moved up in screen space.  The scrolling text of the intro contains a large number of elements that go into this area, moving this one area will move the entire group and is a lot smarter way of handling the goal of scrolling the text up the screen.

m_textColor.a = GetTextAlpha((vPos.y + GUILayoutUtility.GetLastRect().y) / Screen.height);
GUI.color = m_textColor;

Here we have ‘GUILayoutUtility.GetLastRect()’ which is the chronological opposite of ‘CalcSize’ where instead of seeing the future size it instead returns the size of the last GUI element created.  This information is being use to adjust the ‘alpha’ of the text color, this alpha is exactly the same as the alpha on textures used in the previous workshops.  Take a peak at the ‘GetTextAlpha’ function that returns that alpha as a percent if you are interested in the math used.

Step 06: The plodding plot

After adding this script and linking it to the ‘UI_Manager’ clicking new game will tell the into to this workshop.  Without the third script this intro leaves us with many questions and uncertainties, so it’s time to get some answers by adding the ‘UI_TalkingHeads’ script and linking it up to the ‘UI_Manager’ and loading the similarly named XML one more time.

Much of this script is now common place to you, but there is a bit of new here to take a closer look at.  Namely the ‘GUIStyle’ that is custom to this script.

GUIStyle  m_pGUIStyle = new GUIStyle();
m_pGUIStyle.stretchWidth = true;
m_pGUIStyle.stretchHeight = true;
// Set the custom image into the GUIStyle
m_pGUIStyle.normal.background = m_pLeftHead;
GUILayout.Box("", m_pGUIStyle, GUILayout.Height(240), GUILayout.Width(240));

For the talking heads to make sure the images fill the entire space dedicated to them instead of using a ‘GUILayout.Label’ which can be created with an image a ‘GUILayout.Box’ is used instead with the background set to the image desired.  Having a custom style here allows for any image set as the background to be stretched or shrunk to fill the space exactly.

Step 07: I’m still not an artist.

Using the ‘Next’ button the user can walk through this exciting conversation and learn about the evil that is dooming the land.  By now you are probably wondering where all this content is coming from, now is a good time to talk about the XML files that we have been using.

‘XML’ or Extensible Markup Language is a document format that is readable by machines but can also be read by humans.  Up until now all of our data in our projects have been either hard coded into the scripts or manually set from the editor.  Using external XML files a developer gains even more flexibility.  Values and content can be changed without using the editor or recompiling the code, the data can be edited by external tools and shared in a more readable format.

Here is what one of the XML files looks like:

<TalkingHeads Background="MenuBG">
    <DialogLine LeftSpeaker="True" SpeakerName="Librarian" LeftHead="Librarian" RightHead="">Every this is <b>EXPLODING!</b> I sure hope the Duckess is okay! DUCKESS! WHERE ARE YOU MAGISTY?!?</DialogLine>
    <DialogLine LeftSpeaker="false" SpeakerName="The Duckess" LeftHead="Librarian" RightHead="Duckess">QUACK! QUACK! QUACK! quackity quackity QUACK!</DialogLine>
    <DialogLine LeftSpeaker="false" SpeakerName="Evil Incarnate" LeftHead="Librarian" RightHead="SpacePigeon">You'll never stop the darkness you meddiling librarian! We'll take the Duckess and use it for our foul rituals!<b>BWA HA HA HA HA!</b></DialogLine>
    <DialogLine LeftSpeaker="True" SpeakerName="Librarian" LeftHead="Librarian" RightHead="SpacePigeon">Not so fast! I'll stop you at all costs!</DialogLine>
</TalkingHeads>

As a flat text file you can see how the ‘DialogLine’ elements are children of the ‘TalkenHead’ node.  Each of the ‘DialogLine’ nodes have ‘Atrributes’ such as ‘SpeakerName’ and data inside of the node between the <DialogLine …> and </DialogLine>.  You could if you so choose edit this file by hand.

The other option would be to use a tool to edit it, such as the one following appendix creates, but you can use it now if you go back to the Unity editor and under ‘Window’ select ‘XML Editor’ which will let you load a file and work with it.

Step 07: Custom editor window

Having this data is great, but how it’s used in the project can be touched on lightly here and expanded on in detail in a code heavy appendix.  Take a look at the ‘XMLObject’ script

public class XMLObject
{
    public string m_strName = "NewNode";
    public string m_strValue = "";
    public Dictionary<string,string> m_dAttributes = new Dictionary<string,string>();
    public ArrayList m_aChildren = new ArrayList();

    ...
}

Just the very top part of the file, you can see that we are creating a class that contains the data that makes a XML node.  The name of the node, it’s value, an array of it’s children, and a new data type a ‘Dictionary’ of it’s attributes.  Also known as an ‘Associative Array‘ is a collection of elements have a pair of data that creates the associate.  Think of the classic word association technique used in physiology, that is what goes on in an Associative Array.

To access values in the ‘Dictionary’ the following function is used:

TryGetValue("FriendlyName", out strTemp)

This returns ‘true’ if the key is found and the value is copied into the ‘out’ parameter ‘strTemp’ using ‘out’ parameters allows have multiple return values from a function in essence.

Appendix: Custom Spawning

In the 3rd workshop, we spawned two types of enemies but ended up using the same values for health, speed, and size for them. That greatly limited what could be done gameplay wise, in this appendix we will revisit the spawning code and take a deeper dive into what can be done.

Spawner.cs

using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour
{
    // Local Variables
    [System.Serializable]
    public class EnemyType
    {
        public string m_strFriendlyName = "None";
        public string m_strEnemyType = "None";
        public int m_nEnemyHealth = 5;
        public float m_fEnemySpeed = 0.1f;
        public float m_fEnemyScale = 2.0f;
    }

    public float m_fSpawnRadius = 20.0f;
    public float m_fSpawnDelay = 3.0f;
    public EnemyType[] m_aEnemyTypes;
    float m_fAccumulatedSpawnTime = 0.0f;

    // Use this for initialization
    void Start ()
    {

    }

    // Update is called once per frame
    void Update ()
    {
        // Keep track of how much time has passed
        m_fAccumulatedSpawnTime += Time.deltaTime;
        while(m_fAccumulatedSpawnTime > m_fSpawnDelay)
        {
            // Decrement the time
            m_fAccumulatedSpawnTime -= m_fSpawnDelay;

            // Create a new enemy
            GameObject pNewObject = GameObject.CreatePrimitive(PrimitiveType.Cube);

            // Match the spawner's rotation and position
            pNewObject.transform.rotation = transform.rotation;
            pNewObject.transform.position = transform.position;
            // Rotate the new object randomly
            pNewObject.transform.RotateAround(Vector3.forward, Random.Range(0.0f, 360.0f));
            // Then push the object back the spawn offset distance
            pNewObject.transform.Translate(m_fSpawnRadius, 0.0f, 0.0f);

            // Set the behaviour on this new enemy
            int nEnemyType = Random.Range(0, m_aEnemyTypes.Length);
            Enemy pEnemy = (Enemy)pNewObject.AddComponent(m_aEnemyTypes[nEnemyType].m_strEnemyType);
            pEnemy.Init(m_aEnemyTypes[nEnemyType].m_nEnemyHealth, m_aEnemyTypes[nEnemyType].m_fEnemySpeed, m_aEnemyTypes[nEnemyType].m_fEnemyScale);
        }
    }
}

Two areas have changed here, looking at the top first, you will see that the variables that were shared by the enemies have been grouped together into a new class ‘EnemyType’. This is a subclass, that exists within the ‘Spawner’ class, it is not a MonoBehavior like all of the previous classes up until this point. It is being used basically to group a collection of data elements. Subclasses are often interchanged with the ‘struct‘ type depending on the situation and code language, however in our case here using subclasses are easier with the Unity object inspector and will be our default choice.

     // Local Variables
    [System.Serializable]
    public class EnemyType
    {
        public string m_strFriendlyName = "None";
        public string m_strEnemyType = "None";
        public int m_nEnemyHealth = 5;
        public float m_fEnemySpeed = 0.1f;
        public float m_fEnemyScale = 2.0f;
    }

    public float m_fSpawnRadius = 20.0f;
    public float m_fSpawnDelay = 3.0f;
    public EnemyType[] m_aEnemyTypes;
    float m_fAccumulatedSpawnTime = 0.0f;

This class is prefixed with the label ‘[System.Serializable]’, ‘Serialization‘ is a fancy way of saying the code can be saved. A new variable was added the ‘m_aEnemyTypes’ which is an array of enemies for the spawner to pick from.

             // Set the behaviour on this new enemy
            int nEnemyType = Random.Range(0, m_aEnemyTypes.Length);
            Enemy pEnemy = (Enemy)pNewObject.AddComponent(m_aEnemyTypes[nEnemyType].m_strEnemyType);
            pEnemy.Init(m_aEnemyTypes[nEnemyType].m_nEnemyHealth, m_aEnemyTypes[nEnemyType].m_fEnemySpeed, m_aEnemyTypes[nEnemyType].m_fEnemyScale);

Then at the bottom of the code a random element from the array is picked and all the data from that element is used to craft the enemy. Notice that the ‘Random.Range’ function works different with integer values than with floats, by being ‘exclusive’ with the max value which means it won’t be selected random. This is very handy with arrays because the length is always one higher than the indexing would allow.

Take a look at it in the editor to see the difference:

Super Space Pigeon is beyond compare

Here we have created our two enemy types from before, the Space Saw and the Space Pigeon, however a third type was setup ‘Super Space Pigeon’ that reuses the normal Space Pigeon behavior but has different configuration data. Give it a try right here.

Appendix: Keyboard Turning

In the 3rd workshop we took the assumption that the player has a dual stick controller for granted. In a real situation, that may not be the case. A small adaptation to the code needs to be made to support multiple input configurations.

PlayerMovement.cs

     // Update is called once per frame
    void Update ()
    {
        // We are going to read the input every frame
        Vector3 vNewInput = new Vector3();
        if(Input.GetJoystickNames().Length > 0)
        {
            vNewInput.Set(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0.0f);
        }
        // Use keyboard check if no joystick is connected
        else
        {
            // First the 'y' input
            if(Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.E))
                vNewInput.y = 1.0f;
            else if(Input.GetKey(KeyCode.Z) || Input.GetKey(KeyCode.X) || Input.GetKey(KeyCode.C))
                vNewInput.y = -1.0f;
            // Then the 'x' input
            if(Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.Z))
                vNewInput.x = -1.0f;
            else if(Input.GetKey(KeyCode.E) || Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.C))
                vNewInput.x = 1.0f;
        }
        ...
    }

PlayerAiming.cs

    // Update is called once per frame
    void Update ()
    {
        // We are going to read the input every frame
        Vector3 vNewInput = new Vector3();
        if(Input.GetJoystickNames().Length > 0)
        {
            vNewInput.Set(Input.GetAxis("Horizontal_R"), Input.GetAxis("Vertical_R"), 0.0f);
        }
        // Use keyboard check if no joystick is connected
        else
        {
            // First the 'y' input
            if(Input.GetKey(KeyCode.Keypad7) || Input.GetKey(KeyCode.Keypad8) || Input.GetKey(KeyCode.Keypad9))
                vNewInput.y = -1.0f;
            else if(Input.GetKey(KeyCode.Keypad1) || Input.GetKey(KeyCode.Keypad2) || Input.GetKey(KeyCode.Keypad3))
                vNewInput.y = 1.0f;
            // Then the 'x' input
            if(Input.GetKey(KeyCode.Keypad7) || Input.GetKey(KeyCode.Keypad4) || Input.GetKey(KeyCode.Keypad1))
                vNewInput.x = -1.0f;
            else if(Input.GetKey(KeyCode.Keypad9) || Input.GetKey(KeyCode.Keypad6) || Input.GetKey(KeyCode.Keypad3))
                vNewInput.x = 1.0f;    
        }

        // Only do work if meaningful
        if(vNewInput.sqrMagnitude < 0.1f)
        {
            return;
        }
        ...
    }

PlayerShooting.cs

     // Update is called once per frame
    void Update ()
    {
        // We are going to read the input every frame
        if(Input.GetJoystickNames().Length > 0)
        {
            // Check if our aim stick is in use
            Vector3 vNewInput = new Vector3(Input.GetAxis("Horizontal_R"), Input.GetAxis("Vertical_R"), 0.0f);
            if(vNewInput.sqrMagnitude < 0.1f)
            {
                // Reset the shooting timing if we stop shooting
                m_fAccumulatedFireTime = 0.0f;
                return;
            }
        }
        else
        {
            // Check if any of our aim keys are in use
            bool bShoot = Input.GetKey(KeyCode.Keypad1) || Input.GetKey(KeyCode.Keypad2) || Input.GetKey(KeyCode.Keypad3) ||
                          Input.GetKey(KeyCode.Keypad4) || Input.GetKey(KeyCode.Keypad6) ||
                          Input.GetKey(KeyCode.Keypad7) || Input.GetKey(KeyCode.Keypad8) || Input.GetKey(KeyCode.Keypad9);

            if(!bShoot)
            {
                // Reset the shooting timing if we stop shooting
                m_fAccumulatedFireTime = 0.0f;
                return;
            }
        }
        ...
    }

All three of files were modified to include the following:

 if(Input.GetJoystickNames().Length > 0)

This call to the input system will return a list with the names of the joysticks attached to the system, if at anytime the list is empty it will instead read the keyboard status directly and map the left nine letters and they number pad to be moving and shooting.

Swap the files link here for the ones in workshop 3 to try it out. A demo of this working, and it will switch modes while running if you add or remove joysticks.

Alternatively, you can also bind the numpad keys in project’s input manager like this:

The key to good input

The [ ] square brackets are how you specify keys on the number pad, this will also work. As always, there are multiple ways to solve any problem when making games.

Unity3D Workshop – 03 – Dualing sticks

Welcome to the third in our series of unity workshops, in these workshops we will take a closer look at game development by taking a game concept and making it inside of the Unity3D engine (version 4.1.1 at the time of writing). For this workshop, we will be investigating the ‘Dual Stick Shooter’ while this game style has been around for a very long time it had a dip in popularity between the arcades and the home console until the release of analog sticks. You may recognize games such as ‘Robotron‘ ‘Smash TV‘ or ‘Geometry Wars‘ that show the evolution of the game design structure.

Robotron: 2084 (1982), Smash TV (1990), Geometry Wars (2007)

Note: For the purpose of this workshop, the assumption will be made that you are using a controller while making this game, if not there will be a appendix afterwards for making a dual-stick shooter work on a keyboard alone.

The asset package can be downloaded: here.

Our first task for this project is going to be making the player object, we are still using basic cubes and textures as with the previous two projects, our player in this project will be represented by the ‘Starship Macaroni’

The Cheesiest

One adjustment we are going to make for this is instead of the default box collider, a sphere collider will be used. By swamping out the collider that is being the simulation for the hit box of the player can be better customized. Multiple collision shapes could be used to get the accuracy even tighter at the trade off of performance.

Step 01: One small step

Now to add the code to make the player move around, this is very similar to what was used on the tank in the first project, but since this is a space ship the ability to move in any direction is going to take a bit more code. Create or import ‘PlayerMovement’ script into the project.

using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Rigidbody))]
public class PlayerMovement : MonoBehaviour
{
    // Local Variables
    Vector3 m_vMinBounds = new Vector3(-14.0f, 2.0f, 0.0f);
    Vector3 m_vMaxBounds = new Vector3(14.0f, 18.0f, 0.0f);
    public float m_fPlayerSpeed = 0.1f;

    // Use this for initialization
    void Start ()
    {
        rigidbody.useGravity = false;
        collider.isTrigger = true;
    }

    // Update is called once per frame
    void Update ()
    {
        // We are going to read the input every frame
        Vector3 vNewInput = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0.0f);

        // Only do any work if meaningful
        if(vNewInput.sqrMagnitude < 0.1f)
            return;

        // Turn the input in a normalized, or 'Unit' vector
        vNewInput.Normalize();
        // Add our input to our position
        transform.position += vNewInput * m_fPlayerSpeed;

        // Limit the position to our play area
        Vector3 vNewPosition = transform.position;
        vNewPosition = Vector3.Max(m_vMinBounds, vNewPosition);
        vNewPosition = Vector3.Min(m_vMaxBounds, vNewPosition);
        transform.position = vNewPosition;

        // Set our rotation to represent the input
        float fHeading = Vector3.Dot(Vector3.right, vNewInput);
        Vector3 vNewRotation = transform.rotation.eulerAngles;
        vNewRotation.z = fHeading * 90.0f;

        // Adjust our rotation if we're on the bottom half of the input circle
        if(vNewInput.y < 0.0f) // Input is inverted
        {
            vNewRotation.z = 180.0f - vNewRotation.z;    
        }

        // Apply the transform to the object        
        transform.rotation = Quaternion.Euler(vNewRotation);
    }

    // React to taking damage
    public virtual void TakeDamage() { }

}

Things that you should recognize:

  • ‘RequireComponent’ creating a rigid body for this object
  • The ‘public’ keyword making a value editable in the editor
  • The ‘start’ function initializing settings on components
  • The ‘update’ function happening every frame
  • The ‘max’ and ‘min’ functions keeping the player in a play area

Things you may not recognize should be more interesting.

        // Only do any work if meaningful
        if(vNewInput.sqrMagnitude < 0.1f)
            return;

Here the ‘sqrMagnitude’ is being checked to exit the update function early. Analog sticks have something called the ‘DeadZone’ this is the part of the stick that is unreliably inaccurate, and often increases as a the controller wares with use. When in the ‘DeadZone’ the input will be reported as zero instead, since the following code won’t work in that case this check will exit the function instead of trying to run good code on garbage data.

Unlike the first project where the tank always faced the same direction, our ship needs to rotate to respond to the input. To do this we do a bit of math, first a ‘Dot’ product, a dot product is the project of two vectors onto each other. Think of how your shadow changes in the sun, as the sun moves your shadow is the representation of the ‘Dot’ product between your height and the sun’s position in the sky.

One special thing about the Dot product is that if you use vectors of the same length, often Unit vectors that have a length of one, the dot product is the angle between them. We are using that fact here, where we take the Dot product of the engine constant ‘Vector3.right’ (1, 0, 0) and the player’s input which we ‘Normalized’ which turned it into a unit vector.

Before trying this code out make sure to do a small checklist to get the new project to line up with some of the defaults of the previous workshops.

  • Set the ‘Ambient Light’ to white
  • Make sure the ship is ‘Scaled’ to size 2, and rotated 180deg on the y-axis
  • Make sure the camera is in position at (0, 10, -10) and the ship is at (0, 10, 0)
  • Make sure the camera is ‘Orthographic’ and ‘Size’ is 10

Additionally, while in the camera settings, change the background to a nice color that best represents space. If everything is going according to play, your project should look like this.

You may have remembered from the start of this project that this is a ‘Dual’ stick shooter, so we are only half done on that part, adding that feature is the next step. Go to ‘Edit -> Project Settings -> Input’ and change ‘Fire1’ and ‘Fire2’ into our right stick input. You can add new input by adjusting the ‘Size’ setting at the top but since no buttons are being used for this project replacing existing buttons is fine.

Step 02: Taking Control

Notice that the ‘DeadZone’ is set to 0.19 that makes the inner most 19% of the stick read as zero to the scripts. With this new input source defined, something needs to listen to it, and since this is a ‘shooter’ it’s time to add a turret to our spaceship.

[IMG][IMG]

Here we have images for our turret base and the barrel to add our our assets. Create a pair of cubes as normal, each with one of these elements. However, on each of these, delete the ‘Box Collider’, they do not need any collision detection.

In the previous workshops we have talked about using multiple scripts to composite functionality and behaviors together on a single game object. To expand on this, now we will composite multiple game objects together to make a much more complex object. The first step is to stack up all our objects:

  • Set the ship to: (0, 10, 0)
  • Set the base to: (0, 10, -1)
  • Set the turret to: (0, 10, -2)

Step 03: Ship Construction, some assembly required.

You can use the camera settings as indicated by the arrow to adjust your viewport to be axis aligned, this will make the position of the elements much easier. Now while you could use complex math to get the scaling and positioning right, art is pretty subjective so take a few moments to scale and position the rotating base and the turret on the ship as you see fit. Leave the ‘z’ position alone unless you want to change the layering.

You can use the following numbers if you’d prefer to just jump ahead to the next step.

Step 04: The finished product.

This looks pretty good, but they are not associated with the player object at all, to do that, simply drag the ‘TurretBarrel’ onto the ‘TurretBase’ in the object Hierarchy, and then the ‘TurretBase’ onto the ‘Player’ object, and you will then have created your first composite object in Unity3D.

Step 05: The object tree

Running the project now, you will see the ‘Child’ object of the ship will move with ‘Parent’ object, the player. To make it rotate however, it’s time to add another script. Create or import ‘PlayerAiming’ script into the project.

using UnityEngine;
using System.Collections;

public class PlayerAiming : MonoBehaviour
{
    // Update is called once per frame
    void Update ()
    {
        // We are going to read the input every frame
        Vector3 vNewInput = new Vector3(Input.GetAxis("Horizontal_R"), Input.GetAxis("Vertical_R"), 0.0f);
        // Only do work if meaningful
        if(vNewInput.sqrMagnitude < 0.1f)
        {
            return;
        }

        // Set our rotation to represent the input
        vNewInput.Normalize();
        float fHeading = Vector3.Dot(Vector3.right, vNewInput);
        Vector3 vNewRotation = transform.rotation.eulerAngles;
        vNewRotation.z = fHeading * 90.0f;

        // Adjust our rotation if we're on the bottom half of the input circle
        if(vNewInput.y > 0.0f)
        {
            vNewRotation.z = 180.0f - vNewRotation.z;    
        }

        // Apply the transform to the object        
        transform.rotation = Quaternion.Euler(vNewRotation);
    }
}

This script is very similar to the player movement, it does use the new ‘Horizontal_R’ and ‘Vertical_R’ inputs. If you place the script onto the ‘TurretBase’ object and run the project you should have a nice looking turret and a slick space ship flying around, just like this.

There are a few terms that have been used a few times now, ‘Quaternion‘ and ‘Euler‘ these are both mathematical ways of representing angles. There are advantages and disadvantages to using them, which leads to another programming war to which is the right one to use. Because the inspector in the editor uses ‘Euler’ angles, these workshops will stick with using them over quaternions to keep from switching form constantly.

All that math aside, while we have the ‘dual sticks’ working, that is only a third of what we need to make a ‘dual stick shooter’. Our shooting makes noise, so place the ‘Pew.mp3‘ sound effect into a new set of folders ‘Resources’ then ‘Sounds’, this location is important later so make sure to place it in that structure. Then create or import both the ‘PlayerShooting’ and ‘Projectile’ scripts.

using UnityEngine;
using System.Collections;

public class PlayerShooting : MonoBehaviour
{
    public float m_fRateOfFire = 0.2f;
    public float m_fProjectileSpeed = 0.3f;
    float m_fAccumulatedFireTime = 0.0f;
    AudioSource m_pFireSound;

    // Use this for initialization
    void Start ()
    {
        m_pFireSound = (AudioSource)gameObject.AddComponent("AudioSource");
        m_pFireSound.clip = (AudioClip)Resources.Load("Sounds/Pew") as AudioClip;
    }

    // Update is called once per frame
    void Update ()
    {
        // We are going to read the input every frame
        Vector3 vNewInput = new Vector3(Input.GetAxis("Horizontal_R"), Input.GetAxis("Vertical_R"), 0.0f);
        if(vNewInput.sqrMagnitude < 0.1f)
        {
            // Reset the shooting timing if we stop shooting
            m_fAccumulatedFireTime = 0.0f;
            return;
        }

        // Keep track of extra time of shots
        m_fAccumulatedFireTime += Time.deltaTime;

        // Broadcast our shooting
        int nNumShotsFired = (int)(m_fAccumulatedFireTime / m_fRateOfFire);
        for(int nShotIter = 0; nShotIter < nNumShotsFired; ++nShotIter)
        {
            // Create a new shot
            GameObject pNewObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            pNewObject.name = " Player_Projectile";
            pNewObject.renderer.material.color = Color.red;
            // Set it's position and rotation to match this object's in world space
            //  and put it on 0.0f 'z-axis' to make sure it'll hit targets
            pNewObject.transform.rotation = transform.rotation;
            Vector3 vTempPos = new Vector3(transform.position.x, transform.position.y, 0.0f);
            pNewObject.transform.position = vTempPos;
            // Scale it to a nice 'magic number' size
            pNewObject.transform.localScale = new Vector3(0.25f, 0.25f, 0.25f);

            // Add the projectile behavior to the object
            Projectile pNewProjectile = (Projectile)pNewObject.AddComponent("Projectile");
            pNewProjectile.Init(m_fProjectileSpeed);
        }

        if(nNumShotsFired > 0)
        {
            m_pFireSound.Play();    
        }

        m_fAccumulatedFireTime %= m_fRateOfFire;
    }
}
    // Use this for initialization
    void Start ()
    {
        m_pFireSound = (AudioSource)gameObject.AddComponent("AudioSource");
        m_pFireSound.clip = (AudioClip)Resources.Load("Sounds/Pew") as AudioClip;
    }

This is what makes the position of the file very important. Last time a sound was used it was picked in the editor, that method is still valid in this case, learning about this now is foreshadowing for later. What the ‘Resources.Load’ allows us to do is dig into the file structure and fine a resource to use. Most systems are very picky about spelling and capitalization, or ‘case sensitive’, which means if the sound isn’t working check to make sure the files are in the right place.

using UnityEngine;

On many occasions the scripts have used ‘built in’, ‘predefined’, or ‘common’ values and functions. These are generally provided by the libraries included by the ‘using’ keyword at the top of the file. As these projects get more complex the frequency of using additional libraries will also increase.

To add this script and make projectiles appear in the perfect spot, switch over to the editor and create an empty game object from the ‘Game Object’ pull down mention or use the ‘ctrl+shift+N’ shortcut key. Place this object in the hierarchy on the end of the barrel and position right at the end, then place the ‘PlayerShooting’ script onto it.

Step 06: Just the tip.

Switch over now to the ‘MonoDevelop’ editor and the ‘Projectile’ script.

 using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Rigidbody))]
public class Projectile : MonoBehaviour
{
    // Local variables
    float m_fProjectileSpeed = 0.5f;
    float m_fProjectileLifeTime = 2.0f;

    // The constructor for this class
    public void Init(float fProjectileSpeed)
    {
        // How fast are we going to move?
        m_fProjectileSpeed = fProjectileSpeed;
    }

    // Use this for initialization
    void Start ()
    {
        rigidbody.useGravity = false;
        collider.isTrigger = true;
    }

    // Update is called once per frame
    void Update ()
    {
        // Our project moves on it's own without needing player input
        transform.Translate(0.0f, m_fProjectileSpeed, 0.0f);

        m_fProjectileLifeTime -= Time.deltaTime;
        if(m_fProjectileLifeTime < 0.0f)
        {
            // Destroy this projectile after it's life time runs out
            DestroyObject(gameObject);
        }
    }

    // Collision handling
    void OnTriggerEnter(Collider pOther)
    {
        // We don't hit players
        if(pOther.gameObject.GetComponent<PlayerMovement>() != null)
            return;

//        // Tell enemies they were hit                            // Lines to be uncommented after adding the enemy
//        if(pOther.gameObject.GetComponent<Enemy>() != null)        // Lines to be uncommented after adding the enemy
//        {                                                        // Lines to be uncommented after adding the enemy
//            // Tell whatever we hit to take damage                // Lines to be uncommented after adding the enemy
//            pOther.gameObject.BroadcastMessage("TakeDamage");    // Lines to be uncommented after adding the enemy
//        }                                                        // Lines to be uncommented after adding the enemy

        // Destroy this projectile after collision
        DestroyObject(gameObject);
    }
}

No surprises here in this file, but a few minor things to take note of. First, there is a block of code commented out at the bottom, this section won’t work until later so for now we leave it inert as comments.  Secondly, these projectiles have a limited life span and will self delete after 2 seconds if they don’t hit anything. Speaking of collisions, in the ‘OnTriggerEnter’ function we are using:

         // We don't hit players
        if(pOther.gameObject.GetComponent<PlayerMovement>() != null)
            return;

Instead of the previous version which took the component to test for as a string name. Put everything together and the end result should be pretty close to this. The player is pretty much complete at this point, however without anything to shoot the end result falls flat, time to add some things to shoot.

For this workshop we are using two different enemy types, so add in some familiar faces and make materials for their textures, and also grab their associated sounds ‘SpacePigeon‘ and ‘SpaceSaw‘:

 using UnityEngine;
using System.Collections;

[RequireComponent (typeof (AudioSource))]
[RequireComponent (typeof (Rigidbody))]
public abstract class Enemy : MonoBehaviour
{
    // Local variables
    protected int m_nHitPoints = 5;
    protected float m_fMoveSpeed = 0.08f;
    protected AudioSource m_pAudioSource;

    // Use this for initialization
    public virtual void Start()
    {
        m_pAudioSource = (AudioSource)gameObject.GetComponent("AudioSource");
        m_pAudioSource.playOnAwake = false;

        rigidbody.useGravity = false;

        collider.isTrigger = true;
    }

    // Update is called once per frame
    public abstract void Update();

    // The constructor for this class
    public virtual void Init(int nEnemyHealth, float fMoveSpeed, float fScale)
    {
        // How much health do we have
        m_nHitPoints = nEnemyHealth;

        // How fast are we going to move?
        m_fMoveSpeed = fMoveSpeed;

        // How big is this enemy?
        transform.localScale = new Vector3(fScale, fScale, fScale);
    }

    // React to taking damage
    public virtual void TakeDamage()
    {
        // Take a hit, return if still alive
        if(--m_nHitPoints > 0)
            return;

        // Otherwise this enemy is dead
        DestroyObject(gameObject);
    }
}

The code here has all been done before, with just one exception of a new keyword: ‘virtual’, ‘protected’, and ‘abstract’.

Early in this workshop we created a complex object by combining simpler pieces together, using data and the editor, now the process will be repeated but in a coding process known as ‘polymorphism‘ or “Many Forms”. The basic idea is that many complex objects are made from a similar base object, working with base objects is often easier and more flexible.

The study of birds is also known as “ornithology’

That is a simplified example if we were building a ‘polymorphism’ inheritance for a ‘Bird’ type in our game. The ‘basic class’ of bird defines the common features. They have legs, wings, eyes, feathers for example it would also have some basic functions.

    public virtual int GetNumWings() { return m_nNumWings; }
    public virtual bool CanFly() { return m_bCanFly; }
    public virtual enFoodType GetDesiredFood() { return m_enFoodType; }
    public abstract void MoveTo(Vector3 vLocation);

When asking a base bird if it can fly, the result is based on what ‘derived class’ it actually is. Notice that the ‘MoveTo’ function does nothing, this is because it is an ‘abstract’ function which means any derived class must implement a version of this function. In this case, all birds have to define how they will move to a location. For example:

public class Pigeon : Bird
{
    public override void MoveTo(Vector3 vLocation)
    {
        FlyTo(vLocation);
    }
}

public class Penguin : Bird
{
    public override void MoveTo(Vector3 vLocation)
    {
        SwimTo(vLocation);
    }
}

As you can see here, when asked to ‘MoveTo’ a location, the Pigeon which is a Bird type will call it’s ‘override’ function to decided to ‘FlyTo’ that location, while a Penguin would instead use the ‘SwimTo’ route. This will all become clearer as we add our enemy types. First a deadly space saw, create or import the ‘EnemySaw’ script.

using UnityEngine;
using System.Collections;

public class EnemySaw : Enemy
{
    // Local Variables
    Vector3 m_vDirection;
    Vector3 m_vMinBounds = new Vector3(-14.0f, 2.0f, 0.0f);
    Vector3 m_vMaxBounds = new Vector3(14.0f, 18.0f, 0.0f);

    // Use this for initialization
    public override void Start()
    {
        // Call the base class
        base.Start();

        // What do we look like?
        renderer.material = (Material)Resources.Load("Materials/SpaceSaw") as Material;
        gameObject.name = " EnemySaw";

        // Play our sound
        m_pAudioSource.clip = (AudioClip)Resources.Load("Sounds/SpaceSaw") as AudioClip;
        m_pAudioSource.loop = true;
        m_pAudioSource.Play();
    }

    // The constructor for this class
    public override void Init(int nEnemyHealth, float fMoveSpeed, float fScale)
    {
        // Call the base class
        base.Init(nEnemyHealth, fMoveSpeed, fScale);

        // Do this class's init
        // First find our direction towards the target
        GameObject pTarget = GameObject.Find("Player");
        m_vDirection = pTarget.transform.position - transform.position;
        m_vDirection.Normalize();
    }

    // Update is called once per frame
    public override void Update()
    {
        // Move towards the player, try to crash into them
        transform.Translate(m_fMoveSpeed * m_vDirection, Space.World);
        // This saw is always spinning
        transform.RotateAround(Vector3.forward, 30.0f);

        // Adjust our direction
        if((transform.position.x < m_vMinBounds.x && m_vDirection.x < 0) ||
           (transform.position.x > m_vMaxBounds.x && m_vDirection.x > 0))
        {
            m_vDirection.x *= -1;
        }
        if((transform.position.y < m_vMinBounds.y && m_vDirection.y < 0) ||
           (transform.position.y > m_vMaxBounds.y && m_vDirection.y > 0))
        {
            m_vDirection.y *= -1;
        }
    }

    // Collision reaction
    void OnTriggerEnter(Collider pOther)
    {
        // We only hit other players
        if(pOther.gameObject.GetComponent<PlayerMovement>() == null)
            return;

        // Do Damage then explode!
        pOther.BroadcastMessage("TakeDamage");
        Destroy(gameObject);
    }
}

A pretty hefty bit of code to go over, so starting at the top:

public class EnemySaw : Enemy

Our ‘EnemySaw’ is a derived class of ‘Enemy’ which means we have access to what the base class has to offer and can be treated as that class.

    // Use this for initialization
    public override void Start

Here you are using the ‘override’ keyword to tell the engine that this version is more important than the one in the base class and should be called instead of the version in the base class.

        // Call the base class
        base.Start();

This line is where a lot of the polymorphism cleverness shows, while we just said the ‘override’ version is more important than the base class version, we can still run the base class code by using the ‘base’ label.

         // Play our sound
        m_pAudioSource.clip = (AudioClip)Resources.Load("Sounds/SpaceSaw") as AudioClip;
        m_pAudioSource.loop = true;
        m_pAudioSource.Play();

At first you might not notice what is interesting about this part, until you see that ‘m_pAudioSource’ is not local variable we declared in this class. Remember the ‘protected’ keyword from before in the ‘Enemy’ class, by using that derive classes such as ‘EnemySaw’ can then access the variable in it’s base class. For a quick refresher:

  • private (or blank) – Can only be accessed by the class that defined the variable
  • protected – Can only be accessed by the class that defined it and any derived classes
  • public – Can be accessed by anything
        // First find our direction towards the target
        GameObject pTarget = GameObject.Find("Player");

The last bit of new concepts in this file, here we are finding a game object by the name given to it in the editor. After finding that we set a direction towards the player, and on the update call we move on that trajectory, and bounce off the bounds of the play area.

using UnityEngine;
using System.Collections;

public class EnemyPigeon : Enemy
{
    // Local Variables
    GameObject m_pTarget;

    // Use this for initialization
    public override void Start()
    {
        // Call the base class
        base.Start();

        // What do we look like?
        renderer.material = (Material)Resources.Load("Materials/SpacePigeon") as Material;
        gameObject.name = " EnemyPigeon";

        // Play our sound
        m_pAudioSource.clip = (AudioClip)Resources.Load("Sounds/SpacePigeon") as AudioClip;
        m_pAudioSource.Play();
    }

    // The constructor for this class
    public override void Init(int nEnemyHealth, float fMoveSpeed, float fScale)
    {
        // Call the base class
        base.Init(nEnemyHealth, fMoveSpeed, fScale);

        // Do this class's init
        m_pTarget = GameObject.Find("Player");
    }

    // Update is called once per frame
    public override void Update()
    {
        // Make sure we have a target
        if(m_pTarget == null)
        {
            DestroyObject(gameObject);
            return;
        }

        // First find our direction towards the target
        Vector3 vDirToTarget = m_pTarget.transform.position - transform.position;
        vDirToTarget.Normalize();

        // Move towards the player, try to crash into them
        transform.Translate(m_fMoveSpeed * vDirToTarget, Space.World);

        // Rotate to face our target
        float fHeading = Vector3.Dot(Vector3.right, vDirToTarget);
        Vector3 vNewRotation = transform.rotation.eulerAngles;
        vNewRotation.z = fHeading * 90.0f;

        // Adjust our rotation if we're on the bottom half of the input circle
        if(vDirToTarget.y > 0.0f)
        {
            vNewRotation.z = 180.0f - vNewRotation.z;    
        }

        // Apply the transform to the object        
        transform.rotation = Quaternion.Euler(vNewRotation);
    }

    // Collision reaction
    void OnTriggerEnter(Collider pOther)
    {
        // We only hit other players
        if(pOther.gameObject.GetComponent<PlayerMovement>() == null)
            return;

        // Do Damage then explode!
        pOther.BroadcastMessage("TakeDamage");
        Destroy(gameObject);
    }
}

Above is the class for the space pigeon, very similar to the space saw, however on it’s update function it will keep adjusting it’s velocity and rotation to head strait towards the player. Both the saw and the pigeon are of the ‘enemy’ type, but they behave differently.

It is true that with careful composition the same setup could have been created by using a common code for the appearance of an ‘enemy’ and separate code for the unique behaviors, it comes down to a lot of personal preference and organizational tastes, you’ll come across both frequently and it’s good to learn multiple ways to solve problems when making games.

Now that both enemy types are defined, there is just one part left to do. In the previous projects there was only one enemy that was reused over and over. That’s not enough to convey the terrible secrets of space that the player has to deal with. By creating a spawned for the non stop stream of enemies we can achieve that feeling.

Create an empty game object, placed at (0, 10, 0) and name it ‘Spawner’ in the object hierarchy, then create or import the ‘Spawner’ script.

 using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour
{
    // Local Variables
    public int m_nEnemyHealth = 5;
    public float m_fEnemySpeed = 0.1f;
    public float m_fSpawnRadius = 20.0f;
    public float m_fEnemyScale = 2.0f;
    public float m_fSpawnDelay = 3.0f;
    float m_fAccumulatedSpawnTime = 0.0f;

    // Use this for initialization
    void Start ()
    {

    }

    // Update is called once per frame
    void Update ()
    {
        // Keep track of how much time has passed
        m_fAccumulatedSpawnTime += Time.deltaTime;
        while(m_fAccumulatedSpawnTime > m_fSpawnDelay)
        {
            // Decrement the time
            m_fAccumulatedSpawnTime -= m_fSpawnDelay;

            // Create a new enemy
            GameObject pNewObject = GameObject.CreatePrimitive(PrimitiveType.Cube);

            // Match the spawner's rotation and position
            pNewObject.transform.rotation = transform.rotation;
            pNewObject.transform.position = transform.position;
            // Rotate the new object randomly
            pNewObject.transform.RotateAround(Vector3.forward, Random.Range(0.0f, 360.0f));
            // Then push the object back the spawn offset distance
            pNewObject.transform.Translate(m_fSpawnRadius, 0.0f, 0.0f);

            // Set the behaviour on this new enemy
            Enemy pEnemy;
            if(Random.value > 0.5f)
            {
                pEnemy = (Enemy)pNewObject.AddComponent("EnemyPigeon");
            }
            else
            {
                pEnemy = (Enemy)pNewObject.AddComponent("EnemySaw");
            }
            pEnemy.Init(m_nEnemyHealth, m_fEnemySpeed, m_fEnemyScale);
        }
    }
}

This is the spawner script, only a few details to go over. There is another keyword here, ‘while’ a while loop is control for code flow. It does something while the condition in the parentheses are true, they also come in the ‘do-while’ flavor where the action is performed before checking the conditional. Inside of this while loop, after adjusting the accumulated time, it creates a new object and then pushes it out from it’s position in a random direction, then picks a enemy type to assign it.

Add this script to the ‘Spawner’ object, and make sure to uncomment the lines at the bottom of the ‘Projectile’ script (short-cut ‘ctrl+alt+c’) and your project should look like this. At this point it’s time for you to experiment with what you have learned, mix and match techniques from all the workshops and come up with your own end creations.

Unlike the previous workshops this one will have some additional appendices on ‘Keyboard Control’ and ‘Advanced Spawning’ that you are invited to look at.

Unity3D Workshop – 02 – An Animated Discussion

Welcome to the second game development workshop, where you will make a game in under two hours using the Unity3D Engine (version 4.0.1 at time of writing). The game project for this time will be a ‘side scroller’, or more exactly an ‘infinite runner’. You may be familiar with different types of side scrolling games:

Super Mario Brothers – Gradius – Bit.Trip Runner

There is a vast array of games in this category with variance on player movement, direction of scroll, forced scrolling, and many more.  For this project, it will be a one direction forced scrolling setup with a player who can jump or roll to avoid obstacles.

All of the assets and scripts for this project can be downloaded as one package here.

The player character’s run cycle is the first type of animation you will implement. Since this game is using a 2D game style, the first step is going to be creating what is known as a ‘sprite sheet‘. A ‘sprite sheet’ is a collection of images that are use to represent objects in a game at different poses and animation, think of it as a what you would get if you laid the pages of a flip book out end to end. Here is the place holder sprite sheet for today’s project:

Running Man with a head band.

This particular sprite sheet has four frames of animation to represent the character running. It may be hard to tell from seeing them all displayed together like that, each frame is a 128×128 pixel square placed left to right. This is important for how the code that drives the animation works, but the details for that will come later. For the time being if you are making your own art, make sure to keep each frame the same dimensions.

( Note: This sprite sheet could be reduced to 3 frames if we were concerned about optimizations at this point. )

To get started you need to setup a new project in Unity:

  • Create a cube: Object Hierarcy → Create → Cube → Rename to ‘Player’
    Position the cube in view of the camera
  • Import a texture: Drag into Unity → Drag onto ‘Player’ cube → Set to ‘Transparent/Diffuse’
  • Set the lighting: Edit → Render Settings → Ambient Light → White

All of these steps were done in the first workshop, go back to it as needed.. There is one new step at this point, in the inspector panel for the ‘Player’ cube, set the value for the tiling of the x-axis to 0.25. This will only use part of the texture at a time on the cube, and 0.25 exactly the size of one frame when measured horizontally. If the frame sizes on the sprite sheet were not uniform, the math to animate the character would be more complex. If everything is going according to plan, you should have something that looks a pretty close to this:

Step 1: The first step

The actual animation is going to be done in a script, create a new C# script named ‘SpriteAnimation’ (or import and rename ‘SpriteAnimation_01’) and open it MonoDevelop. The first pass on the animation setup only requires a few lines of code:

using UnityEngine;
using System.Collections;

public class SpriteAnimation : MonoBehaviour
{
    // Local Variables
    float m_fFrameTime = 0.0f;
    float m_nCurrentFrame = 0;
    Vector2 m_vTextureOffset = new Vector2();

    // Use this for initialization
    void Start ()
    {

    }

    // Update is called once per frame
    void Update()
    {
        // Keep track of how much time has passed in the current frame
        m_fFrameTime += Time.deltaTime;
        // If we have shown the current frame for more than 0.2 seconds
        if(m_fFrameTime > 0.2f)
        {
            // Cut off the frame time
            m_fFrameTime -= 0.2f;
            // Increment the frame number
            m_nCurrentFrame = (m_nCurrentFrame + 1) % 4;
            // Set the texture position
            m_vTextureOffset.x = 0.25f * m_nCurrentFrame;
            renderer.material.SetTextureOffset("_MainTex", m_vTextureOffset);
        }
    }
}

Much of this you have seen before, as a refresher here it is broken down in detail:

    // Local Variables
    float m_fFrameTime = 0.0f;
    float m_nCurrentFrame = 0;
    Vector2 m_vTextureOffset = new Vector2();

These are a few member variables are helpful to have handy, they keep track of what frame the animation is currently on, the time that is passing between frames, and what part of the texture is showing. This is a good opportunity to talk a little bit about variable types. Every programming language have it’s own variations on how it handles variables, we are using C# which is a ‘typed‘ language for variables. That means you have to be specific on the type of a variable when creating one.

Fortunately, there are a pretty common set of built-in types for most programming languages:

  • int: integer a whole number
  • float: a number with a decimal point
  • char: a single character
  • string: a string (set) of characters
  • bool: a true or false ‘boolean’

A full list of C# built-in types can be found here: http://msdn.microsoft.com/en-us/library/cs7y5x0x(v=vs.90).aspx

        // Keep track of how much time has passed in the current frame
        m_fFrameTime += Time.deltaTime;
        // If we have shown the current frame for more than 0.2 seconds
        if(m_fFrameTime > 0.2f)
        {
            // Cut off the frame time
            m_fFrameTime -= 0.2f;
            // Increment the frame number
            m_nCurrentFrame = (m_nCurrentFrame + 1) % 4;
            …

The update loop starts by keeping track of how much time has passed ‘deltaTime’ since the last update call. There are a few options on how to do the timing for games, this project is going to use game time to do the animation timing. As with the ‘Input’ global variable, you can also access ‘Time’ information globally.  By using time to determine the frame rate you will keep the animations looking consistent even if the frame rate of the game fluctuates.

A quick note about a lot of the numbers being used here. They are what programmers often refer to as ‘Magic Numbers’, which are in most cases evil, bad, and shouldn’t be used. They are called ‘magic’ because the programmer picked numbers to work instead of using variables and assumes they will always work ‘magically’ for all situations. By choosing 0.2f as the magic number it causes the sprite to change frames 5 frames per second.

// Set the texture position
 m_vTextureOffset.x = 0.25f * m_nCurrentFrame;
 renderer.material.SetTextureOffset("_MainTex", m_vTextureOffset);

To make the animation cycle the offset into the texture is shifted over based on the current frame. If you had variations in the size of the animation frames, you would have to have a list offsets, with a fix sizes simple math can be used.

At this point in the project when you try running it, it should look a lot like this.

As cool as that may look, out of context it feels a little rough just running in the back of the screen without any context. Adding a background should help a lot with that, you may recognize this background from the last project:

It’s still full of stars.

It’s still a good background for this project so no reason not to use it, put it into the project the same way that was done in the first workshop.

  • Create a plane: Object Hierarcy → Create → Plane → Rename to ‘Background’
  • Set the Camera to Orthographic size 10
  • Import a texture: Drag into Unity → Drag onto ‘Background’ plane
  • Rotate and Scale the plane to fill the camera

Double check your editor looks something like this:

Step 2: The background is back.

You now would be faced with a choice on how to animate this background, either by adding more options to the ‘SpriteAnimation’ script or by creating a new script all together. There is a trade off whenever you make this decision.  Some it lands on the side of the platform you are working on, there may be cases where you are limited to size or quantity of files.  Another factor is human readability, is it easier sort through one 10,000 line file or 10 files that are 1,000 lines each.  For these workshop we will lean heavliy to the use of multiple files keeping every distinct aspect separate as much as possible.

Create a new script, name it ‘TextureAnimation’ (or import it) and drop an instance of it onto the background. Switch over to the MonoDevelop editor. The texture animation done here at a fixed rate, it is similar to the ‘SpriteAnimation’ but it does not use a frame count, instead it simply moves the texture offset based on the update time.

using UnityEngine;
using System.Collections;

public class TextureAnimation: MonoBehaviour
{
    // Local Variables
    public float m_fScrollSpeed = 0.2f;
    Vector2 m_vTextureOffset = new Vector2();

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update ()
    {
        // Scroll the texture on the x-offset but keep the value under 1.0
        m_vTextureOffset.x = (m_vTextureOffset.x + Time.deltaTime * m_fScrollSpeed) % 1.0f;
        // Apply it to the material
        renderer.material.SetTextureOffset("_MainTex", m_vTextureOffset);
    }
}

There is one symbol here that has been used a few times, and that is the ‘%’ or percent sign. In most programming languages this does not represent a percentage instead it stands for ‘mod’ or ‘modular‘, notice how the percent sign looks like it has the ‘/’ symbol for division in it? That’s intentional, here division is perform as normal but instead of normal division, only the remainder is kept. You have probably done this math before such as converting military time into twelve hour time, 18:00 hours is 6 p.m., not 1.5 a.m.s

With this script on your background, running the project should look like this.

The good news is that your runner looks like it is really moving through the world, the bad news is most games lie to their players.  A lot of what goes into make games is ‘smoke and mirrors’ you maybe surprised how much of this there is as you progress through the workshops.

There is one more bit of animation to add often referred to as ‘Tweening‘, or the moving, rotating, modifying of an object over time. To show this technique setup a new box object and name it ‘cloud’ below is the place holder art, don’t forget the transparency setting.

Silver Lining Included

After adding that your project should be close to this:

Step 3: A dark and cloudy night.

Again, you are faced with the option of modifying existing code or adding a new script, as before this workshop is sticking with small scrips with narrow scope. The name for the new script you need to create (or import) is ‘TweenAnimation’

using UnityEngine;
using System.Collections;

public class TweenAnimation : MonoBehaviour
{
    // Local Variables
    public float m_fXSpeed = -10.0f;
    public bool m_bLoop = true;
    public float m_fRotateSpeed = 0.0f;

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () 
    {
        // Move and rotate the object every update
        transform.Translate(m_fXSpeed * Time.deltaTime, 0.0f, 0.0f, Space.World);
        transform.Rotate(Vector3.forward, m_fRotateSpeed * Time.deltaTime);

        // If we are past the end of the screen
        if(transform.position.x < -20.0f)
        {
            transform.position = new Vector3(20.0f, transform.position.y, transform.position.z);
            gameObject.BroadcastMessage("TweenLoop");
        }
    }

    // Do nothing, this function is here to hide the warning
    //  on objects that don't have a use for 'TweenLoop' broadcast
    void TweenLoop() { }
}

With this code, you have handled three of the most common forms of 2D animation. Frame based animation on the player character, an animated texture on the background, and tweening animation on the cloud. The best part is that you are not limited to using just one, by combining scripts and settings you have a lot of flexibility to explore later.

Your project should look something like this.

Adding the extra element of the cloud moving at a speed slightly faster than the background creates a layering effect which enhances the appearance of the person running. This effect is known as ‘parallax‘ the optical illusion that objects closer to the viewer appear to pass faster than objects off in the distance.

This is something seen in everyday life for example riding in a car, if you fix your eyes on a tall building far in the distance it will stay in your view much longer than a street sign that is only in sight for fraction of a second. This is what creates the sense of speed when playing a racing game or a 2D side scroller.

All that animation looks good, but at this point, you have probably noticed that there is no gameplay yet in this project, fortunately now is a great time to add some. As with the last workshop, most gameplay requires some input from the player, meaning you should start by creating (or import) a new script ‘CharacterControl’ and load it up into MonoDevelop.

As the name would lead you to believe an infinite runner is just that, a game where the player never stops running. The player control this time will be jumping and rolling which will be used avoid obstacles that will be added in just a bit. The new script file will look very close to what was used in the previous project for player input.

[RequireComponent (typeof (Rigidbody))]
public class CharacterControl : MonoBehaviour
{
    public enum enPlayerState
    {
        enRun,
        enJump,
        enRoll
    }
    enPlayerState m_enPlayerState = enPlayerState.enRun;
    Vector3 m_vRollScale = new Vector3(1.0f, 1.0f, 1.0f);
    public float m_fJumpForce = 5.0f;

    // Use this for initialization
    void Start ()
    {
        rigidbody.useGravity = false;
    }

    // Update is called once per frame
    void Update ()
    {
        // The update is dependant on the state the player is
        switch(m_enPlayerState)
        {
            // What to do if the player is in jump state
            case enPlayerState.enJump:
            {
                // Anything above 2.0f is considered mid jump
                if(rigidbody.position.y < 2.0f)
                {
                    // Reset the rigid body
                    rigidbody.useGravity = false;
                    rigidbody.position = new Vector3(transform.position.x, 2.0f, transform.position.z);
                    rigidbody.velocity = new Vector3(0.0f, 0.0f, 0.0f);

                    // Reset the player state
                    m_enPlayerState = enPlayerState.enRun;
                    gameObject.BroadcastMessage("OnStateChange", (int)m_enPlayerState);
                }
            }
            break;

            // What to do if the player is in roll state
            case enPlayerState.enRoll:
            {
                // Roll lasts until the player lets go of the button / stick
                if(!Input.GetButton("Fire2") && (Input.GetAxis("Vertical") > -0.5f))
                {
                    // Reset the position and scale from rolling
                    transform.localScale += m_vRollScale;
                    rigidbody.position = new Vector3(transform.position.x, 2.0f, transform.position.z);

                    // Reset the player state
                    m_enPlayerState = enPlayerState.enRun;
                    gameObject.BroadcastMessage("OnStateChange", (int)m_enPlayerState);
                }
            }
            break;

            // What to do in all other cases
            default:
            {
                // Check jump first, it takes priority over roll
                if(Input.GetButton("Fire1") || (Input.GetAxis("Vertical") > 0.5f))
                {
                    m_enPlayerState = enPlayerState.enJump;
                    rigidbody.AddForce(0.0f, m_fJumpForce, 0.0f, ForceMode.Impulse);
                    rigidbody.useGravity = true;
                    // Broadcast the change to our animation state
                    gameObject.BroadcastMessage("OnStateChange", (int)m_enPlayerState);

                }
                // No jump?  Check roll then
                else if(Input.GetButton("Fire2") || (Input.GetAxis("Vertical") < -0.5f))
                {
                    m_enPlayerState = enPlayerState.enRoll;
                    transform.localScale -= m_vRollScale;  // Scale the player down
                    rigidbody.position = new Vector3(transform.position.x, 1.5f, transform.position.z);
                    // Broadcast the change to our animation state
                    gameObject.BroadcastMessage("OnStateChange", (int)m_enPlayerState);
                }
            }
            break;
        }
    }
}

This is by far the largest script used for a project, most of it will look very similar but there are some new elements, such as:

[RequireComponent (typeof (Rigidbody))]

This part at the very top is a lot more powerful that what it may seem. Up until now, if an object needed a component it was either created in a script or manually placed in the editor. This line here will cause the script to only work on an object that has a ‘Rigidbody’ component, it will create one automatically when the script is placed on the player object. As the projects get more complicated you’ll see this used a lot more.

    public enum enPlayerState
    {
        enRun,
        enJump,
        enRoll
    }
    enPlayerState m_enPlayerState = enPlayerState.enRun;

This is a new type of variable, the ‘enum’ or enumeration, a way to make code easier for humans to read and understand. By default the items here are given numbers in order they are listed starting with zero, so enRoll would equal 2. In large code bases it is much easier to search for ‘enRoll’ instead of the number ‘2’ and get usable results, also if the order changes later all uses of the enum values are still valid and do not need to be updated.

    // Update is called once per frame
    void Update ()
    {
        // The update is dependant on the state the player is
        switch(m_enPlayerState)
        {
            // What to do if the player is in jump state
            case enPlayerState.enJump:
                ...
            break;

            // What to do if the player is in roll state
            case enPlayerState.enRoll:
                ...
            break;

            // What to do in all other cases
            default:
                ...
            break;
        }
    }

The ‘switch‘ statement is a new way to control the flow of code, in some ways it is very close to how you have been using the ‘if’ and ‘if -> else’. Switch statements work by giving it a variable at the start of the switch statement and then using multiple ‘case’ conditionals to check and run the first one that matches. The code in ‘Default’ is called if no match is found amongst the case conditionals. The ‘break’ keyword here ends the switch statement and the flow of code continues after the end of the switch statement. If there is no ‘break’ statement the code will continue into the next ‘case’ until it reaches a ‘break’ if any.

Note: C# does not support ‘fallthrough’ in switch statements which makes them less useful than in other languages such as ‘JavaScript’ one of the other major languages for Unity. Because of this, the use of ‘switch’ statements in this workshop ‘switch’ and ‘if->else’ is a style and readability choice.

With that covered, it is time to look at what is actually happening here. You are now dipping your toe into creating a ‘state machine’. A ‘State Machine‘ is set of rules built for how objects, such as in this case the player, can interact in the world. Some state machines are trivial, such as a light switch being on or off, others are much more complex. In our use, the player state machine is very simple:

You are in the ‘reading’ state

If the player is in the running state they can exit the state by pressing the ‘jump’ button or holding the ‘roll’ button. However, the state machine will not allow a jumping player to roll or vice versa. You can see this in the ‘default’ case in the switch statement.

                // Check jump first, it takes priority over roll
                if(Input.GetButton("Fire1") || (Input.GetAxis("Vertical") > 0.5f))
                {
                    m_enPlayerState = enPlayerState.enJump;
                    rigidbody.AddForce(0.0f, m_fJumpForce, 0.0f, ForceMode.Impulse);
                    rigidbody.useGravity = true;
                    // Broadcast the change to our animation state
                    gameObject.BroadcastMessage("OnStateChange", (int)m_enPlayerState);

                }
                // No jump?  Check roll then
                else if(Input.GetButton("Fire2") || (Input.GetAxis("Vertical") < -0.5f))
                {
                    m_enPlayerState = enPlayerState.enRoll;
                    transform.localScale -= m_vRollScale;  // Scale the player down
                    rigidbody.position = new Vector3(transform.position.x, 1.5f, transform.position.z);
                    // Broadcast the change to our animation state
                    gameObject.BroadcastMessage("OnStateChange", (int)m_enPlayerState);
                }

The code will only reach this if the player isn’t in the jump or roll state, and then here it checks the input to determine if there will be a state change or not. Notice how the ordering of the if statements sets the priority of actions this is something to be aware of when making more complex state machines.

Inside of each of the conditionals, a few changes are made to the character to reflect the state change. In the jump, that rigidbody that the script will add is given an upwards force as an ‘impulse’ or shove upwards, and gravity is turn on to give the jump a nice parabolic arc. For the roll, the player is scaled down slightly to avoid potentially low obstacles, since the position of the player in this case is taken from center, the position is also offset slightly.

However, if the player was instead already in one of these states, the code flow would instead check to see if the player meet the conditions to return to the running state:

            // What to do if the player is in jump state
            case enPlayerState.enJump:
            {
                // Anything above 2.0f is considered mid jump
                if(rigidbody.position.y < 2.0f)
                {
                    ...
                }
            }
            break;

or

            // What to do if the player is in roll state
            case enPlayerState.enRoll:
            {
                // Roll lasts until the player lets go of the button / stick
                if(!Input.GetButton("Fire2") && (Input.GetAxis("Vertical") > -0.5f))
                {
                    ...
                }
            }
            break;

When the conditions are met that causes the state machine to return to ‘enRunning’, the changes made to enter the states such as scale or gravity are returned to normal. Through out all of this, whenever there was a state change, the code would broadcast it to the object:

gameObject.BroadcastMessage("OnStateChange", (int)m_enPlayerState);

Earlier we mentioned that C# is a typed language, and you need to be exact when using variables. Now, until the inevitable AI uprising, we can force a computer to believe us when it comes to what something is. This is done by ‘casting’ here the ‘m_enPlayerState’, which is an enum not an int, is be forced into the role of an int by the casting caused by the ‘(int)’ in front of it. Now to make something that listens for this message, to do that, open up the SpriteAnimation script file. (or import SpriteAnimation_02.cs as ‘SpriteAnimation’)

using UnityEngine;
using System.Collections;

public class SpriteAnimation : MonoBehaviour
{
    // Local Variables
    float m_fFrameTime = 0.0f;
    float m_nCurrentFrame = 0;
    Vector2 m_vTextureOffset = new Vector2();
    float[] m_afAnimStates = new float[] { 0.5f, 0.0f, 0.0f };

    // Use this for initialization
    void Start ()
    {
        // Set the tiling value
        renderer.material.SetTextureScale("_MainTex", new Vector2(0.25f, 0.5f));
        // Set the initial state
        OnStateChange(0);  
    }

    // Update is called once per frame
    void Update()
    {
        // Keep track of how much time has passed in the current frame
        m_fFrameTime += Time.deltaTime;
        // If we have shown the current frame for more than 0.2 seconds
        if(m_fFrameTime > 0.2f)
        {
            // Cut off the frame time
            m_fFrameTime -= 0.2f;
            // Increment the frame number
            m_nCurrentFrame = (m_nCurrentFrame + 1) % 4;
            // Set the texture position
            m_vTextureOffset.x = 0.25f * m_nCurrentFrame;
            renderer.material.SetTextureOffset("_MainTex", m_vTextureOffset);
        }
    }

    void OnStateChange(int nStateIndex)
    {
        m_vTextureOffset.y = m_afAnimStates[nStateIndex];
        renderer.material.SetTextureOffset("_MainTex", m_vTextureOffset);
    }
}

Just a few small changes here, first a new local variable.

  float[] m_afAnimStates = new float[] { 0.5f, 0.0f, 0.0f };

This is a member variable that is an array of floats ‘m_af’ notation. An ‘array‘ is an order collection of similar elements, like a row of mail boxes. All the mail boxes are in order by number and hold similar objects, however unlike city mailboxes, arrays don’t typically have gaps in the numbering. In our usage here, the array is initialized like this:

m_afAnimStates[0] = 0.5f;
m_afAnimStates[1] = 0.0f;
m_afAnimStates[2] = 0.0f;

Computers almost always start counting with zero, so this array has a length of three elements, the index for the last one is 2 (length – 1). This array is used lower in the ‘OnStateChange’ listener function.

    void OnStateChange(int nStateIndex)
    {
        m_vTextureOffset.y = m_afAnimStates[nStateIndex];
        renderer.material.SetTextureOffset("_MainTex", m_vTextureOffset);
    }

A small amount of code that does some interesting work. First it receives the player state from the CharacterControl script as an integer, it then uses that value to pick that right animation offset for that state and applies it to the material. Another way to look at it would be:

m_afAnimStates[enRun] = 0.5f;
m_afAnimStates[enJump] = 0.0f;
m_afAnimStates[enRoll] = 0.0f;

Why are two of the states using the same value? Simply because the new texture for the workshop doesn’t include a separate set of images for the roll and jump state. Something that could be added later by those artistically inclined.

Speaking of new textures, it’s time to switch back to the Unity editor, to import this new texture and apply it along with the character control script to the player object.

On a roll now

This time you do not have to adjust the tiling, that is now being done in the start function of the SpriteAnimation function, but it might look a bit odd in the editor. If everything is going well your editor should look something like this:

Step 4: Ready to roll

Give the project a quick test and try out jumping and sliding. Now you may have noticed that since the object now has a rigidbody it is colliding with the cloud or background. Worth a laugh when that happens, but not really useful for the project, uncheck the ‘Box Collider’ the cloud and background, and you should have a project that plays like this.

For the last few steps of this project, it is time to something for the player to dodge. For this ‘enemy’ the place holder art to use is:

A sharp foe

Create a cube with this texture in the world and then drop the ‘TweenAnimation’ script file on it, then switch over to the MonoDevelop to take a look at some unused parts from earlier.

        // Move and rotate the object every update
        transform.Translate(m_fXSpeed * Time.deltaTime, 0.0f, 0.0f, Space.World);
        transform.Rotate(Vector3.forward, m_fRotateSpeed * Time.deltaTime);

Adding a rotation option to the tween really adds a lot to the appears of this spike ball. Notice here that this is a new way to use the ‘Translate’ function. Using the ‘Space.World’ key word, it has the object ignore it’s own rotation which is changing constantly as the object rotates, and instead moves in a fixed direction. A bit like wheels on a car, their rotation is changing but the movement of the car is independent of the that rotation.

Now create a new script (or import), called ‘Enemy’ and fill it in with the following:

using UnityEngine;
using System.Collections;

[RequireComponent (typeof (AudioSource))]
[RequireComponent (typeof (Rigidbody))]
public class Enemy : MonoBehaviour
{
    AudioSource m_pAudioSource;

    // Use this for initialization
    void Start ()
    {
        rigidbody.useGravity = false;
        m_pAudioSource = (AudioSource)gameObject.GetComponent("AudioSource");
    }

    // Update is called once per frame
    void Update () {

    }

    // Receiver for TweenLoop broadcast
    void TweenLoop()
    {
        // Pick a random position to make the player need to roll or jump to avoid this object
        transform.position = new Vector3(transform.position.x, Random.Range(2.0f, 10.0f), 0.0f);
    }

    // Action To Perform when a rigid body is touching this object
    void OnTriggerEnter(Collider pOther)
    {
        // Play the 'Buzz' sound on collision
        if(!m_pAudioSource.isPlaying)
            m_pAudioSource.Play();
    }
}

There is a new component here the ‘AudioSource’ which will be looked at in detail later, but for now a quick peek at the code. You will see the ‘TweenLoop’ function:

    // Receiver for TweenLoop broadcast
    void TweenLoop()
    {
        // Pick a random position to make the player need to roll or jump to avoid this object
        transform.position = new Vector3(transform.position.x, Random.Range(2.0f, 10.0f), 0.0f);
    }

Every time the object loops around, it’s position is randomly picked for the next pass to cause the player need to dodge out of the way. Additionally there is the ‘OnTriggerEnter’ function that you should recognize from the previous workshop from the ‘Projectile’

    // Action To Perform when a rigid body is touching this object
    void OnTriggerEnter(Collider pOther)
    {
        // Play the 'Buzz' sound on collision
        if(!m_pAudioSource.isPlaying)
            m_pAudioSource.Play();
    }

Only instead of causing damage, it simply plays a sound effect. The place holder sound effect was created from one of many free tools, in this cause ‘Audacity’ was used to make this simple recording.

Bzzzz

Grab that sound effect, and place it into the project the same way you would for a texture, you should create a ‘Sounds’ folder first to keep everything organized. Then drop the ‘Enemy’ script you just created onto the ‘Enemy’ cube you added earlier and take a look at the component inspector. Pick the sound effect and uncheck ‘Play on awake’ inside of the ‘Audio Source’ component.

Step 5: Sounds like a plan

All that is left now is to run the project and try it out. If everything is working well it should play just like this.

That’s where work shop 2 will end, take any remaining time to try out your own customizations. Consider adding techniques from the first work shop such as being able to shoot the enemy or using the sin wave to make it harder to dodge. In the next work shop we will be taking the first steps into some AI, VFX, and UI by making a dual stick shooter.

Unity3D Workshop – 01 – Invading Space

Hello and welcome to this game development workshop, for this series we will be using the ‘Unity 3D’ engine (4.0.1 at time of writing) to be the backbone of our projects. The goal across all of these workshops is to learn what elements go into making a game, and some of the roles that are available to you as part of making games. Since we are going to be using the Unity3D engine before we get too far into introductions, let’s take a look at the engine itself.

Open up your web browser and download the latest version of Unity,  available here: http://unity3d.com/unity/download/

So you may be wondering, “What is Unity3D, and why are we using it?”.  Unity3D is a powerful game creation editor and engine that can be deployed to multiple platforms. There are numerous SDKs (Software Development Kits)  that are either free or low-priced (especially for students) available for building games including: GameMaker, Flash, Unreal, XNA, and others. We’ll be using Unity because while free, and though it does do much of the ground work for us, it still leaves enough for us to dig in and get our hands dirty. Many of the retail games you may be familiar with are made with Unity for a variety of platforms and devices. You can see some of the big ones on their site: http://unity3d.com/gallery/made-with-unity/game-list

As we make our way through these workshops we will examine not only programming but art, sound, design and many other areas of game development. You may not feel skilled in every area of game development but I encourage you to take part in every exercise. In a large-scale professional setting, it is very likely that you would be using an engine and/or tools created in house. The experience you gain from these workshops is designed to line up with what you would experience as an entry-level developer. Working on any size project with a team and having a basic understanding of the various roles you may encounter and their tasks will aid you in communication.  Remember, there is no grade at the end of this workshop, placeholder assets and full code examples will be available for all the steps should you find yourself struggling. So you have nothing to lose!

Once everything is downloaded and up and running, let’s take a quick look around the Unity editor. We will be spending a lot of time in there so it will be good to be familiar with how to navigate it. All of the reference screenshots will be in the default layout, however there are many different pre-sets and you are free to dock windows wherever you like, this is a great feature, especially if you’re using multiple monitors. Let’s make one small change to the default layout, pull the game preview tab out from behind and dock it to the right of the assets panel, this can be done by left clicking on the table and dragging it into position.

Step 0: Editor Customization

With each workshop we are going to look a few existing games that share common gameplay style and features.  This being the first workshop, we are going back almost to the beginning of video game history with a 1978 classic, designed by Tomohiro Nishikado. The game of course, is ‘Space Invaders‘ a game that features gameplay design that is still used today.

Space Invaders – 1928 Titan Attack – 2012

The gameplay here is the player controls an object that is locked to a single axis of movement and fires projectiles at enemies who move about the screen and can fire back.  Hundreds of games have use this concept with minor changes, for example, simply rotate the game 90 or 180 degrees and you have a completely different feeling game.

To start making our own version, we are going to setup a simple background image for our project to use.  Now as you might not have known, the original arcade version of ‘Space Invaders’ had no background, later it was faked by using a piece of cardboard. With the advances in hardware since the 1970s we can avoid the use of cardboard for making our background. Instead, we will use one of the default objects that are part of the Unity editor, the ‘Plane’. Creating one is simply click on the create button in the from the Hierarchy tab on the left and selecting plane from the pull down menu.

Step 1: Adding the plane to our project.

This creates a new ‘plane’ object in the world and adds it to the list of items in our ‘object hierarchy’, your screen should look something like this:

Step 2: Plane to see

Now there is a lot of new information here, our ‘Plane’ is made up a grid of 10×10 unit squares each made up of a pair of triangles, you can see the wire frame that makes up the object when it selected.  Additionally, the object inspector on the right is loaded with all sorts of new options, which are organized as ‘Components’.  Unity3D is best when used as a composition based object creation, objects are built by combining independent parts.  Roughly the difference between building a toy car out of blocks instead of carving one out of a single piece of wood.  Our plane being a built in object comes preloaded with a few default components that are common enough to assume that we will want to have them.

Before we start working with that, a brief disclaimer: we will not be focusing on optimizations this early in the workshop.  While, internally I would be screaming if I found someone using 200 polygons to make a square that could be done with only 2 triangles, don’t worry, we’ll come back around to this later.  When dealing with 3D graphics, polygons, most often triangles, are the basic building blocks. While definitely not the only aspect that goes into the ‘quality’ of graphics it is an important part. For the vast majority of this project we will be dealing with games in 2D, we will touch on the importing of 3D models for use later in the workshops.

Right now all we have is a simple gray square that is not very exciting and offers very little gameplay value.  This drives us to another of the major elements that is needed for our project’s graphics, which is the texture. In our case with today’s project being fully 2D the texture will be pretty much all that determines our graphics. Also for today’s project, we will be working with an 16:9 aspect ratio. This is relative number of pixels, 9 pixels vertically for every 16 pixels horizontally. This aspect ratio is very common and likely what is used for any monitor, television, or phone that you may use.

Here is the placeholder background that will be used in any of the following screenshots, make sure to get the full size version and not the thumbnail:

A star filled sky.

As a professional, I am a programmer not an artist as you can easily see.  So this is a great chance try your hand at making your own textures. I used MSPaint to make my background, but there are numerous tools out their you can use to make a texture. For our needs, anything that can save a file in ‘.png’ format will work, and for the background I recommend using a canvas size of 1280×720, or any image with a 16:9 aspect ratio so it won’t be stretched unevenly will work just fine.

Importing assets into our project is as simple as dragging the image file from where it is saved into the project tab in the bottom left. It will take a brief second to import and we will be able to use it.  I had already made a folder for textures and scripts to help me organize the assets for the project, we will only be using a few assets for this so you could do fine with out folders, but building a good habit for this will be a huge time saver down the road.

To create a folder, simply click on ‘Create’ right under project tab in the bottom left and select folder which you can then immediately rename.

Step 3: Everything in it’s place.

To apply it to our ‘Plane’ it is the nearly the same process, you can just drag the texture from the assets folders and drop it onto the ‘Plane’ in our either in the object hierarchy or the object in the editor view. While were in that area, we should rename our ‘Plane’ to something more appropriate such as ‘Background’, right clicking on the object name will present a context menu with the ‘Rename’ option.

If everything is still on track, we should all be seeing something close to this:

Step 4: A plane with a name.

That’s a good bit of progress for a little mouse work, a texture on that gray square goes a long way. However, we’re still not seeing anything down in the game preview window at the bottom. This is because our plane is two dimensional and exactly in line with our camera, it would be essentially holding up a piece of paper exactly at our eye level. Except in this case, it is the game camera not our eye, and the piece of paper is infinitely thin, which makes it effectively invisible.

Easy enough problem to fix, you can either move the camera or the ‘Background’, in this case we will do the latter. Pressing ‘e’ which is the hot-key for rotate mode in the editor we can click the rotation rings and adjust our objects rotation. Holding down ‘control’ will lock the rotation into 15degree increments. For a complete list of all the hot keys you can refer to this list.

You may have notice that depending on which way the object rotated, that it disappeared even from our editor window.  The reason for that is the default setup for polygons used in the editor are ‘Front Facing’, this means that you can only see them if you are viewing them from their front, similar to how a one way mirror works.

With a little trial and error, you should be able to find that the target rotation you want for the background to face the camera is ( 90:X, 180:Y, 0:Z ).  If you are not seeing this, double check that the ‘Background’ object position is at the origin (0:X, 0:Y, 0:Z) and the camera is positioned a bit back on the Z axis (0:X, 0:Y, -10:Z) and not rotated. It is very easy to nudge things out of position, and lose sight of objects.

Step 5: A texture book example

At this point you may be wondering why the texture is looking a bit darker than the source file, the problem is a lack of lighting. With a quick couple of clicks you can adjust the ambient lighting to a solid white light.  Another option would have have been changing the textures to be ‘unlit’ and draw fully without reference the project’s lighting, but since this project is not doing any complex lighting making this global change is the simpler solution.

Step 6: Let there be light.

The difference is night and day, and makes it real easy to see what is going on, but before we continue with camera changes we are going to toss a few generic objects into our project to help us visualize the next change. The example I used was a pair of cubes added the same way we did for the plane.  The are positioned slightly offset from each other, enough to see both and have them appear to be different sizes in the ‘Game’ preview window.

Having these cubes here will help us highly the differences between a ‘perspective‘ camera and an ‘orthographic‘ one. The perspective camera uses all three dimensions when drawing an object, just like the human eye works, objects look smaller the farther away they are. By moving the cube around in the editor you should see it grow smaller or larger in the game preview, despite the objects being the same size.

The ‘orthographic’ camera removes this effect, and sees everything only in two dimensions, so the by switching the camera to that mode, the two squares snap back to appearing as the same size, as highlighted in the image below.

How to remove a dimension.

To switch camera modes, you need to select the ‘Main Camera’ out of the object hierarchy as you would do for any other object and then adjust the ‘Projection’ setting in the object inspector. You may have also noticed that the ‘Depth of Field’ setting also changed to ‘Size’. Think of this ‘Size’ as if you were looking at piece of graph paper, every square in one direction would be one unit of ‘Size’, for our project we will be using a ‘Size’ of 10 mainly to keep the math simple. This gives us a total of ‘Size’ of 20 units 10 above and 10 below the center of the camera on the y-axis.

The math on the x-axis is a little more complex, as you recall we are using a 16:9 aspect ratio for our game. Let’s switch the preview window to that mode but adjusting it in the pull down menu which currently says ‘Free Aspect’.

Step 7: Not what you aspected

To figure out how big our space is on the x-axis it takes just a bit of math:

20 (total vertical height) / 9 (vertical aspect) * 16 (horizontal aspect) = 35.55~ units wide.

A total size 20 x 35.55~ for the play area will be great for the project, however you may have noticed that our background does not fit fully into our preview window. To scale it up, just hop back over to the object in the hierarchy and then set the size in the ‘scale’ part of the transform area in the object inspector. If you remember from before, the plane was a 10×10 grid of squares, and luckily, each one of the squares is the same size of the unit used by the camera, this makes scaling the background very simple. Setting the scale to (3.55:X, 1:Y, 2:Z) will fit the plane perfectly filling the camera’s view.

Step 8: Drawn to scale

Despite the fact that many believe that programmers are supposed to love math, most really only love simple math. To help keep things simple, take a quick second to move the positions of the camera and the background to be positive 10 on the y-axis, it’ll pay off later. As for the two cube companions, only one of them can survive to the next step, delete one from the object hierarchy and rename the survivor as ‘Player’ because it’s time to put some game into this project.

The player object is going to need it’s own texture, as with the background you can use anything you would like for the the player. However, the player is going to keep it’s square shape, so the texture we will use is a 1:1 aspect ratio. Below is the placeholder texture to use, if you are making your own, 128×128 pixels is the recommended size.

The goal was a tank.

Just like before, smoothly drag the texture into the proper folder inside of the Unity3D editor, and then drag that texture onto the ‘Player’ object. Unlike the background, which was solid in it’s entirety, the player object needs to make use of ‘transparency’ or the ‘alpha‘ channel.  The .png files used for this project have an alpha layer which dictates what parts are see through or not.  Other setups are done using the ‘green screen‘ method picking once color of the image to be ignored or replaced.

A single setting takes care of it in a snap. If this does not work for you custom texture, make sure the editor you are using allows for transparency when saving the file as a .png

Step 9: Transparency in game development

As you can see, while setting the transparency, it was also a good time to rotate the object so it was facing up right to the camera, scale it up a bit, and move it’s position up to where it looked good on the background, all things doable in the ‘Transform’ component of the inspector.

Now up to this point, you have done everything in the project by just composting elements together by dragging and dropping followed up by small modifications in the inspector. However, the time to do some coding has arrived, and to do that you will need to make a script, and for this workshop you will be working with C# as the default language. To make a script once again use the create button in the project tab and select ‘C# Script’ then name it ‘PlayerInput’.  In the example image the script was created in an aptly named ‘Scripts’ folder.

Step 10: Stick to the script

Scripts can not be edited in the primary Unity3D editor, it will require a much more complete text editor.  The good news is that Unity comes with it’s own script editor called ‘MonoDevelop’ and will open automatically if you double click on the script you just made.

Step 11: Or step 3 in binary

The Unity engine already handles much of the low level input for you, everything you need for this project is already setup by default.  However, a quick quick look at these settings to get familiar with where they can be found later, is a good idea.

Step 12: Seizing control

Notice how there are already two definitions for ‘Horizontal’? This is because the Unity engine supports publishing for many platforms and devices, being able to use the same name for input saves a lot of time when publishing cross platform.

Switching back to MonoDevelop you just need to add a few lines to our Update function. The player input can be read from the global ‘Input’ object by asking it for the ‘Horizontal’ axis value.  The Unity engine and C# already provide many functions and abilities for you to use.  In this case ‘Input’ is a common element for all projects, and is known as a ‘Global’ object mean it can be found and accessed everywhere.

float fXInput = Input.GetAxis("Horizontal");

To adjust the object’s position, which is stored as part of the ‘transform’ component on the object, this is the same transform component you have been making changes to inside of the editor up to this point, you are just accessing it from code while the project is running.

transform.Translate(-fXInput, 0.0f, 0.0f);

Put both of the lines together in the update function and you end up with:

    // Update is called once per frame
    void Update ()
    {
        // We are going to read the input every frame
        float fXInput = Input.GetAxis("Horizontal");
        // We move the player left or right
        transform.Translate(-fXInput, 0.0f, 0.0f);
    }

One thing to not here, the lines that start with two slashes, colored green in this example are known as ‘Comments’ these are notes left by developers to remember what code is or at least should be doing, they are ignored by anything non-human.

Save this file in MonoDevelop, and switch back to the Unity editor and drag the script from the assets folders onto the ‘Player’ object exactly how you did for the texture.  Now you can test the project to make sure that your script is working, simply click the play arrow at the top of the screen and you should then be able to make the player object move left and right by using the arrow keys, a / d, or a connected gamepad, our first bit of gameplay.

It should look very close to this example.

Not bad for just two lines of codes, but there are definitely a lot that can still be done, first off issue to address is that it is very easy to slide right off the edge of the screen. The fix for that is very strait forward, switching back to MonoDevelop and the ‘Update’ function in the ‘PlayerInput’ file you can add in some checks to keep the player inside of the play space.

    // Update is called once per frame
    void Update ()
    {
        ...

        // Make sure we're inside the bounds of our play space
        //  NOTE: This is a non-optimal solution
        if(transform.position.x < -14.0f)
        {
            transform.position = new Vector3(-14.0f, 2.0f, 0.0f);
        }
        else if(transform.position.x > 14.0f)
        {
            transform.position = new Vector3(14.0f, 2.0f, 0.0f);
        }
    }

Working with computers is purely binary, or ‘yes’ and ‘no’ type interactions. You have just used a pair of very simple conditional statements which read like math problems comparing two numbers or yes/no questions. Such as:

if(transform.position.x < -14.0f)
{
  .A.
}
else if(transform.position.x > 14.0f)
{
  .B.
}

Translating such conditionals to speakable english would be:

if our position is less than -14 do ‘A’
else if our position is greater than 14 do ‘B’

Breaking problems down in basic yes/no logic is a skill that will always be useful to a programmer, there are more tools and complex conditionals and they will be touched as needed.

Congratulations, with these two conditionals, you have just solved your first code bug. Notice that there is a comment pointing out that this code is a non-optimal solution, this will be a place for those interested in deeper code exploration to dig in and spend some time improving the code of others. A task that most programmers will undertake at one time or another.

This is also a good place where you can make a game play decision: “How far the player can move on the play space?” and “How fast can the player move?” simple questions like this make up a large part of game play balance and ultimately the ‘fun’ of a game.

This will be very clear when moving into the next feature to add to your project, that ability to fire a projectile. Many of these steps you have already done and as you likely guessed first you will need to start off with an image to represent the projectile. Below are two examples of possible place holder art:

Drop these into your texture folder or if you are creating your own stick with the name of ‘Projectile_01’ and ‘Projectile_02’ to stay consistent with the rest of the steps to follow. The place holder images above were created as 128×128 pixel images, also with transparency support. Up until this point, the Unity editor has handle all of the material creation for objects, these which were automatically generated when we placed the textures onto the objects.

While the texture is the raw data creating a picture, a material is a combination of many modifiers and often multiple textures.  You could have two coins in your pocket with the same image on them, but one might be shiny or rough or bumpy, these differences are what are combined to make materials.

In the case of the projectile the approaching will be a bit differently, you will want to make the materials by hand. Change to the material folder and create a material by either right clicking in the folder and navigating to create → material or by using the create pull down under the project tab. Then name it ‘Projectile_01’ and head over to the object inspector.

Inside the object inspector for our new material you can see it’s set to nothing, click on select and pick the projectile, then at the top where it says ‘Shader’ like with the player object, you want to select ‘Transparent → Diffuse’ as the material type.

Step 13: Material concerns

Repeat this process for the other projectile, and you should then have four different textures that look like this:

On a side note, the slider at the bottom right of the asset windows will allow you to scale the icons for each object, this can be handy when the folders start to fill up.

Next step is to create a fresh script, and call it ‘Projectile’, then open it up into MonoDevelop. Like before with the player object, this projectile needs to move to move, and that happens in the update function.

public class Projectile : MonoBehaviour
{
    // Local variables
    float m_fProjectileSpeed = 0.5f;

    ...

    // Update is called once per frame
    void Update ()
    {
        // Our project moves on it's own without needing player input
        transform.Translate(0.0f, m_fProjectileSpeed, 0.0f);

        if(transform.position.y > 21.0f)
            DestroyObject(gameObject);
    }
}

This was also a good time to set the default speed for the projectile, this will be something you can  customize.  Take not of the ‘DestroyObject’ function, this function is common to all objects in Unity and the use here is to have the object delete itself.  A process generally refereed to as ‘clean up’ or ‘garbage collection‘ some systems perform this task automatically but many performance conscious systems do not and leave that work to the programmers.  This is important because computers have limited resources such as ‘ram’ and if objects are not cleaned up it will cause the program to crash.

This is just what a projectile will do when it updates, to actually create a new projectile, you need to go back to our ‘PlayerInput’ script and setup code to listen for the ‘Fire1’ button.

public class PlayerInput : MonoBehaviour
{
    ...

    // Update is called once per frame
    void Update ()
    {
        ...

        // Check if the player press the fire button
        //  'left mouse' 'left control' or 'button 0' on the game pad
        bool bFirePressed = Input.GetButtonDown("Fire1");
        if(bFirePressed == true)
        {
            // Create a new shot
            GameObject pNewObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
            pNewObject.name = " Player_Projectile";
            pNewObject.AddComponent("Projectile");
        }
    }
}

Input buttons work differently than an analog axis would. They have states of ‘Up’ and ‘Down’ if you were making a game where holding down a button would charge a shot, using the time between ‘Down’ → ‘Up’ would be a great way to add that feature, something to remember for later.

Input buttons work differently than an analog axis would. They have states of ‘Up’ and ‘Down’ if you were making a game where holding down a button would charge a shot, using the time between ‘Down’ → ‘Up’ would be a great way to add that feature, something to remember for later.

Take a look at the new code that is being using to create a projectile line by line.

GameObject pNewObject = GameObject.CreatePrimitive(PrimitiveType.Cube);

Here a basic or ‘primitive’  cube is being used as the object for the new projectile, this is the code equivalent to adding a cube to the object hierarchy like you did for the player object.

pNewObject.name = " Player_Projectile";

The object is given a name, just as would rename it in the object hierarchy.  Even though the projectile objects only exist temporarily, having a descriptive name for it would make later problem solving and ‘debugging’ much easier.

pNewObject.AddComponent("Projectile");

Last but not least,  giving the new object the ‘Projectile’ script, which is referenced here as a component.  This would have been the same as placing the ‘Player Input’ script on ‘Player’ object. As you can see, you can accomplish much the same results by very different methods, the trick is knowing the right time to use each. In this case, the projectiles are short lived objects, unlike the player object which is persistent for the entire game. This makes the creation of the projectiles manually into the game space not a viable solution.

Take a quick moment to try out your project and see how well the projectiles work. A demo at this point is available here

Step 14: A long shot

On the plus side if you watch the space in the object hierarchy in the upper left, we see that the projectiles are being created when you press the fire button and then destroyed after they pass the top of the screen. On the down side, the projectiles are all being created from the center of the play space and that is definitely a bug in this feature, most players like to be able to aim.

Additionally, it is only shooting raw cubes, and not the one of the textures we setup earlier. Now you could go into the ‘Projectile’ script and assign the texture there. However, since we are are basing this off a game where the enemies fire back, a better solution would be make changes on the player object so we can reuse the projectile script for the enemies.

Back into ‘PlayerInput’ to add some new variables at the top of the file.

public class PlayerInput : MonoBehaviour
{
    // Projectile type
    public Material m_pProjectileType = null;
    public float m_fProjectileSpeed = 0.5f;

    …
}

The ‘m_pProjectileType’ here is a local ‘member’ variable and is defined outside the start function at the top of the file. The naming convention here is know as ‘Hungarian notation‘ now to be honest, there have been thousands of hours lost in the game industry debating what the best way to name things.

The reason for such a heavy emphasis on picking good names is that a good name helps you remember what you are working with. In this case, the ‘m’ before the underscore represents ‘member’ which means the variable is part of this object itself, while a ‘g’ would represent a variable that is globally accessible. The lower case ‘p’ designates this as a ‘pointer’ type, we will go into greater detail as we use these more.

One nice feature of the Unity editor, but placing the ‘public’ keyword in front of the the variables become visible back in the object inspector.

Step 15: Public Access

Here you can adjust the material to use on the projectiles created by the player, and the speed in which the projectiles move, select one of the projectile materials that you setup earlier. The ability to do this is a huge time saver as projects get larger allowing you to make changes quickly without having to adjust the scripts. Additionally, you could have different settings for the same script on different objects in the editor.

One more small change needs to be done in the ‘PlayerInput’ script before moving onto the ‘Projectile’ script. The process of actually telling the newly created projectile the settings that were just added to the Player object, and you do so like this:

public class PlayerInput : MonoBehaviour
{
    ...

    // Update is called once per frame
    void Update ()
    {
        ...

        if(bFirePressed == true)
        {
            // Create a new shot
            GameObject pNewObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
            pNewObject.name = " Player_Projectile";
            Projectile pNewProjectile = (Projectile)pNewObject.AddComponent("Projectile");
            pNewProjectile.Init(gameObject, m_pProjectileType, m_fProjectileSpeed);
        }
    }
}

Now when a new projectile is created, it is given the a few of the values saved by the object that created it.  A quick note, the MonoDevelop might give you a warning that ‘Init’ is not a valid function to call on a ‘Projectile’ object, don’t worry that is to be expected be cause you are going to add that now by switching over to the ‘Projectile’ script.

public class Projectile : MonoBehaviour
{
    // Local variables
    GameObject m_pOwner;
    float m_fProjectileSpeed = 0.5f;

    // The constructor for this class
    public void Init(GameObject pOwner, Material pMaterial, float fProjectileSpeed)
    {
        // Remember who made us
        m_pOwner = pOwner;

        // Projectiles start where their owner was
        transform.position = m_pOwner.transform.position;
        transform.Rotate(0.0f, 0.0f, 180.0f);

        // How fast are we going to move?
        m_fProjectileSpeed = -fProjectileSpeed;

        // Apply the material
        renderer.material = pMaterial;
    }

    …
}

This new function ‘Init’ has a lot of new information in it, so time to go over it line by line in detail.

public void Init(GameObject pOwner, Material pMaterial, float fProjectileSpeed)

This is the ‘definition’ for the function, as with the variables the ‘public’ keyword allows the function to be seen outside of the object, but in this case it is visible to other parts of code and is not something you would see in the editor. The ‘void’ keyword is what this function will return when it finishes back to what called the function, in this case ‘void’ is simply saying ‘nothing’. Finally, the next three parts inside of the parentheses are variables that are being passed in to use be used inside of the function.

// Remember who made us
m_pOwner = pOwner;

// Apply the material
renderer.material = pMaterial;

// How fast are we going to move?
//   Speed is negated to adjust for object rotation
m_fProjectileSpeed = -fProjectileSpeed;

This lines are all assignments, the three variables sent to this object are stored in it. The ‘renderer’ is a intrinsic or default part of the object, and already has a spot to assign the material to.

// Projectiles start where their owner was
transform.position = m_pOwner.transform.position;
transform.rotation = m_pOwner.transform.rotation;

The final part of the function is where we set the initial position of the projectile to be wherever the calling object is at the time, and then rotate the object so the texture is facing up the right direction.

Save the files and give the project another go, if everything went according to plan, it should look just like this.

All this shooting is looking good, however without something to shoot at the fun runs out rather quickly, so time make a target to shoot at. This is our last new art asset for the project, and definitely saved my best art for last, behold the evil Space Pigeon!

Evil but Cute

Taking the simple approach here, you can create this frightful creature the same way as you did for the player object. Create a cube named ‘Enemy’ in the object hierarchy, import and apply the texture to the cube, and then place the cube in a nice center spot on the game area. Don’t forget to set the shader for the material as ‘Transparent/Diffuse’.

Step 16: Evil takes flight

If you test the project now, you will quickly notice that the player’s projectiles simply pass through the enemy with no effect. The reasons is that basic simple collision detection has to be setup to make the projectiles effective. The first step towards this goal is setting the ‘box collider’, a component that is created on cubes automatically, as a trigger simply by checking the box ‘Is Trigger’.

Step 17: Collusion

The unity editor allows use to ‘multi-edit’ objects, so you can change the ‘Is Trigger’ setting on both the player and the enemy at the same time by simply selecting both in the object hierarchy while holding down the ‘control’ key.

The second half of this task is done back in the MonoEditor in the Projectile.cs file. First off you are going to add a new component to the projectile inside the ‘Start’ function. The ‘Start’ function for objects in Unity is special, like the Update function and many others it is something that all ‘MonoBehaviour’ based objects have. The ‘Start’ function is called only once right before the ‘Update’ is called for the first time.

public class Projectile : MonoBehaviour
{
    // Local variables
    GameObject    m_pOwner;
    Rigidbody    m_pRigidBody;

    ...

    // Use this for initialization
    void Start ()
    {
        m_pRigidBody = (Rigidbody)gameObject.AddComponent("Rigidbody");
        m_pRigidBody.useGravity = false;
    }

    …
}

The new code is adding a ‘Rigid Body’ to all of the projectiles that are created, this is the basic piece of collision physics for many games. Up until now nothing in the project had any mass or substance, like a hologram of a ghost. By creating a rigid body for the projectiles, you can easily add object interactions. Please note, as before, this is not an optimal solution for this problem, but it’s simplicity out weighs it being inefficient.

The only custom setting that you need to apply to the rigid body of the projectile is turning off gravity for it, adding gravity wouldn’t be beneficial and would throw off the settings you have already made. Now that you are done with the ‘Start’ function, and made both the player and the enemy into ‘Triggers’ the projectile’s rigid body has three ways to interact with them.

OnTriggerEnter – The first time the rigid body touches the trigger
OnTriggerExit – The last time the rigid body touches the trigger
OnTriggerStay – Every update that the rigid body is touching the trigger

For needs of this project, you want to use ‘OnTriggerEnter’ since all the work you want to do is the first time the rigid body touches one of the triggers. Go ahead and add that as a new function to the ‘Projectile’ script.

public class Projectile : MonoBehaviour
{
    ...

    void OnTriggerEnter(Collider pOther)
    {
        // We don't want to hit the object that fired us
        if(pOther.gameObject == m_pOwner)
            return;

        // Tell whatever we hit to take damage
        pOther.gameObject.BroadcastMessage("TakeDamage", 1.0f);

        // This projectile can only hit one thing
        DestroyObject(gameObject);
    }
}

The ‘BroadcastMessage’ is something new, so a quick pause to take a closer look at this. Up until now, every time function calls have been made very precisely, by calling public functions on the object itself. There are however many cases where you cannot be that exact. In such a case you can use ‘BroadcastMessage’, in essence you are shouting at the object “Anyone have a ‘TakeDamage” function!?’ and then every component is checked to see which if any has a ‘TakeDamage’ function. Because right now the ‘TakeDamage’ function is not defined on any of the components, nothing will happen.

Which leads right into the final stretch of the project, setting up a script for the enemy.  This time to create a new script, you can do it right from MonoDevelop by right clicking on the script folder in the upper left and then add → new file. Inside of the popup window we select unity → C# script and give it the name ‘Enemy’

Step 18: Code Creation

public class Enemy : MonoBehaviour
{
    // Local variables
    float m_fReenableTime;            // When we re-enable the render for this object
    public float m_fXSpeed = 0.2f;    // How fast this object moves horizontally
    public float m_fYSpeed = 0.2f;    // How fast this object moves vertically

    // Use this for initialization
    void Start ()
    {

    }

    // Update is called once per frame
    void Update ()
    {
        // Turn our rendering back on if it was off and enough time has passed
        if(renderer.enabled == false && Time.time > m_fReenableTime)
            renderer.enabled = true;

        // Move our object on the screen
        transform.Translate(m_fXSpeed, Mathf.Sin(Time.time * 5.0f) * m_fYSpeed, 0.0f);

        // Check play area boundaries
        if(transform.position.x < -14.0f && m_fXSpeed > 0 ||
          transform.position.x > 14.0f && m_fXSpeed < 0)
        {
            m_fXSpeed = -m_fXSpeed;
        }
    }

    // React to taking damage
    void TakeDamage(float fAmount)
    {
        // Turn off the rendering of this object for a while
        renderer.enabled = false;
        m_fReenableTime = Time.time + 2.0f;
    }
}

The last script is a big block of code to take in all at once, time to break it down into smaller pieces.

    // Local variables
    float m_fReenableTime;            // When we re-enable the render for this object
    public float m_fXSpeed = 0.2f;    // How fast this object moves horizontally
    public float m_fYSpeed = 0.2f;    // How fast this object moves vertically

This code is setting up local variables for this script, to make it more interesting the enemy should move around the screen, to do that you need to give it some speed values. They are public once again to let you change them quickly in the Unity Editor.

    // React to taking damage
    void TakeDamage(float fAmount)
    {
        // Turn off the rendering of this object for a while
        renderer.enabled = false;
        m_fReenableTime = Time.time + 2.0f;
    }

This is the new ‘TakeDamage’ function that is being broadcasting to from the projectile when collision between the two is occurs. The function that reacts to damage is simple, the enemy is just made invisible for a short amount of time by disabling the object’s renderer.   The damage ‘fAmount’ variable we send this function is not actually used, however if the enemy had ‘heath’ you would find that variable to very useful.

    // Update is called once per frame
    void Update ()
    {
        // Turn our rendering back on if it was off and enough time has passed
        if(renderer.enabled == false && Time.time > m_fReenableTime)
            renderer.enabled = true;

        // Move our object on the screen
        transform.Translate(m_fXSpeed, Mathf.Sin(Time.time * 5.0f) * m_fYSpeed, 0.0f);

        // Check play area boundaries
        if((transform.position.x < -14.0f && m_fXSpeed > 0) ||
          (transform.position.x > 14.0f && m_fXSpeed < 0))
        {
            m_fXSpeed = -m_fXSpeed;
        }
    }

The update function has some similar parts to the ‘PlayerInput’, you should recognize the ‘transform.Translate’ and the boundary check. However it is not using the player input but instead a flat ‘m_fXSpeed’ to determine how fast and which direction the enemy is moving. For a little extra flare, ‘m_fYSpeed’ is being used to add some vertical motion regulated by a sine wave.

The very first part of the update function is where some basic logic is performed. However, there are some new pieces of syntax to learn. Sometimes you need the code consider multiple pieces of data before making an yes/no decision, you can do that by combining and grouping the simple conditionals.

A && B → means ‘A and B’ need to be true
A || B → means ‘A or B’ need to be true
!A → means ‘not A’ needs to be true
^ this on is tricky it is using a double negative in programming ‘not false’ would cause the condition to be true.

Just like with algebra, these conditionals also respect an order of operations, in the bottom part of the ‘Update’ function we use parentheses to group conditionals together:
(A and B) or (C and D)

That may be a bit complex all at once, but the more you use these conditionals the easier it will become.

Back to the project and to the Unity Editor, if we place the ‘Enemy’ script onto the enemy object when you run the project you should now have a moving target to shoot, something like this.

With that you have reached the end of this first workshop, take some time to customize your projects. Consider new art, having the enemy shoot back, allowing the player to move vertically, add health or multiple enemies, be creative!