8. Ticker Taker – Unity 3.x Game Development by Example

Chapter 8. Ticker Taker

Now that your veins are coursing with Unity GUI superpowers, the keep-up game that you built a few chapters ago is looking pretty feeble. Get used to it: as you grow your skills, you'll look at your earlier games and think, "Gee, I could have done that a different way and wound up with a much better product," or more likely, "MAN, that game is weak".

It's high time we revisit that keep-up game and add the stuff we said we would add to make it play properly. Open up your keep-up game Unity project by going to File | Open Project.... If you don't have the file any more, you can download it from the Packt website (www.packtpub.com). When the project finishes loading, double-click on the Game Scene to see the ball and the paddle, just as we left them.

In this chapter, we'll:

  • Replace our boring primitives with real 3D models

  • "Skin" the keep-up game to make it more awesome

  • Add a keep-up counter to the screen to keep track of our score

  • Detect when the player drops the ball, and then add a score recap and a Play Again button

Welcome to Snoozeville

Let's face it: the keep-up game is dull. You've got a ball and a paddle. We're going to add a score counter, but that's just not going to cut it. We need a fun theme to hold the player's interest, and to set our Unity game apart from the rest.

How's this? Let's call the game Ticker Taker. We'll replace the ball with a human heart and the paddle with a pair of hands holding a teevee dinner tray. We'll string these bizarre elements together with a compelling story:

Mr. Feldman needs a heart transplant stat and there's no time to lose! Help Nurse Slipperfoot rush a still-beating human heart through the hospital hallways to the ER, while bouncing it on a dinner tray! Drop the heart, and it's lights-out for Feldman!

With a simple story re-skin, we now have a time-limited life and death drama. Doesn't that sound more exciting than "bounce the ball on the paddle"? Which game would you rather play?

To pull this off, we need to import the assets package for this chapter. When the Importing package dialog game: Importing package dialog" pops up, leave everything selected and click on the Import button.

Model behavior

Let's take a look at what we just added to the Project folder: two items with mysterious blue icons called handsAndTray and heart. These models were created in a free 3D software package called Blender and exported to the .fbx file format.

Note

When things don't mesh

Remember that Unity doesn't actually ship with any 3D modeling tools. If you want your game to contain more than just primitives spheres, boxes, cylinders, and so on you'll need to get your hands on some 3D models. You can buy them, create them yourself, or cozy up with a 3D artist and bat your eyelashes at him.

Just as the 2D images for the Robot Repair game were created in a drawing package and then imported, these 3D models were built in Blender and brought into the project. All imported assets wind up in the Assets folder behind the scenes which, you'll recall, is not to be fiddled with outside of Unity. There is complex metadata keeping track of everything in that folder, and monkeying around with it could break your project. Check the indices of this book for different resources you can use to create or buy assets for your own games.

Time for action – Exploring the models

Click on the little gray arrow next to the handsAndTray model. This model contains two separate meshes a pair of hands, and a hospital dinner tray. There seem to be two instances of each, with two different icons next to them a blue cube icon with a little white page and a black meshy/spiderwebby-looking icon.

Here's what's happening. The top-level handsAndTray parent is the .fbx file that Unity imported. Before you can use this file inside the program, Unity runs it through an import process to make sure that the models are the right size and orientation, along with a slew of other settings. The routine that preps the models for use in Unity is the FBXImporter. You can see it by clicking on the parent handsAndTray model in the Project panel.

There's a lot of fancy stuff going on with the FBXImporter. Thankfully, we don't have to touch much of it. Our biggest concern is that the models are facing the right way, and are the right size. Different models from different 3D software packages can import in funny ways, so this is like our check-in counter to make sure everything's okay before we admit the model into our Hotel de Game. At the very bottom of the FBXImporter, you can see what the model looks like.

Note

Rank and file

Unity has superpowers. It's true. Even better, it's like one of those superheroes that can absorb other superheroes' powers. Unity doesn't have modeling or art tools itself, but as we've seen, it can import meshes and images from the outside world. And if you have a supported 3D software package (like 3D Studio Max, Maya, or Blender) or 2D software package (like Photoshop) installed on your computer, Unity can import the native file format. That means your .max, .ma, .blend, and .psd files will be sitting right there in the Assets folder. If you double-click on one of these assets, Unity is smart enough to launch the corresponding program for you. And if you make a change to the file and save it, the results are automatically updated right inside your project's Assets folder. No need to re-import the file!

You can also tell Unity which program you'd like to launch when you double-click on a file with a format that can be read by many different programs, like .fbx or .jpg. Here's a list of native 3D file formats and software packages that Unity supported at the time of writing:

  • Maya .mb and .ma

  • 3D Studio Max .max

  • Cheetah 3D .jas

  • Cinema 4D .c4f

  • Blender .blend

  • Carrara

  • Lightwave

  • XSI 5.x

  • SketchUp Pro

  • Wings 3D

  • 3D Studio .3ds

  • Wavefront .obj

  • Drawing Interchange Files .dxf

  • Autodesk FBX .fbx

Note

The ability to handle native file formats for a number of major software applications is one of the coolest things about Unity. If you work a lot with Photoshop, you'll appreciate that you don't have to flatten your image and save it as another format just hide or show your various layers, and save the file. Unity automatically flattens the PSD for you and updates the image.

Time for action – Hands up!

Let's get the hands and tray into our Scene!

  1. Click on GameObject | Create Empty. This is the GameObject that will contain the hands and tray models.

  2. Rename the new Game Object as HandsAndTray.

  3. Click-and-drag the handsAndTray model, which contains the Tray_mesh and Hands_mesh models, from the Project panel to the new HandsAndTray Game Object that you just created in the Hierarchy panel. You'll see the models appear indented beneath the GameObject, which will gain a gray arrow to indicate that it's now a parent.

  4. Click on the HandsAndTray Game Object in the Hierarchy panel, and change its position/rotation/scale settings in the Inspector panel:

    Position     X:-0.18 Y: -0.4     Z:—0.2
    Rotation    X:8   Y:180     Z:0
    Scale          X:1   Y:1   Z:1
    

What just happened size matters

When you change the position and rotation values, the HandsAndTray Game Object swings into view near our old Paddle Game Object, but the meshes are disappointingly tiny. Clearly, something went wrong during the FBX import process. We could scale the models' transforms up inside Unity, but strange things have been known to happen when you mess with a model's scale after it's been imported. Animations break, colliders stop colliding properly, the sun turns blood-red… it's a bad scene.

Time for action – Changing the FBX import scale settings

Let's revisit the FBXImporter and crank up the scale to make our models the proper size with no breakage.

  1. Click on the blue HandsAndTray model in the Project panel. The FBXImporter will appear in the Inspector panel.

  2. Change the Scale factor near the top of the FBXImporter from 0.01 to 0.03.

  3. Click on the Generate Colliders checkbox. (We'll find out what this does very shortly.)

  4. Click on the Apply button near the bottom of the FBXImporter. You may have to scroll down, depending on your screen size.

You should see the handsAndTray models scale up within the Game view to a reasonable size. That's better!

Note

Auto-generating colliders

The checkbox that we ticked in the FBXImporter tells Unity to put a collider cage around our model once it's imported. You may remember that the spherical Ball Game Object and the cubic Paddle Game Object both got their own collider components when we created them a Sphere Collider for the Ball, and a Cube Collider for the Paddle.

Unity uses an additional copy of the mesh as the collision "cage" around the object. This is great if you have a strangely-shaped mesh, and you want things to collide with it naturally. The trouble is that your game will take a performance hit with a more complex collider. If you can get away with adding your own primitive (Cube/Sphere/Capsule) collider to your model to make things less complicated for the physics engine, you should. Our Ticker Taker game has very little complexity, so I think we can indulge ourselves with a fancier collider on our tray model. Maybe later, we'll take a long soak in the tub and paint our toenails?

Time for action – Making the mesh colliders convex

Provided our mesh is made from fewer than 255 triangles, one thing that we can do to make our Mesh Colliders behave better is to mark them as "convex". Game Objects that are in motion (like our HandsAndTray soon will be) tend to react better with other moving colliders if we make this small change.

  1. In the Hierarchy panel, hold down the Alt key on your keyboard and click on the grey arrow. Instead of just expanding the top-level item, this expands the entire tree beneath the parent GameObject.

  2. Select Hands_Mesh.

  3. In the Inspector panel, under the Mesh Collider Component, check the box labeled Convex.

  4. Do the same for Tray_Mesh.

Now we're all set up to put some motion in the ocean. Let's turn our eyes towards making this imported mesh move around the same way our boring primitive paddle does.

Time for action – Making the hands and tray follow the mouse

Any script that we create in Unity is reusable, and we can attach scripts to multiple Game Objects. Let's attach our MouseFollow script to the HandsAndTray Game Object.

  1. Find the MouseFollow script in the Project panel (I put mine inside a Scripts folder), and drag it onto the HandsAndTray Game Object in the Hierarchy panel. (Make sure to drop it on the parent HandsAndTray Game Object, not the child handsAndTray model.)

  2. Test your game by clicking on the Play button.

The hands and tray should follow your mouse the very same way the paddle does. Because we generated a collider on the tray in the FBXImporter settings, the Ball should bounce on the tray just as it does with the Paddle.

The only trouble is that the hands and tray may be flipped backwards. That's because in the MouseFollow script, we're telling the paddle to constantly tilt towards y=0. Our HandsAndTray Game Object is rotated 180 degrees in the Y axis. No worries it's an easy fix.

Open the MouseFollow script and find this line of code:

var target = Quaternion.Euler (tiltAroundX, 0, tiltAroundZ);

Change the y rotation target to 180, like so:

var target = Quaternion.Euler (tiltAroundX, 180, tiltAroundZ);

Et voila the hands and tray point in the correct direction.

What just happened monkey see, monkey do

Because both the Paddle Game Object and the HandsAndTray Game Object have the same script attached, they both do exactly the same thing. Imagine a game where you have a number of enemies onscreen, all following the same Script:

  1. Hunt the player.

  2. Eat his face.

Reusable scripts make rampant face-eating possible, with just a single script.

Time for action – Get your heart on

Just as we did with the hands and tray models, we're going to create a new Heart Game Object and parent it to our imported heart model, with a few adjustments in the FBXImporter.

  1. In the Project panel, click on the heart model.

  2. Change the Scale factor of the heart model in the Inspector panel to 0.07.

  3. Leave Generate Colliders unchecked.

  4. Click on the Apply button.

  5. Drag the heart model from the Project panel into the Scene.

  6. In the Inspector panel, change the heart's position values to X:0, Y:0, Z:0.

  7. In the Hierarchy panel, click on the gray arrow on the heart Game Object to reveal the Heart_Mesh inside.

  8. Click to select the Heart_Mesh, and in the Inspector panel, change its Transform position to X:0, Y:0, Z:0.

    By setting everything to position 0, we ensure that the collider we're about to add will appear at roughly the same spot where the heart model is. Now instead of using a special Mesh Collider, let's add a more primitive Capsule collider, and set up the heart so that it bounces.

  9. Select the parent heart Game Object in the Hierarchy panel, and navigate to Component | Physics | Rigidbody to add a Rigidbody component to it. This includes the heart in the physics simulation. A message will appear, warning us that we'll be Losing prefab, whatever that means. Click on Add to ignore this message. Notice that the heart Game Object label in the Hierarchy panel is no longer blue, indicating that we've broken its prefab connection. (We'll learn more about prefabs shortly.) The heart's Rigidbody component appears in the Inspector panel.

  10. Click on Component | Physics | Capsule Collider to add a pill-shaped collider to the heart. A green cage-like Collider mesh appears around the heart in the Scene view.

  11. Change the settings on the Capsule Collider in the Inspector panel to:

    Center

    X: -0.05 Y: 0.05 Z: 0

    Radius: 0.2

    Height: 0.6

    That should make the Capsule Collider fit snugly around the heart model.

  12. Still in the Capsule Collider Component, click on the little circle button in the field labeled Material, and choose the custom BouncyBall PhysicMaterial (which we created in an earlier chapter). This is what will give the heart its bounce.

  13. Finally, adjust the heart Game Object's Transform position in the Inspector panel to X: 0.5, Y: 2.0, Z: -0.05. This will place the heart right next to the Ball.

  14. Save the Scene and test the game by clicking on the Play button. Paddles, trays, hearts, and balls should all be bouncing around. In fact, it's a smidge confusing. Let's take the old Paddle and Ball out of the equation and focus on the new stuff.

Time for action – Ditch the ball and paddle

Click on the Paddle in the Hierarchy panel, and uncheck the checkbox next to its name in the Inspector panel to make it disappear. Do the same for the Ball. Retest the game. The Ball and Paddle are turned off; you should see only the heart bouncing on the tray.

What just happened bypass the aorta

The reason we're using a Capsule Collider on the heart instead of a Mesh Collider is that the heart model is pretty weird and irregular. It could catch its aorta on the corner of the tray and go spinning unpredictably out of control. The Capsule Collider gives the heart a little more predictability, and it won't be glaringly obvious that the heart isn't colliding perfectly. (Of course, a capsule shape is a little wonkier than the Sphere Collider we were using earlier. This capsule is kind of a compromise.)

Time for action – Material witness

Currently, the models we're using are about as exciting as clipping your toenails over the sink. All three models are a dull, default gray. Let's change that by creating some custom Materials, and applying them to the models.

Note

Out with the old

If you had eaten your Wheaties this morning, you may have noticed that when we imported the models, their Materials were imported as well. The Project panel has a folder called Materials in it, which includes the three drab, boring Materials that we currently see on our models. Unity will import the materials that we create for our models depending on the 3D software package we used. Since these three models were not given Materials in Blender, they came in with default gray Materials. Now's a good time to select the Materials folder in the Project panel and press the Delete key (Command + Delete if you're on a Mac) to get rid of it. After confirming this action, the models turn pinky-purple. Don't worry we'll fix that.

  1. Right-click/secondary-click any empty area in the Project panel (or click on the Create button at the top of the panel) and choose Create | Material. A New Material appears in the Project panel.

  2. Rename the New Material as Skin Material.

  3. Select the Skin Material and click on the color swatch (next to the eyedropper) in the Inspector panel.

  4. Choose a skin color for Nurse Slipperfoot. I chose R 251 G 230 B 178 for a Caucasian nurse, but feel free to make her whatever color you like.

  5. Close the color window by clicking on the X icon in the top-right corner (top-left corner on a Mac).

  6. Click-and-drag the Skin Material from the Project panel to the Hands_Mesh in the Hierarchy panel. You may have to click on the gray arrows to expand the parent/child tree beneath the HandsAndTray Game Object in order to see the Hands_Mesh.

    In a twinkling, Nurse Slipperfoot's hands go from a hot fuschia to a rosy peach.

What just happened understanding Materials

We've used Materials in a few other situations so far for fonts and other goofy things, but applying Materials to models is the classic example. If meshes are like chicken wire, Materials are like the papier-mâché coating we lay over them to give them "skin". A Material is a collection of shaders and textures that affect how light bounces off our models.

This diffuse, flat color is about as simple as a Material can get. If we wanted to go all-out, we could draw a more realistic image of Nurse Slipperfoot's skin, complete with freckles, beauty marks, and fingernails, in a program like Photoshop. Then we'd import the texture into Unity as an image file, and drag-and-drop it into that Texture2D slot that you see beneath the Material's color swatch. Suddenly, Nurse Slipperfoot's arms would look far more realistic. (It should be obvious, though, that realism isn't exactly what we're going for with this game.)

Note

UV mapping

Any textures that we apply to our models this way may wind up disappointingly maligned. There's a process in 3D texturing called UV mapping, which is where you adjust the position and orientation of your textures in relation to your models. Most textures that get wrapped around anything more complex than a primitive shape require some monkeying around with the models' UV settings, which happens in your 3D art package outside Unity.

You can use textures in combination with Shaders to pull off even more impressive effects. One popular Shader called a Bump Map (Bumped Specular or Bumped Diffuse in Unity) lets you paint the "high" and "low" areas that the light will hit. By creatively painting a flat 2D texture with light and dark tones, you can indicate which parts of the image you want to appear to be raised, and which ones should appear to recede. A bump map could give Nurse Slipperfoot's thumbs the illusion that there are modeled fingernails on their ends. The advantage is that you get this illusion without the cost of actually modeling these extra details the Shader is doing all the work, and is tricking the light into seeing more complex geometry than we actually have. Remember that more polygons (mesh geometry) require more computer power to draw and move around the screen, so faking this complexity is a great plan.

If you're so inclined, you can even program your own Shaders to pull off custom lighting and texturing effects on your models. That topic is way outside the scope of a beginner book, but if writing custom Shaders interests you, don't let me hold you back. Go forth and code! You can find some good resources for learning about more complex stuff like custom Shaders in the back of this book.

Have a go hero Adding Materials to the other models

Now that you know how to create your own Materials, create two more Materials for your other models one for the tray and one for the heart. I chose a bright red color for the heart (R:255 G:0 B:0), and made it revoltingly wet-looking and shiny by choosing Specular from the Shader drop-down. Then I cranked the "Shininess" slider up. If you had the inclination, you could draw a veiny texture in your favorite 2D program and import it into Unity, and then drag it onto the Heart Material. Gross!

When you're finished creating and adding new Materials to the tray and the heart, and dropping them into a new folder called "Materials", let's continue.

This just in: this game blows

Despite all the work we've done to add a fun theme, there's still a fundamental problem with our keep-up game. 2D is one thing, but because we're expecting the player to bounce the heart in three dimensions, the game is nigh impossible. I can only get a few bounces in before the heart goes flying off into Siberia. But thanks to the miracle of cheating, we can actually fix this problem, and the player will feel much better about playing our game.

Time for action – Multiple erections

We're going to erect four invisible walls around the play area to keep the heart reined in.

  1. Click on GameObject | Create Other | Cube.

  2. Rename the new Cube GameObject as Wall Back.

  3. Give the Cube these transform settings in the Inspector panel:

    Position     X:-0.15    Y: 1    Z: 1.6
    Rotation:   X: 0    Y: 90    Z: 0
    Scale:         X: 0.15    Y: 12   Z: 6
    
  4. Uncheck the Mesh Renderer checkbox in the Inspector panel to make the back wall invisible to the player.

  5. Repeat these steps to create three more cubes called Wall Front, Wall Left, and Wall Right. The best way to do this is to duplicate the Front Wall three times. Just right-click/alternate click on the Wall Front Game Object and choose Duplicate from the context menu. These are the settings I used to place the other walls, but feel free to tweak them to suit your taste:

    Wall Front

    Position      X: -0.7   Y: 1.4   Z: -2.4
    Rotation     X: 0   Y: 90   Z: 0
    Scale           X: 0.15   Y: 12   Z: 5.6
    

    Wall Left

    Position     X:- 2   Y: 1.4   Z: 0.56
    Rotation    X: 0   Y: 17.6   Z: 0
    Scale          X: 0.15   Y: 12   Z:6
    

    Wall Right

    Position     X: 1.6   Y: 1.4   Z: -0.06
    Rotation    X: 0   Y: 348   Z: 0
    Scale          X: 0.15   Y: 12   Z:6
    
  6. Remember to turn off the Mesh Renderer settings for each wall. You can turn them back on in case you need to fine-tune the positioning of each wall.

  7. I made a slight tweak to the handsAndTray transform, nudging the X rotation value to 16.

It's like TRON up in here! Save the Scene and test the game. To the player, it seems as if the heart is bouncing off Nurse Slipperfoot's forehead, or the edges of the screen, but we know better. We've actually cheated in favor of the player, to help him have more fun and success with our game.

Note

In defense of the cube

If you've poked around Unity a little on your own, you may have wondered why we placed four cubes instead of four planes. For starters, planes are one-sided and they disappear when you look behind them, making it a little more difficult to position them into place. And because they're one-sided, we risk having the heart pass right through them if they're oriented the wrong way.

For the home stretch, we'll repeat a few of the steps we've already learned to display the number of hits the player achieves while playing, and to add a Play Again button when we detect that the heart has plummeted past the tray and into the abyss.

Time for action – Creating a font texture

First, let's create a custom font texture to display some GUIText showing the player how many times he's bounced the heart.

  1. Create a new GUIText object and call it Bounce Count. Follow the instructions in Chapter 7 (in the Creating a font texture and material section) for creating a GUIText object and mapping a font material to it. This time, I chose a font called "Cajun Boogie" because it's ridiculous.

  2. Change the font size in the Font Exporter. I chose 45 pts for the font your mileage may vary.

  3. In the Inspector panel, set the Bounce Count transform position to X:0.9 Y:0.9 Z:0 to place it in the upper-right corner of the screen.

  4. Choose Anchor: Upper Right and Alignment: Right from the GUIText component settings in the Inspector panel.

Now we have a fun onscreen GUIText GameObject to display the player's bounce score.

Time for action – Creating the HeartBounce script

We need to attach a new script to the heart. The script will respond to two important situations: when the heart hits the tray, and when the heart misses the tray completely and dive-bombs into the great beyond. Let's create the script and add some simple code to it.

  1. Right-click/secondary-click on the Project panel (or use the Create button) and choose Create | JavaScript.

  2. Rename the new script as HeartBounce.

  3. Double-click to open the script in the script editor.

  4. Add the following code to the top of the script above the Update function:

    function OnCollisionEnter(col : Collision) {
      if(col.gameObject.tag == "tray") {
         Debug.Log("yes! hit tray!");
      }
    }
    

What just happened charting a collision course

There's some new code happening here. OnCollisionEnter is a built-in function that gets called when the Game Object's collider touches (or collides with) another Game Object's collider, as long as one of those colliders is attached to a Game Object with a non-kinematic Rigidbody Component (like our heart). We use the variable col (short for collision) to store the argument that gets passed in. That argument contains a reference to whatever it was we hit.

One of the ways we can find out exactly what we hit is to ask for its tag. Here we're asking whether the collision's Game Object is a tagged tray. In order for this to work, we need to learn how to tag things.

Time for action – Tagging the tray

Save and close the HeartBounce script we'll come back to it in a jiffy. First, let's tag the tray Game Object so that we can determine if the heart has collided with it.

  1. Click on the HandsAndTray Game Object in the Hierarchy panel.

  2. In the Inspector panel, just beneath the Game Object's name, is a drop-down labeled Tag. Choose Add Tag from the bottom of this drop-down list. (By default, all Game Objects are marked "Untagged")

  3. We're taken to the Tag Manager. Click on the gray arrow beside the word Tags at the top of the list.

  4. There's an invisible text field next to the line labeled Element 0. This takes a leap of faith the first time you do it. Click on the blank area beneath the 1 on the Size line. Then type the word tray. Press the Enter key to make it stick. Notice that Unity adds a new blank tag for us, labeled Element 1.

  5. Click to select the Tray_Mesh in the Hierarchy panel (you may need to click on the gray arrows to expand the hierarchy beneath the HandsAndTray Game Object). When we chose "Add Tag," you may have thought that we were adding a tag called "tray" to the HandsAndTray GameObject. In fact, "Add Tag" actually means "add a tag to the list of tags we can choose from". The new tag isn't automatically added to any GameObject.

    Now that we've added the tray tag, we can select it from the drop-down list.

  6. With the Tray Mesh selected, choose tray from the Tags drop-down in the Inspector panel to tag the Tray Mesh.

  7. Tag the Hands_Mesh with the tray tag as well.

  8. Click-and-drag the HeartBounce script to the heart Game Object to attach it.

Save the Scene and test your game. Keep an eye on the message bar at the bottom of the screen. When the heart hits the tray or hands models, you should see a message saying yes! Hit tray!

Time for action – Tweak the bounce

Now that we can detect when the heart is hitting the tray, there's no end to the fun we can have! While we're in the code, let's make a quick change to make the gameplay slightly better.

  1. Double-click to open the HeartBounce script.

  2. Type the following at the top of the script:

    var velocityWasStored = false;
    var storedVelocity : Vector3;
    function OnCollisionEnter(col : Collision) {
      if(col.gameObject.tag == "tray") {
        Debug.Log("yes! hit tray!");
        if (!velocityWasStored) {
          storedVelocity = rigidbody.velocity;
          velocityWasStored = true;
        }
        rigidbody.velocity.y = storedVelocity.y;
      }
    }
    

Save the script and test the game. You may not notice a difference at first, but we've made a clear improvement to our mechanic.

What just happened storing velocity

The trouble with Unity's physics simulation is that it's almost too good. Left to its own devices, our heart will bounce less and less until it eventually runs out of steam and comes to rest on the dinner tray. Realistically, this might actually be what a still-beating human heart would do, given the circumstances. I'm too squeamish to find out for sure.

But this isn't realism this is video games, baby! This is the land of gigantic guns and anthropomorphic mushrooms! We want that heart to bounce forever, with no degradation over time when the heart bounces against the tray and the walls.

That's what our little script does. The first time the heart bounces, we store its velocity (distance over time) in a variable. Then we inject a little high-velocity serum into the heart every time it hits the tray, overriding the slowly degrading velocity that the heart suffers from hitting our invisible walls. We use the velocityWasStored flag to determine whether the heart has bounced yet.

Time for action – Keeping track of the bounces

Let's introduce a few more variables to record how many times the player has bounced the heart.

  1. Change the code to add these three variables at the top:

    var hitCount:GUIText;
    var numHits:int = 0;
    var hasLost:boolean = false;
    var velocityWasStored = false;
    var storedVelocity : Vector3;
    
  2. Save the script and return to Unity.

  3. Select the Heart in the Hierarchy panel.

  4. We've created a variable (bucket) to hold a GUIText object. Drag the Bounce Count GUIText Game Object from the Hierarchy panel into the GUIText slot of the heart in the Inspector panel (or choose it from the pop-up menu). Now, whenever we refer to hitCount in our script, we'll be talking about our GUIText Game Object.

  5. Add the following code to the Update function:

    function Update() {
      var str:String = "";
    
      if(!hasLost){
        str = numHits.ToString();
      } 
      else {
        str = "Hits:" + numHits.ToString() + "\nYour best:" + 
    	bestScore;
          
        if(bestScore > lastBest) str += "\nNEW RECORD!";
      }
       
      hitCount.text = str;
    }
    

What this script will do is check the hasLost flag on every Update, and change the hitCount GUIText to show either just the bounce count, or a big score recap at the end of the game that will look like this:

Hits: 32 Your Best: 12 NEW RECORD!

Note that we don't have any logic yet to flip the hasLost flag, nor do we have any code to increment the numHits variable. Let's add those two things next.

Note

\n

This code creates a line break. Whenever you see it, you can think of it as if someone has hit the Enter key to space out some text. Be aware that it only works inside Strings.

Time for action – Adding the lose condition

The easiest way to figure out if the player has lost is to check the transform.position.y value of the heart. If the heart has fallen through the floor, the player obviously isn't bouncing it on the tray any longer.

  1. Add the logic for incrementing the player's score (number of hits/bounces before losing):

    function OnCollisionEnter(col : Collision) {
      if(col.gameObject.tag == "tray") {
        //Debug.Log("yes! hit tray!");
        if (!velocityWasStored) {
          storedVelocity = rigidbody.velocity;
          velocityWasStored = true;
        }
        if(rigidbody.velocity.y > 1) {
        numHits ++;
        }
      rigidbody.velocity.y = storedVelocity.y;
    }
    
  2. Add the "lose the game" check to the Update function:

    function Update() {
      var str:String = "";
    
      if(!hasLost){
         str = numHits.ToString();
      } else {
        str = "Hits:" +  numHits.ToString() + "\nYour best:" +       bestScore;
          
        if(bestScore > lastBest) str += "\nNEW RECORD!";
      }
      hitCount.text = str;
      if(transform.position.y <-3){
        if(!hasLost) {
          hasLost = true;
          lastBest = bestScore;
          if(numHits > bestScore) {
            bestScore = numHits;
          }
        }
      }
    }
    
  3. Add these variables to the top of the script:

    var hitCount:GUIText;
    var numHits:int = 0;
    var hasLost:boolean = false;
    var bestScore:int = 0;
    var lastBest:int = 0;
    var velocityWasStored = false;
    var storedVelocity : Vector3;
    
    

What just happened understanding the code

We start by incrementing numHits whenever the heart hits the tray:

if(rigidbody.velocity.y > 1) {
  numHits ++;
}

Why is velocity a positive number, when the heart is actually falling down (through the negative range of the Y axis)? Because velocity doesn't describe distance it describes distance over (divided by) time. What we're actually checking here is whether the heart is moving significantly in any direction. We put this conditional check on the heart's velocity so that the player doesn't score any points if he catches the heart on the tray and it just starts rolling around.

if(transform.position.y < -3){
     if(!hasLost) {
       hasLost = true;

This is like saying "if the heart is through the floor (transform.position.y is less than negative 3), and the player hasn't lost yet, make the player lose".

Next, we record what the last high score was.

   lastBest = bestScore;

If the number of hits the player pulled off in this round beats the player's best score, reset the best score to the player's number of hits.

   if(numHits > bestScore) {
      bestScore = numHits;
   }

Save the script and test the game. The onscreen score counter updates with every hit you get, and when you drop the heart, you get a recap of your last and best scores.

Time for action – Adding the Play Again button

The very last thing we need to do is to add a button to the end of the game so that the player can play again. Let's revisit our good friend GUI from the last few chapters.

  1. Add the OnGUI function to the HeartBounce script:

    function OnGUI(){
      if(hasLost){
       var buttonW:int = 100; // button width
       var buttonH:int = 50; // button height
       
       var halfScreenW:float = Screen.width/2; // half of the Screen 
       width
       var halfButtonW:float = buttonW/2; // Half of the button width
    
         if(GUI.Button(Rect(halfScreenW-halfButtonW, Screen.height*.8, 
    	 buttonW, buttonH), "Play Again"))
         {
           numHits = 0;
           hasLost = false;
    velocityWasStored = false ;
           transform.position = Vector3(0.5,2,-0.05);
           rigidbody.velocity = Vector3(0,0,0);
         }
      }
    }
    
    

What just happened?

For GUI pros like us, this script is a piece of cake.

The whole function is wrapped in a "has the player lost the game?" conditional statement.

We start by storing some values for half of the screen's width and height, and the width or height of the button.

var buttonW:int = 100; // button width
var buttonH:int = 50; // button height
   
var halfScreenW:float = Screen.width/2; // half of the Screen width
var halfButtonW:float = buttonW/2; // Half of the button width

Next, we draw the button to the screen, and reset some game variables if the button is clicked:

if(GUI.Button(Rect(halfScreenW-halfButtonW, Screen.height*.8,     buttonW, buttonH),"Play Again")){
     numHits = 0;
     hasLost = false;
     velocityWasStored = false;
     transform.position = Vector3(0,2,0);
     rigidbody.velocity = Vector3(0,0,0);
}

It's important that we reset the heart's starting position and the velocity of its Rigidbody component. Try commenting out either of these lines or both of these lines and see what happens!

Save the script and try it out. Ticker Taker now uses 3D models and an onscreen score counter to elevate a standard, dull keep-up game to a madcap emergency ward adventure.

Ticker taken

Let's recap the mad skillz we picked up in this chapter. We:

  • Added 3D models to our game

  • Created some simple Materials

  • Learned how to tag Game Objects

  • Detected collisions between Game Objects

  • Overrode the physics simulation with our own code

  • Programmed an onscreen score counter and recap

We could still do a much better job of conveying the story and setting to the player in Ticker Taker, so let's put another pin in this game and return to it in a later chapter. In the next action-packed installment, we'll get a little more practice with GameObject collisions, and we'll learn what all the fuss was about when Unity popped up that mysterious prefab warning. Prefabs will change the way you use Unity! I can't wait.