Unity 3 Game Development HOTSHOT

By Jate Wittayabundit
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Develop a Sprite and Platform Game

About this book

Unity 3d is the game engine of choice for creating professional looking games at no cost. Its combination of powerful tools and outstanding community support make it the natural choice for experienced and aspiring game developers.

Unity3D Game Development Hotshot will show you how to exploit the full array of Unity3Dtechnology in order to create an advanced gaming experience for the user. It has eight exciting and challenging projects with step- by-step explanations, diagrams, screenshots, and downloadable materials.

Every project is designed to push your Unity skills to the very limits and beyond. You will create a hero/heroine for a role playing game. Create a menu for the RPG game allowing you to customize your character with powerups, armor, and weapons. You will shade, model, rig, and animate your hero/heroine. The end result will be a  character on the level of Final Fantasy, far superior to a simple sprite.

Now for some damage - rocket launchers! Typically the most powerful weapons in any first person shooter, you will create a rocket launcher that has fire and smoke particles and most importantly causes splash damage for that all important area effect. Create AI controlled enemies for your hero/heroine to eliminate with the rocket launcher. Forge  a destructible  interactive world so if the rocket launchers miss their target they will at least cause significant damage to the surrounding environment. Learn to save and load your game so you can take a break from the action for life’s necessities like going to the bathroom. Incorporate social gaming by uploading scores online so everyone can see the carnage.

Publication date:
August 2011
Publisher
Packt
Pages
380
ISBN
9781849691123

 

Chapter 1. Develop a Sprite and Platform Game

Even in today's world, people remember Mario, Sonic, and Mega Man. Of course, Mario was first introduced in the Eighties, followed by Mega Man and Sonic, but even now the new generation love these games. Yes, we are talking about the old style 2D platform games, which still exist.

In this book, we will start the first chapter with a 2D platform game because there are some basic tricks for a 2D platform game, which will help you—those who haven't got into the 3D world yet—to understand more before jumping into the 3D world for the project in later chapters.

 

Mission briefing


We'll be creating a 2D platform or side-scrolling game, which is similar to Mario or other games that we have mentioned previously; it will have a simple character that the player will be able to move, jump, and collect a key item to be able to pass the level, and a Restart button for the player to play the game again.

We will use the 2D character sprite sheet (as shown in the previous image), and create the sprite manager class to control it instead of the 3D character model. Some of you might have a few questions: Why are we doing this? Why don't we just use the 3D model, which should be easier to do, instead of creating the sprite manager class?

Well, there are some advantages of using a sprite manager class. Firstly, creating a 3D model and animation takes time. It takes more time to create a simple 3D character with animation than to create a 2D character with a sprite sheet because you don't have to deal with the polygon count, rigging the character, unwarping the textures, and animating it. You just draw it. Since the 2D sprite object only shows one view, we can use the plane object to save the number of polygons instead of using the 3D character object. It is also an advantage to learn this sprite technique to create an animated texture in your game.

The purpose of this chapter is to familiarize you with all the tools and language syntax in Unity, which is very important to create a playable game. We will also see how to use MonoDevelop for a JavaScript user (sometimes called UnityScript; in the rest of the book, we will call it Unity JavaScript) and what is good about MonoDevelop when compared to Unitron (or UniSciTE in PC).

What does it do?

In this project, we will start with creating a camera for our game, and adding light and level to the scene. Next, we want to create our character object as a plane, apply the transparent material, and use the 2D graphic sprite sheet for its texture. We will also create the script, which will control the sprite sheet to show the right graphic on our character object. This script will allow us to be able to control our character to walk and jump by pressing the arrow key. Also, we will learn how to set up the custom input manager. Then, we will have the right animation for the character idle, walking, or jumping.

For the level, we will create it by using a Unity built-in cube and give it a collision which will react with the character by using a Unity built-in physics. To end the game, we will create a trigger event by creating a door and a key. The player needs to collect the key to open the door and end the game. We will also add sound to make our game seem alive, but we are not finishing it yet. The game needs to be replayable. Lastly, we will add a Replay or Play again button to replay our game by using destroy and instantiate to reset our character position and key item.

Why Is It Awesome?

When we are done with this chapter, we will get a good understanding of how to create a sprite and 2D platform game by using a 3D game engine such as Unity. Also, we will be able to create our own 2D platform style game like Sonic, Mario, Mega Man, and so on, and reuse some of our techniques, scripts, and concepts to create a 3D game at a later stage.

Your Hotshot Objectives

This project will be split into six tasks. Since we are not creating any enemies in our game, we don't have to deal with any complex scripting. It will be a simple step-by-step process from beginning to end. Here is the outline of the tasks:

  • Creating a camera and a level

  • Creating a 2D character

  • Creating CharacterController and SpriteManager classes

  • Jumping and physics

  • Creating key and door

  • Adding Sound and Replay button

Mission Checklist

Before we start, we will need to get the latest Unity version http://unity3d.com/unity/download/ which includes MonoDevelop that we will use for our scripting editor. We will also need a few graphics for our character, key, and door as well as a collection of sound FX. These could be downloaded as ZIP files from Packt's website: http://www.packtpub.com/support?nid=8267.

Browse to the preceding URL and download Chapter1.zip package and unzip it. Inside the Chapter1 folder, there are five subfolders, which are Buttons, Characters, FBX, Level, and Sound.

 

Creating a camera and a level


This part is just about creating a camera and a level to use in our platform game. We will be creating a camera that will show all the objects in the scene and follow our character movement.

Prepare for Lift Off

Before we start creating this project, we will create the project in Unity by following these steps:

  1. Create a new project by going to File | New Project to bring up the Project Wizard window. Next, click on the Create new Project tab and set the Project Directory as you want, as we can see in the following screenshot:

    Note

    As we can see from the preceding screenshot, we won't import any Unity assets packages because we won't be using any in this chapter.

  2. Import the Chapter1 package folder that you downloaded into the project assets folder, by copying it into the project's Assets folder or drag-and-dropping it into the Unity window, as we can see in the following screenshot:

  3. Go back to Unity and make sure that you have Plane and background.png in your Project folder, as shown in the following screenshot:

  4. Click on the Plane object in the Project view to bring up its Inspector view. Next, we go to the FBXImporter | Meshes component, and set the Scale Factor to 1, as shown in the following screenshot, and click on the Apply button:

Engage Thrusters

We are now ready to start, so let's get on with it!

  1. Let's start by creating the background with the Plane prefab object in the FBX folder—go to the Project view, click on the Plane prefab object, and drag it into the Hierarchy view.

    Note

    There is also the Unity built-in Plane object that you can use, but you don't really want to use it, because the Unity built-in Plane object will have way too many triangles for our 2D objects. As we can see from the following screenshot, our prefab Plane only has two triangles, but the Unity built-in Plane object will have around 200 triangles.

  2. In the Hierarchy view, right-click on the Plane prefab object, and choose Rename to change the name to Background.

  3. Then, click on this object and go to its Inspector view, and set its transform Position to X: 0, Y: 0, Z: 24, Rotation to X: 0, Y: 180, Z: 0, Scale to X: 200, Y: 200, Z: 1.

  4. Right-click on the Animation component in the Inspector view and choose the Remove Component option to remove it, as shown in the following screenshot:

    This will bring up the pop-up window, as shown in the following screenshot. Click on the Continue button to break the prefab:

  5. Now, to create the background material, go to Assets | Create | Material, and name it whatever you want; here we will call it M_Background. Then, we assign our background texture to this material, in the project window click on M_Background. We will see the Inspector view of the background material, as shown in the following screenshot:

    Note

    If you don't see the detail as seen in the preceding screenshot, you can click anywhere that isn't a button on the banner (the lighter gray area that says M_Background) to show the details.

  6. Next, drag the background.png file from the Chapter1/Level folder in the Project view and drop it in the texture thumbnail, and then set the following:

    • Shader: Diffuse

    • Main Color:R:164, G:219, B:225, A: 255

    • Base (RGB): x-Tiling: 2, Offset: 0; y-Tiling: 2, Offset: 0

    Now, we are adding our material to the background object, click on Background object in the Hierarchy view to open the Inspector view, and in Mesh Renderer | Materials, set the parameters as follows:

    • Size: 1

    • Element 0: M_Background

  7. Next, we will create a new Tag and Layer for our Background object; go to Edit | Project Settings | Tags and click on the arrow next to the Tags option to open it, as shown in the following screenshot:

  8. Enter the parameters as follows:

    For Element 0 type Background, for Element 1 type Floor, for Element 2 type Wall, and then for User Layer 8 type Background, for User Layer 9 type Level; we select our Background object, and then go back to the Background object's Inspector view setup as follows:

    • Tag: Background

    • Layer: Background

  9. Set the Main Camera, which is already in our scene when we first create the project, as follows:

    • Position: x: 0, y: 0, z: -20

    • Projection: Perspective

  10. To light up our scene by adding sound light into it, go to GameObject | Create Other | Directional Light and set its parameters as follows:

    • Rotation: x:20, y:0, z:0

  11. For the last step, we will create our quick, easy, and simple level:

    • First, we need to create our container to contain all the objects for the level. Go to GameObject | Create Empty or use Command + Shift + N in Mac and Ctrl + Shift + N in Windows, and change the name to Level, and reset the transform position to X: 0, Y:0, Z: 0), rotation to X: 0, Y: 0, Z: 0, and scale to X: 1, Y: 1, Z: 1.

    • For creating our floor, let's go to GameObject | Create Other | Cube, change the name of this object to Floor, and change the tag and layer as follows:

      • Tag: Floor

      • Layer: Level

        • Same thing for creating a wall; just repeat the same step and change the name to Wall, and set the tag and layer as follows:

      • Tag: Wall

      • Layer: Level

    So, now we have our Floor cube and Wall cube.

    • Next, we want to apply the material to our cubes. We will have only one material for both the floor and wall to make it simple. Go to Assets | Create | Material, name it M_Level, adjust the color to R: 150, G: 230, B: 225, A: 255, and apply this material to the Floor and Wall objects by dragging the material Floor and Wall objects in the Hierarchy view. Then we drag-and-drop Floor and Wall inside our Level object, as shown in the following screenshot:

    • Now, we will click on the floor object in the hierarchy, and press Command + D for Mac users or Ctrl + D for Windows users to copy it six times, and click on the wall object in the hierarchy and copy it twice. So now we have seven floor objects and three wall objects.

    • Next we create our level by setting up the position and scale of our floor and wall objects. Let's set them up as follows:

      • 1st Floor object: Position: x: -4, y: -9, z: 0 Scale: x: 125, y: 15, z: 1

      • 2nd Floor object: Position: x: -6, y: 5, z: 0 Scale: x: 32, y: 1, z: 1

      • 3rd Floor object: Position: x: -25, y: 12, z: 0 Scale: x: 19.5, y: 1, z: 1

      • 4th Floor object: Position: x: 14, y: 12, z: 0 Scale: x: 20, y: 1, z: 1

      • 5th Floor object: Position: x: -7, y: 9, z: 0 Scale: x: 9, y: 1, z: 1

      • 6th Floor object: Position: x: -31, y: 1, z: 0 Scale: x: 6, y: 1, z: 1

      • 7th Floor object: Position: x: 21, y: 2, z: 0 Scale: x: 10, y: 1, z: 1

      • 1st Wall object: Position: x: -49, y: 17, z: 0 Scale: x: 36, y: 40, z: 1

      • 2nd Wall object: Position: x: 42, y: 17, z: 0 Scale: x: 38, y: 39, z: 1

      • 3rd Wall object: Position: x: -7, y: 23, z: 0 Scale: x: 1, y: 36, z: 1

    • Finally, we will save the scene by pressing Command + s in Mac or Control + s in Windows. Since it is our first save, we will be asked to name this scene, so let's name it SimplePlatform.

Objective Complete - Mini Debriefing

Basically, what we have done here is create a Background object behind the Level object, and set the Main Camera in front of the Level object. Our Main Camera will also follow our character while he is moving. This way we can make sure that the player will always see our character and background image. We can set our scene and level, as shown in the following diagram:

In our Main Camera, we set the Projection to Perspective because we want to show the thickness of our level and the depth of the object, which will give a nice view for the player.

Classified Intel

We can set the Camera Projection in our scene to be either Orthographic or Perspective. The difference between both projections is that with the Orthographic Projection, the object won't scale by the distance of the camera. So in our scene, we will see only one side of the object that faces the camera. On the other hand, in Perspective Projection we will see the depth of the object that will scale down by the distance of the camera, which is very similar to real life.

In our scene, we won't see any significant difference on our background object because our background object is a plane and doesn't have any thickness on it, but if we are trying to adjust the Projection of our camera, we will see the difference between the two projections. We can do this by going to the Hierarchy view, clicking on Main Camera, changing Projection to Orthographic, and Size to 8.5, and then changing Projection back to Perspective. The difference is shown in the following screenshot:

 

Creating a 2D character


In this step, we will create our 2D character and material, which will contain our 2D character sprite sheet from our Chapter1 package folder. We will have our character act out three different types of animation: staying, walking, and jumping.

Prepare for Lift Off

Let's make sure that we have all the sprites we need in the project folder:

  1. Go to Chapter1/Characters where you will see three subfolders, Jump, Stay, and Walk.

  2. Open the jump folder. We will see the files J_Frame1.png, J_Frame2.png, and J_Frame3.png. Next, open the Stay folder, we will see the s_set.png file. Then, open the last folder Walk, we will see the w_set.png file as shown in the following screenshot:

Now, we are ready to get started.

Engage Thrusters

Since our character is a 2D sprite animation, we only need to have a plane object to contain it. Let's do it as the follows:

  1. Go to the Plane prefab object in the FBX folder and drag it into the Hierarchy view.

  2. Next, right-click on the Animation component in the Inspector view and choose the Remove Component option to remove it. We will see the pop-up window, so just click on the Continue button, similar to the one we did for our Background object.

  3. Then, we click on this object and go to its Inspector view, and set it as follows:

    • Tag: Player

    • Position: x: -25, y: 16, z: 0

    • Rotation: x: 0, y: 180, z: 0

    • Scale: x: 5, y: 5, z: 1

  4. We will call our character Player. Go to the Hierarchy view, right-click on the Plane prefab object, and choose Rename to change the name to Player.

  5. Next, go to Assets | Create | Material and name it M_Character.

  6. Go to material's Inspector view and set it up as follows:

    • Shader: Transparent | Cutout | Soft Edge Unlit

    • Base (RGB) Alpha (A)

    • Drag-and-drop our s_set.png from the Characters/Stay folder to the texture thumbnail in material inspector

    • X: Tiling: 0.5, Offset: 0

    • Y: Tiling: 1, Offset: 0

    • Base Alpha cutoff: Drag the dragger to the very right end

    We have now got the material for our Player.

  7. Next, we go back to the Player and assign this material to him by dragging and dropping M_Character from the Project view to the Player object in the Hierarchy view. Finally, add the Box Collider and add a RigidBody to the Player. We will use the Box Collider because our Player is basically a plane and doesn't need any complex collider to detect his collision.

  8. Let's click on the Player and go to Component | Physics | Box Collider set Size: x: 0.4, y: 0.875, z: 1 and Center: x: 0, y: -0.06275, z: 0.

  9. Then, we will add the RigidBody, which is used to calculate our walking speed, jumping, and collision detection with the level; go to Component | Physics | RigidBody and make sure that Use Gravity is On and the Kinematic option is Off.

  10. Set the freeze the rotation of the object (Freeze Rotation) by clicking on the arrow in front of Constraints. In Freeze Rotation, check each box X, Y, and Z to freeze rotation. We will also check the Z box to freeze the character movement in the Z-axis, as shown in the following screenshot:

Objective Complete - Mini Debriefing

We just created a plane that will act as our main character, our Player. We also created a material for our Player by using Transparent | Cutout | Soft Edge Unlit Shader. This Shader will cut out the Alpha channel and make it transparent. In addition, it will also soften the edge, not to the shape of the object it is on, but instead it will soften the edges of the image itself. We can control which portion of the image will be cut out, and how much the edge will soften by adjusting the Base Alpha Cutoff slider.

We also set the tiling for the X-axis to 0.5 because our image contains two frames, but we want to use only one image at a time. We used the Box Collider instead of Mesh Collider. We were also adding a RigidBody for our character and setting it to enable Freeze Rotation, which will ignore all the rotation on our character that will be calculated by Physics Engine in Unity. This will cause our Player not to rotate.

The RigidBody will also give our character the ability to activate the Physics Engine in Unity, such as gravity or velocity, and act as real-life physics. We will see this in the next step.

Classified Intel

Why do we need to freeze the rotation and position of the Rigidbody in our character?

Note

We freeze the rotation of the Rigidbody because we are using the sprite texture to present the character movement. So, we don't want our character to rotate when it moves. We also freeze the position on the Z-axis because our character will only move on the X and Y axes. In this way, we can also save the CPU cycles because Unity will ignore the unnecessary calculation and only calculate the one it needs.

Box Collider and Mesh Collider

So why are we using Box Collider instead of Mesh Collider? Both the colliders are basically similar. Think about it this way: each surface of the mesh will have its own normal that will be perpendicular to each vertices and check if it hits any object. So, if we think about our plane object, we will see that it has only one face that has the normal pointing towards the camera. So, it means that if we apply the Mesh Collider to the plane object, we won't get any collision detection from the top, bottom, left, right, and back side of the plane. This is basically because there is no surface at the top, bottom, left, right, and back side of this object to create the collision detection with the other objects.

On the other hand, Box Collider uses the volume that it represents to check for the collision detection. This result will be a lot faster than the Mesh Collider. In this case, we are checking the volume of the character Box Collider with the the Floor Box Collider to see whether there is any part of the Floor collider without Player or not, as we can see in the following diagram:

Note

The Box Collider can save a lot of memory and CPU cycles in real-time rendering compared to the Mesh Collider.

Next, we will talk about Tiling in Material, which is very similar to many 3D programs. Every texture that we applied to the material there will be stretched to fit in the square space, which we can see in the 1x1 cube.

Tiling is very much similar to scaling, and it's basically repeating the texture on X and Y axes. So, if we set the Tiling X: 0.5, Y: 1.0, we see the result as shown in the previous figure with the texture on X-axis that scales half size, but it still looks the same in Y. We also see that the second image will show only the first left side of the texture (the first frame of our character). Now, if we want to show the right side of our texture what will we do? We will use Offset in Material, which will give a different result from Tiling. The Offset basically tells us the starting position of our texture. So, if we set the Offset X: 0.0, Y: 0.0, this means that our texture will display from the top-left corner of the original texture. On the other hand, if we set Offset X: 0.5, Y: 0.0, we will see the result that our texture's start point is at the middle of the original texture image, and we will see our material show the right side of our texture (the second frame of our character, as we can see in the following figure):

We can change the Tiling by calling the material.mainTextureScale function to set the X tile and Y tile, and use calling material.mainTextureOffset to set the X and Y Offset.

After learning this technique, we can manage our sprite image by just changing the number of Tiling and Offset of our character Material in the next step.

 

Creating CharacterControl class and SpriteManager class


In this section, we will create new Unity JavaScript code to control the movement of our character, and a sprite animation for each action of our character. We have a choice to use Unitron (Mac), UniSciTE (Windows), or MonoDevelop, but in this book we will use MonoDevelop as our scripting editor instead of Unitron or UniSciTE. MonoDevelop is mainly designed for C# and .NET environment, so if you are comfortable with C#, you will probably love it. However, we will still use it to edit our JavaScript because it has a lot of functions that will help us to write the script faster and debug better, such as finding and replacing words in the whole project by pressing Command + Shift + F in Mac or Control + Shift + F in Windows, and autocomplete, to name a few. Moving from Unity JavaScript to C# is also a comparatively smooth transition.

Prepare for Lift Off

Now, we are just about to start coding, but first let's make it organized:

  1. Create a new folder in your project window and name it Scripting. This folder will contain our script for this chapter.

  2. Next, we want to set up our Unity to use MonoDevelop as our main Scripting editor (Unity | Preferences in Mac or Edit | Preferences in Windows).

  3. We will see a Unity preferences window. In the General tab, go to the External Script Editor and change Use build-in editor (Unitron/UniSciTE) to MonoDevlop by clicking on Browse... and choose Applications | Unity | MonoDevelop.app in Mac or {unity install path} | Unity | MonoDevelop | MonoDevelop.exe in Windows, and we are done.

    Note

    The default Unity script editor is set to Unitron/UniSciTE because they are the built-in editors that are included in Unity from the beginning. MonoDevelop is basically the IDE that is just included in Unity 3.X, which has a better scripting and debugging environment. We can see more information about how to set up the MonoDevelop on this website: http://unity3d.com/support/documentation/Manual/HOWTO-MonoDevelop.html.

Engage Thrusters

  1. First, go to Assets | Create | Javascript and name our script as CharacterController_2D.

  2. Double-click on the script; it will open the MonoDevelop window.

  3. Now, we will see three windows in the MonoDevelop screen:

    • On the top-left is Solution; we can see our project folder here, but it will only show the folder that contains a script.

    • On the bottom-left, we will see a Document Outline; this window will show all the functions, classes, and parameters in the file.

    • The last window on the right will be used to type our code.

  4. Let's get our hands dirty with some code—first create the CharacterController_2D class. At present, we are creating parameters:

    public var f_speed : float = 5.0;
    public var loopSprites : SpriteManager[];
    private var in_direction : int;

    f_speed is the speed of our character, and we set it to public so we can adjust it inside the Unity editor. The array loopSprites of the SpriteManager class will control the update of our sprite animation texture, which we will create later. in_direction tracks the direction of our character, which will return only 1 (right direction) or -1 (left direction).

  5. Next, we will include the script in the Start() function, which is already created by default:

    public function Start() : void {
      in_direction = 1;
    //Initialization Sprite Manager
      for (var i : int = 0; i<loopSprites.length; i++) {
        loopSprites[i].init();
      }
      //Update Main Camera to the character position
      Camera.main.transform.position = new Vector3(transform.position.x, transform.position.y, Camera.main.transform.position.z);
    }
  6. Next, we will include the script in the Update() function, which is already created by default similar to the Start() function:

    // Update is called once per frame
    public function Update () : void {
      if (Input.GetButton("Horizontal")) {
        //Walking
        in_direction = Input.GetAxis("Horizontal") < 0 ? -1: 1;
        rigidbody.velocity = new Vector3((in_direction*f_speed), rigidbody.velocity.y, 0);
        //Reset Stay animation frame back to the first frame
        loopSprites[0].resetFrame();
        //Update Walking animation while the character is walking
        loopSprites[1].updateAnimation(in_direction, renderer.material);
      } else {
        //Stay
        //Reset Walking animation frame back to the first frame
        loopSprites[1].resetFrame();
        //Update Stay animation while the character is not walking
        loopSprites[0].updateAnimation(in_direction, renderer.material);
      }
    }
  7. Then, we create a LateUpdate() function, which is called after all the Update() functions have been called. We will use this function to update our camera position after our character movement by setting its transform to follow our character:

    public function LateUpdate() : void {
    //Update Main Camera
      Camera.main.transform.position = new Vector3(transform.position.x, transform.position.y, Camera.main.transform.position.z);
    
    }
  8. Next, we create the SpriteManager class to manage our sprite texture in the CharacterController_2D.js file; continue from our preceding script, and add the following:

    class SpriteManager {
      public var spriteTexture : Texture2D; //Set Texture use for a loop animation such as walking, stay, etc.
      public var in_framePerSec : int; //Get frame per sec to calculate time
      public var in_gridX : int; //Get max number of Horizontal images
      public var in_gridY : int; //Get max number of Vertical images
      
      private var f_timePercent : float;
      private var f_nextTime : float; //Update time by using frame persecond
      private var f_gridX : float;
      private var f_gridY : float;
      private var in_curFrame : int;
      
      public function init () : void {
        f_timePercent = 1.0/in_framePerSec;
        f_nextTime = f_timePercent; //Update time by using frame persecond
        f_gridX = 1.0/in_gridX;
        f_gridY = 1.0/in_gridY;
        in_curFrame = 1;
      }
      
      public function updateAnimation (_direction : int, _material : Material) : void {
        //Update material
        _material.mainTexture = spriteTexture;
        //Update frame by time
        if (Time.time>f_nextTime) {
          f_nextTime = Time.time + f_timePercent;
          in_curFrame++;
          if (in_curFrame>in_framePerSec) {
            in_curFrame = 1;
          }
        }
        _material.mainTextureScale = new Vector2 (_direction * f_gridX, f_gridY);
        var in_col : int = 0;
        if (in_gridY>1) {
          //If there is more than one grid on the y-axis update the texture
          in_col= Mathf.Ceil(in_curFrame/in_gridX);
        }
          if (_direction == 1) { //Right 
          _material.mainTextureOffset = new Vector2(((in_curFrame)%in_gridX) * f_gridX, in_col*f_gridY);
        } else { //Left
          //Flip Texture
          _material.mainTextureOffset = new Vector2(((in_gridX + (in_curFrame)%in_gridX)) * f_gridX, in_col*f_gridY);
        }
      }
      
      public function resetFrame () :void {
        in_curFrame = 1;
      }
    }
    
    
  9. Now, save it and go back to Unity; drag-and-drop our script to our Player, then click on Player and go to the Inspector window. Click on the Loop Sprites, and set Size to 2, then set the following:

    • Element 0:

      • Sprite Texture: s_set

      • In_frame Per Sec: 2

      • In_grid X: 2

      • In_grid Y: 1

    • Element 1:

      • Sprite Texture: w_set

      • In_frame Per Sec: 8

      • In_grid X: 4

      • In_grid Y: 2

We are done. Let's click on the play button to play the game. We will see our Player moving his hand back and forth. Next, press the A key or ¬ key, D key or ® key to move the Player to the left or to the right; now we see that he is walking. Isn't that cool?

Objective Complete - Mini Debriefing

We just created a script that controls the movement of our character, and his animation. First, we set in_direction to 1 because we want our character To start by facing the right-hand side. Then, we are looping through the array and initializing the SpriteManager class from its length. We will get the main camera from the current scene by using Camera.main. This syntax allows us to access the Main Camera object from anywhere we want, and then we assign the main camera position point to our character. Next, we will put the script in the Update() function, which is already created by default —similar to the Start() function. This function will be used to control our character movement from walking to jumping, and for updating the animation.

Then, we used the Input class to detect when the player presses a key on the keyboard. We do all the character control in the update() function. First, we use if (Input.GetButton("Horizontal")) { } to check if the player pressed a Horizontal key, (for which the default in Unity is A, D, left arrow, or right arrow key), and we move our character if he/she did. The first line in this if statement checks the direction, which we are using, in_direction =Input.GetAxis("Horizontal") < 0 ? -1: 1;, which means that if the player presses a Horizontal key, we will get the axis number from the Input.GetAxis("Horizontal") function. The Input.GetAxis function will return the range from -1 to 1 depending on the pressure of the player pressing. Then, we check if the number is lower than 0 or not, if it's then the function returns -1 (move to left), if not it returns 1 (move to right). Then in the line rigidbody.velocity = new Vector3((in_direction*f_speed), rigidbody.velocity.y, 0);,we applied the direction and speed to the rigidbody velocity. We don't apply any velocity in the Z-axis because we are not moving our character in that direction.

Lastly, we included the SpriteManager class in our CharacterController_2D.js file to control our sprite texture to play loop animation by using the maximum of frame we had calculated with the time to play each frame. Let's take a look at our SpriteManager class. spriteTexture is basically a set of sprite texture that get held in this class. This textures will get a call and apply to the main material texture when the character is changing their movement, such as from walk to stay, stay to walk, or walk to jump, and so on. in_framePerSec is the total frames of the sprite texture, which will be used to calculate when the next frame will be showed. in_gridX is the number of the row in our sprite texture, and in_gridY is the number of the column in our sprite texture, which will be used to calculate the Tiling and Offset of the texture that we have already seen in the last step. We also have private parameters f_timePercent, f_nextTime, f_gridX, f_gridY, and in_curFrame, which are used to calculate in the updateAnimation() function. Next, we have the init() function. This function is basically for setting up our parameters. Then, the updateAnimation() function will get the material and direction from our main character to calculate and update our sprite animation. Lastly, we have a resetFrame() function to reset our animation frame back to one.

Classified Intel

There are a few more things that we need to know:

Input Manager

In Unity, we can set a custom Input Manager by going to Edit | Project Settings | Input. In the Inspector, click on Axes and you will see Size: 17, which is the array length of all the inputs. If we want more than 17 inputs, we can put the number here (the default is 17). Next, we will see all 17 names from Horizontal to Jump as a default setting. Each one will have its own parameters, which we can set up, as follows:

We can see the information of each parameter on the Unity website:

http://unity3d.com/support/documentation/Components/class-InputManager.html.

In our code, we use the Input.GetButton("Horizontal") function. GetButton means we are checking if the Horizontal button is being held down. The Horizontal is the name of the first input button, as we can see in the preceding screenshot. We can also use Input.GetKey("left") to control our character. It will have the same result with Input.GetButton, but the difference is GetKey will only detect the specific key in our code. It isn't flexible for the user to adjust the key configuration during the game play. The Negative Button and Positive Button here will send the negative and positivevalue, which in most cases is used for controlling direction such as left, right, up, and down. There is a Dead parameter, which will set any number that is lower than this parameter to 0, which is very useful when we use a joystick. Also, setting the Type to key/mouse button and enabling the Snap parameter will reset axis values to zero after it receives opposite inputs.

 

Jumping and physics


Now, we are making our character jump by using Physics.Raycast in Unity. We can also use the OnCollisionEnter, OnCollisionExit, or OnCollisionStay functions to check the collision detection between our character and the floor, but in this case we will use Raycast because it's more flexible to adjust.

However, because the Raycast is only a line with no thickness, there is a chance that if we have a very thin platform, the Raycast can miss it. And it will cause the problem that we might not be able to jump. So, we should make sure that the platform should have the thickness of least 0.1 units.

Engage Thrusters

Continuing from the last step, let's get on with it as follows:

  1. Let's open our CharacterController_2D.js file and add this code to it; first our parameters:

    private var b_isJumping : boolean;
    private var f_height : float;
    private var f_lastY : float;
    public var jumpSprite : JumpSpriteManager;
    public var layerMask : LayerMask; //to check for the raycast
  2. Then, we go to the public function Start () function and add the following code inside this function:

       //Get mesh from the character MeshFilter
      mesh = GetComponent(MeshFilter).sharedMesh;
      //Get hight from the top of our character to the bottom of our box collider
      f_height = mesh.bounds.size.y* transform.localScale.y;
      //Set up the last y-axis position of our character
      f_lastY = transform.position.y;
      b_isJumping = false;
  3. Next, we go to the public function Update () function and add some code as follows (the highlighted part is our new code):

     //If our character isn't jumping
      if (!b_isJumping) {  
    if (Input.GetButton("Horizontal")) {
          //Walking
          in_direction = Input.GetAxis("Horizontal") < 0 ? -1 : 1;
          rigidbody.velocity = new Vector3((in_direction*f_speed), rigidbody.velocity.y, 0);
          loopSprites[0].resetFrame();
          loopSprites[1].updateAnimation(in_direction, renderer.material);
        } else {
          loopSprites[1].resetFrame();
          loopSprites[0].updateAnimation(in_direction, renderer.material);
        }
        if (Input.GetButton("Jump")) { //Jump
          b_isJumping = true;
          //Then make it Jump
          loopSprites[0].resetFrame();
          loopSprites[1].resetFrame();
          rigidbody.velocity = new Vector3(rigidbody.velocity.x, -Physics.gravity.y, 0);
        }
      } else {
        //update animation while it Jump
        jumpSprite.updateJumpAnimation(in_direction, rigidbody.velocity.y, renderer.material);
      }
    

    So, we basically add the statement to check if our character is jumping or not.

  4. Next, we add the highlighted code in the LateUpdate() function, in which we already had our camera update position:

    public function LateUpdate() : void {
      //Checking Jumping by using Raycast
      var hit : RaycastHit;
      var v3_hit : Vector3 = transform.TransformDirection (-Vector3.up) * (f_height * 0.5);
      var v3_right : Vector3 = new Vector3(transform.position.x + (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
      var v3_left : Vector3 = new Vector3(transform.position.x - (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
    if (Physics.Raycast (transform.position, v3_hit, hit, 2.5, layerMask.value)) {
    b_isJumping = false;
        } else if (Physics.Raycast (v3_right, v3_hit, hit, 2.5, layerMask.value)) {
        if (b_isJumping) {
      b_isJumping = false;
            }
        } else if (Physics.Raycast (v3_left, v3_hit, hit, 2.5, layerMask.value)) {
    if (b_isJumping) {
      b_isJumping = false;
            }
        } else {
        if (!b_isJumping) {
        if (Mathf.Floor(transform.position.y) == f_lastY) {
          b_isJumping = false;
        } else {
          b_isJumping = true;
        }
          }
      }
    f_lastY = Mathf.Floor(transform.position.y);
    //Update Main Camera
      mainCamera.transform.position = new Vector3(transform.position.x, transform.position.y, mainCamera.transform.position.z);
    
    }
  5. This is a very nice function that will allow us to debug our game, the result of which we won't see in the real game. Let's add the following block of code:

    public function OnDrawGizmos() : void {
      mesh = GetComponent(MeshFilter).sharedMesh;
      f_height = mesh.bounds.size.y* transform.localScale.y;
      var v3_right : Vector3 = new Vector3(transform.position.x + (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
      var v3_left : Vector3 = new Vector3(transform.position.x - (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);
      Gizmos.color = Color.red;
      Gizmos.DrawRay(transform.position, transform.TransformDirection (-Vector3.up) * (f_height * 0.5));
      Gizmos.DrawRay(v3_right, transform.TransformDirection (-Vector3.up) * (f_height * 0.5));
      Gizmos.DrawRay(v3_left, transform.TransformDirection (-Vector3.up) * (f_height * 0.5));
    }
  6. Finally , we add another SpriteManager class, which is a little different from our first sprite class. This SpriteManager is for our jumping animation. Since our jumping animation is quite unique and not a loop animation, we need another sprite class to control it. Let's add this code underneath our SpriteManager class and call it JumpSpriteManager:

    class JumpSpriteManager {
      public var t_jumpStartTexture : Texture2D; //Alternative Jump Texture play after t_jumpReadyTextures
      public var t_jumpAirTexture : Texture2D; //Alternative Jump Texture play when the player in the air at the top position of projectile
      public var t_jumpDownTexture : Texture2D; //Alternative Jump Texture play when the player fall to the ground
      
      public function updateJumpAnimation (_direction : int, _velocityY : float, _material : Material) : void {
        //Checking for the player position in the air
        if ((_velocityY>= -2.0) && (_velocityY<= 2.0)) { //Top of the projectile
          _material.mainTexture =t_jumpAirTexture;
        } else if (_velocityY> 2.0) { //Start Jump
          _material.mainTexture = t_jumpStartTexture;
        } else {  //Fall
          _material.mainTexture = t_jumpDownTexture;
        }
        _material.mainTextureScale = new Vector2 (_direction * 1, 1);
        _material.mainTextureOffset = new Vector2 (_direction * 1, 1);
      }
    }
  7. Now we are done with coding, go back to Unity, go to Hierarchy, and click on our Player, then go to the Inspector view. Now, we will see a new parameter Jump Sprite; click on it to get the parameters, then set the following:

    • T_jump Start Texture: J_Frame1

    • T_jump Air Texture: J_Frame2

    • T_jump Down Texture: J_Frame3

    • Layer Mask: Level

We have now finished this step. Let's click plays the game and Space on the keyboard. Now, you will see your Player Jumping.

Objective Complete - Mini Debriefing

First, we attached the jumping ability to our character. b_isJumping is for checking if the character is already jumping in the air or not. f_height is the height from top to bottom of our character, which we will use to calculate physics later. f_lastY is the last position of our character in the Y-axis. jumpSprite is the SpriteJumpManager class, which will be a bit different from our SpriteManager class because our jumpsprite is not loop animation, so we need to create a new class to control this.

Then, in the start function we had a code that sets up and gets the information that we need as soon as our character is created. The mesh = GetComponent(MeshFilter).sharedMesh; line will basically get the mesh information from the GameObject that our CharacterController_2D script attached, which is our Player. Next, we get the height of this mesh by its size multiplied by the local scale of this object. Then, we set up f_lastY to the object's current position and set the b_isJumping parameter to false.

Next, if it is jumping, it will update the jumping Sprite animation. If it isn't jumping, it will go to our old code to check for walking or staying. We also added a new Input.GetButton("Jump"), which will check if the player pressed jump; in that case it will reset all the loop sprites and change the Y-velocity to negative gravity. This will basically make our character jump after the player presses the jump button.

We also added a Physics.Raycast to make our character move better and bug free. Then, we used the OnDrawGizmos() function to see and check if our ray from Physics.Raycast is from the right position or not. We can also use this to test or debug our game without taking the code out because it won't be shown on the real game. As we can see from the following figure, the red arrows represent where the raycast is, but we don't actually see them in the game:

The Physics.Raycast is used to check if our character is on the floor or not. If it isn't, it will tell the game that now our character isn't on the floor, making its state equal to the jumping state (or we can say that it is in the air). By using this checking statement, we will be able to play a jumping animation when our character is falling down, but the player doesn't press the jump button.

We also draw three rays:

  1. We draw the ray from the middle of our character to the bottom.

  2. We draw the ray at the very right bound of the object to the bottom; we won't draw the ray at exactly the right bound position because we don't want it to hit the edge thickness of the floor.

  3. Then, we do the same thing as with the right bound to the left bound.

Next, we check to make sure that the ray didn't hit anything and our character isn't jumping. We check the last Y-position for our character—whether it is equal to the current Y-position or not. If it is, it means the character is on the floor, so we set b_isJumping = false. If it doesn't, the character will fall down and we set b_isJumping = true. Finally, we update our character's last position on the Y-axis.

Next, we want the OnDrawGizmos() function to show our Physics Raycast in our editor scene which will give us a nice visual to see our Ray pointing to the right direction. In this function, we will use Gizmos, which is the class that basically allows us to draw the visual debugging or set-up aids in the scene view. We can get more information on how to use Gizmos from the following website:

http://unity3d.com/support/documentation/ScriptReference/Gizmos.html

Lastly, we create another Sprite class to manage our jumping texture to show the jumping animation by checking its velocity in the Y-axis. In the JumpSpriteManager, we almost have everything similar to our SpriteManager. Since we don't need a loop animation in this Jumping Sprite, but we still need to change the texture. First, we will change the main texture to t_jumpAirTextures, which will show the sprite while the character is at the top in the air. So, we check the velocity in the Y-axis, to see if it is between -2 to 2. Next, we check if the velocityY is greater than 2. This means that the player has just started jumping, but anything other than that means our character has fallen. Finally, we update our character's Tiling and Offset.

Classified Intel

There is something else we must look at in this chapter—the Physics.Raycast.

Physics.Raycast

Why do we need to shift a little bit from the collider edge by multiple 0.45 instead of 0.5?

var hit : RaycastHit;
  var v3_hit : Vector3 = transform.TransformDirection (Vector3.forward) * (f_height * 0.5);
var v3_right : Vector3 = new Vector3(transform.position.x + (collider.bounds.size.x*0.45), transform.position.y, transform.position.z);

From the line of code if (Physics.Raycast (v3_right, v3_hit, hit, 2.5)), we will see that the Raycast is drawing from the middle of our character on the right bound downward by 2.5 units. Now we can take a look at the Gizmos that we drew in the editor:

As we can see in the preceding figure, our gizmo (the red line) is basically shifting from the boundary of the box collider (green box) a little bit. This will only check at the bottom of our character because our box collider covers the line. On the other hand, if we draw the Raycast at the edge of the box collider, it will cause the problem that our character will be able to walk on air while the edge of the floor hits the Raycast, as shown in the previous figure.

One last thing for the Gizmos: if we want to see our gizmos in the game scene, we can click on the Gizmos tab on the top-right corner of the game scene:

 

Creating a key and door


In this step, we will create the finish point, which is the door in this case. We will also create a Trigger Collider, which makes it so that the player can't end the game if he/she didn't collect our item; of course it's the key to our door.

Prepare for Lift Off

Let's prepare and make sure that we have all the graphics that we need; go to the Graphics folder in the Project window, and make sure in our subfolder Level, we have doorClose.png, doorOpen.png, and key.png. Let's get start.

Engage Thrusters

Here, we will create the object's Key and Door. Let's do this as follows:

  1. Let's create the new material for our key, so go to Assets | Create | Material, name it M_Key, and set the following:

    • Shader: Transparent | Cutout | Specular

    • Main Color: R: 255, G: 166, B: 0, A: 255

    • Specular Color: R: 236, G: 224, B: 26, A: 0

    • Shininess: Drag the dragger almost to the right side

    • Base (RGB) TransGloss (A)

    • Drag-and-drop our key.png in the Graphics/Level to the texture thumbnail in the material inspector:

  2. Next, we create another material for our door; go to Assets | Create | Material, name it M_Door, and set the following:

    • Shader: Diffuse

    • Main Color: R: 219, G: 255, B: 255, A: 255

    • Base (RGB)

    • Drag-and-drop our doorClose.png in the Graphics/Level to the texture thumbnail in the material inspector

  3. Before we create our mesh object, we have to create a new Tag for our Door and Key, so go to Edit | Project Settings | Tags.

  4. Under the Element 3 type Door, and under Element 4 type Key. Now, we will create a key object by using a plane in Unity; it's very similar to our Player. So, go to the Plane prefab object in the FBX folder and drag it into the Hierarchy view.

  5. In the Hierarchy view, right-click on the Plane prefab object, and choose Rename to change the name to Key.

  6. Next, right-click on the Animation component in the Inspector view and choose the Remove Component option to remove it. We will see the pop-up window, so just click on the Continue button similar to how we did for our Player object.

  7. Click on this object and go to its Inspector view, and set the following:

    • Tag: Key

    • Position: x: 21, y: 7.5, z: 0

    • Rotation: x: 0, y: 180, z: 0

    • Scale: x: 2.75, y: 2.75, z: 2.75

  8. Assign our M_Key to this material. Then add the Box Collider to the Key as we did for our Player; go to Component | Physics | Box Collider set Size: x: 1, y: 1, z: 1 and Center: x: 0, y: 0, z: 0, and toggle Is Trigger to true.

  9. Copy the Key by pressing Command + D or Control + D to create the Door object. Then, we name it Door, assign material M_Door to it, and set the following:

    • Tag: Door

    • Position: x: 19.5, y: 16, z: 0

    • Rotation: x: 0, y: 180, z: 0

    • Scale: x: 7.5, y: 7.5, z: 1

  10. We have finished creating our Door and Key. Next, we will go back to our code and add some scripting to make our Door and Key work. Double-click our CharacterController_2D.js, and add these parameters to it:

    public var doorOpenTexture : Texture2D;
    public var doorCloseTexture : Texture2D;
    private var b_hasKey : boolean;
  11. Then, we add these lines of code to the Start() function:

       //Start with no Key
      b_hasKey = false;

    This will set the character to start without a key.

  12. Next, we add the OnTriggerEnter() function to our code; this function will check if our character hit Key or Door:

    public function OnTriggerEnter (hit : Collider) : IEnumerator {
      if (hit.collider.tag == "Key") {
        if (!b_hasKey) {
          //We hit our Key
          b_hasKey = true;
          Destroy (hit.gameObject);
        }
      }
      
      if (hit.collider.tag == "Door") {
        if (b_hasKey) {
          //If we had Key and hit door the door will open
          hit.gameObject.renderer.material.mainTexture = doorOpenTexture;
          //wait for 1 second and destroy our character
          yieldWaitForSeconds(1);
          Destroy (gameObject);
          //We close the door
          hit.gameObject.renderer.material.mainTexture = doorCloseTexture;
        }
      }
    }

    In this function, we are checking if our character hit the key or door by checking their tag. When the player hits the key, the key will destroy itself and we set our character to have a key by setting b_hasKey = true. Also, when we hit the door, we are checking if our character has the key or not. If the character has the key, it will change the door texture to doorOpen texture. Then, we wait for one second to remove our character and we change the door texture back to doorClose texture to close the door.

  13. Before we are done, we need to add doorOpen.png and doorClose.png to the Player. Go back to Unity, and click on the Player; in the Inspector view now, we will see two new parameters, Door Open Texture and Door Close Texture; drag-and-drop doorOpen.png to Door Open Texture and doorClose.png to Door Close Texture. Now we are done.

Click play and try out your game, collecting the key and going to the door. Behold the door opening and closing!

Objective Complete - Mini Debriefing

We just created a key and door object, and placed them at our level. We also created the function that will trigger when the character hits the key and door objects. Then, we changed the texture of our door object when our character had a key object and hit the door. Lastly, we waited for one second to remove our character from the scene and changed the door texture back to closed state by using yield and Destroy.

Classified Intel

We can pause or wait for the next action by using coroutines.

Coroutines

In our script, we need to wait for a second between opening the door and ending the game. We could do this by looping or performing some other task for a second, but that would stop the animations, the sound, and everything else. We get around this by using the yield command; this tells Unity to stop running our function and come back later (in our game, 1 second later as we call yield WaitForSecond(1)). By using the yield command our function becomes Coroutines and now it must return IEnumerator (Unity needs this so that it can tell when to start our function again). This means Coroutines can't return a value like a normal function. We can change most functions in our MonoBehaviours script into Coroutines, apart from the ones which already run in every frame, such as Update(), FixedUpdate(), OnGUI(), and so on. We can get more information about coroutines from the following Unity script reference:

http://unity3d.com/support/documentation/ScriptReference/Coroutine.html.

Next, we will talk about the return type. Sometimes, when we use JavaScript, we don't really care about what type to return or what type of parameters we will pass to the function, because it is really convenient to type only var myParams = 0 or function DoSomething(var). This isn't a bad thing to do, but if we are working with a team of people, it is very important to have code that is readable for others. So, it is better to have this habit. It also makes the code run faster, since it doesn't have to go and do type lookups. On the other hand, if we use C#, we will be forced by the language itself to type the return type of this function or the type of this parameter. So, it's a good thing to know because you will be able to read C# code easily if you have to and it is readable for everyone, even the person using C#.

 

Adding a sound and replay button


Finally, we are in the last step of this chapter. We will add sound effects and a simple replay button for us to be able play this again.

Prepare for Lift Off

Let's make sure that we have all the graphics that we need for the replay button; go to the Chapter1 folder in the Project window, and make sure we have restartButtonOut.png and restartButtonOver.png in our subfolder Buttons. We also need some sound effects to use for our character, go to our Sound subfolder. We will see button_click. aiff, doorOpen.wav, getKey.aiff, and Jump.wav. Unity, by default, translates every sound that we import in our project to 3D, but we don't really need it as we are creating a 2D game. So, we will click on each sound in the Sound folder in the Project view and go to their Inspector window and uncheck 3D Sound and then click on the Apply button:

Engage Thrusters

In this section, we will create the button and script for the restart button:

  1. First, we need to create a simple TextureButton class to control our restart button (Assets | Create | Javascript) and name our script to TextureButton. Double-click to open MonoDevelop and add the following code:

    public var normalTexture : Texture2D;
    public var rollOverTexture : Texture2D;
    public var clickSound : AudioClip;
    public var key : GameObject;
    public var Player : GameObject;

    From the preceding code, we have two Texture2D parameters, normalTexture and rollOverTexture, for the restart button when it's in the rollout and rollover state. We also have an audio to play a click sound FX.

  2. Next, we have key and Player GameObject, which we will assign the prefab of key and Player to use when the game is at an end. We are creating a function to change our restart button texture when the user performs rollover and rollout. Add the following script:

    public function OnMouseEnter () : void {  //Mouse Roll over function
      guiTexture.texture = rollOverTexture;
    }
    
    public function OnMouseExit() : void { //Mouse Roll out function
      guiTexture.texture = normalTexture;
    }
  3. Now, we will create the function that will reset our character back to the start position, and the key will appear in the scene again by using Instatiate to clone our object from our prefab object in the projects, which we will create at a later state.

  4. Finally, we have @script RequireComponent(AudioSource) to basically force the script to add an AudioSource script to our restartButton, and to prevent the error when we are running this script without the AudioSource script. Let's add the following script:

    public function OnMouseUp() : IEnumerator{ // Mouse up function
      audio.PlayOneShot(clickSound);
      yield new WaitForSeconds (1.0); //Wait for 0.5 secs. until do the next function
      //Create a new Player at the start position by cloning from our prefab
      Instantiate(Player, new Vector3(Player.transform.position.x, Player.transform.position.y, 0.0), Player.transform.rotation);
      //Create a new key at the start position by cloning from our prefab
      Instantiate(key, new Vector3(key.transform.position.x, key.transform.position.y, 0.0), key.transform.rotation);
      //Hide restart button
      guiTexture.enabled = false;
    }
    
    @scriptRequireComponent(AudioSource)
  5. Then, we go back to our CharacterController_2D.js to add sound code for playing the sound effect and a restart button. Add these parameters to the top of this class:

    private var restartButton : GUITexture;
    public var doorOpenSound : AudioClip;
    public var getKeySound : AudioClip;
    public var jumpSound : AudioClip;
  6. Add code to get the restartButton from our game scene; put this code in the Start() function:

    //Get restartButton from the Game Scene  
      restartButton = GameObject.FindWithTag("RestartButton").guiTexture;
      //make restart Button disabled
      restartButton.enabled = false;
  7. Add a jump sound, inside the Update() function and inside if (Input.GetButton("Jump")) {} as follows:

    if (Input.GetButton("Jump")) { //Jump
          b_isJumping = true;
          //Then make it Jump
          audio.volume = 0.3;
          audio.PlayOneShot(jumpSound);
          loopSprites[0].resetFrame();
          loopSprites[1].resetFrame();
          rigidbody.velocity = new Vector3(rigidbody.velocity.x, -Physics.gravity.y, 0);
        }
  8. Next, we add the getKey sound, doorOpen sound, and enable our restartButton. Go to OnTriggerEnter() and update the code. First, inside if (hit.collider.tag == "Key") {} add the highlighted code:

    if (hit.collider.tag == "Key") {
        if (!b_hasKey) {
          //We hit our Key
          audio.volume = 1.0;
          audio.PlayOneShot(getKeySound);
          b_hasKey = true;
          Destroy (hit.gameObject);
        }
      }
  9. Inside if (hit.collider.tag == "Door") {}, add the highlighted code:

    if (hit.collider.tag == "Door") {
        if (b_hasKey) {
          audio.volume = 1.0;
          audio.PlayOneShot(doorOpenSound);
          //If we had Key and hit door the door will open
          hit.gameObject.renderer.material.mainTexture = doorOpenTexture;
          //wait for 1 second and destroy our character
          yieldWaitForSeconds(1);
          Destroy (gameObject);
          //We close the door
          hit.gameObject.renderer.material.mainTexture = doorCloseTexture;
          //Show Restart Button
          restartButton.enabled = true;
        }
      }

    And for the last thing before we go back to Unity, put this line at the end of the code to basically force the script to add an AudioSource script to CharacterController_2D.js:

    @scriptRequireComponent (AudioSource)
  10. Then, we go back to Unity, and click on the Player to open the Inspector. We will see Door Open Sound, Get Key Sound, and Jump Sound. Then, we assign the sounds to these as follows:

    • Door Open Sound: doorOpen.wav

    • Get Key Sound: getKey.aiff

    • Jump Sound: Jump.wav

  11. Next, we need to add an Audio Source script to be able to use our sound for the Player. This is because we already attached this script to the game object, so Unity doesn't add it for us.

  12. Go to Component | Audio | AudioSource. There, create three prefabs for the key, Player, and restartButton. Go to Assets | Create | Prefab three times, and name all of them as follows: Key, Player, and restartButton.

  13. Next, we drag our Player in Hierarchy to the Player Prefab in the Project window. We will also do the same with Key; drag our Key in Hierarchy to the Key Prefab in the Project window.

  14. For the restartButton, we need to create a new tag; go to Edit | Project Settings | Tags. Under Element 5 type RestartButton.

  15. Next, create a new GUI Texture object, which is for our replay button GameObject | Create Other | GUI Texture and name it restartButton, and in the object inspector set it as follows:

    • Tag: RestartButton

    • Position: x: 0.5, y: 0.5, z: 0

    • Rotation: x: 0, y: 0, z: 0

    • Scale: x: 0, y: 0, z: 1

    • GUITexture:

    • Texture: Drag-and-drop restartButtonOut.png here

    • Color: Leave it as default

    • Pixel Inset:

    • X: -64, Y: -16, Width: 128, Height: 32

  16. Drag restartButton in Hierarchy and drop to the restartButtonPrefab in the Project Window, click on the restartButtonPrefab in the Project window, and drag our TextureButton.js script to restartButtonPrefab. In the Inspector, we add all objects needed for Texture Button (Script) as follows:

    • Normal Texture: Drag-and-drop restartButtonOut.png in here

    • Roll Over Texture: Drag-and-drop restartButtonOver.png in here

    • Click Sound: Drag-and-drop button_click.aiff here

    • Key: Drag-and-drop Key Prefab here

    • Player: Drag-and-drop Player Prefab here

Ok, now we are done; click play to see what we have. Now, when we collect the key and go inside the door, we will see a restart button appear; click on this button and the game will restart.

Objective Complete - Mini Debriefing

We just finished creating a restart button for our platform game. We used Destroy and Instantiate to remove and create a new clone of the object from the prefab. We also added a sound effect to our restart button and character. Then, we set audio.volume to 1.0; to set the volume of our sound effect and used audio.PlayOneShot(AudioClip); to play a sound effect once it is triggered.

Classified Intel

In restartButton, we can also add Application.LoadLevel(LevelName) to reset our game, which is much easier than using instantiate, but the Application.LoadLevel will destroy all the game objects in the scene and reload again. In this case, we use instantiate in our game because we only have one scene and don't want to load the whole game level again. However, we can also put DontDestroyOnLoad() in the Awake() function of the object that we don't want to destroy, but it needs a bit of setup. So, there is no right or wrong. It depends on what we want to use or where we want the project to go.

 

Game over-Wrapping it up


We just created a simple 2D platform game, and it is our first piece to get started with Unity. In this chapter, we have learnt how to manage a sprite animation by adjusting the Tiling and Offset of the material. We have gone through the MonoDevelop scripting editor and created a JavaScript class. Also, we have learnt the basics of how to use Input Manager, Physics Raycast, Gizmos, and Collider. Finally, we have attached the sound effect and a restart button to our game. Let's take a look at what we have:

 

Are you ready to go gung ho? A Hotshot challenge


Now we have a game that looks good, but it's not complete, yet. So, why don't you try to do something by using the knowledge gained from this chapter to add more fun to your game and make it look better? Let's try the following:

  • Add a background music and more sound effects

  • Make more challenges in our level, such as create a movable platform, collect more items to open the door, or even have a longer level

  • Add obstacles that can make your character dead, lose Hit Points, or restart to another position

  • Add Hit Point for our character

  • Create an animated background or level by using the concept from our SpriteManager class swapping the texture

  • Create a parallax background by adding more layers for the background or foreground object

About the Author

  • Jate Wittayabundit

    Jate was born in Bangkok, Thailand in 1980 and has a passion for both arts and mathematics. He received a Bachelor’s Degree of Architecture in 2003 and used to be an interior architect for several companies. Then,he came to Ottawa, Canada in 2005 and graduated from the Game Development program at Algonquin College in 2008. Since he graduated from the Game Development program, he started working at Launchfire Interactive Inc. (http://www.launchfire.com) as a Flash Actionscript Programmer and developed many games and interactive contents (for the client such as Dell, Alaska Airline, etc.). In 2009, he decided to move to Toronto, bigger city, to get more chances to work in Game industry. He started a new position as a Game Developer & 3D Artist at Splashworks.com Inc. (http://www.splashworks.com). At Splashworks, he had a great chance to work with many different games and clients (such as, Shockwave, Swiss Chalet, and so on). It also gave him a chance to get to know Unity3D and love just how friendly UI is and how fast the workflow is. The first video game he ever played was “Super Mario Bros.” and he has loved playing games ever since. He believes that being an Architect is also his strength; it supports his concepts and ideas of how the real world could apply in the virtual world. In his spare time, he loves to work on 3D software, such as Zbrush, or 3D Studio Max. He also loves painting and drawing. Currently, he's trying to marry his architectural and 3D skills with his game development skills to create the next innovation game. His website – http://www.jatewit.com His Zbrush Character - http://www.zbrushcentral.com/showthread ... ight=tyris

    Browse publications by this author
Book Title
Access this book, plus 7,500 other titles for FREE
Access now