Chapter 4. Getting Started Building the Game

Knowing how to navigate Unity’s interface is one thing. Creating an entire game with it is another. In this section of the book, you’ll take what you’ve learned in Part I, and use it to create a 2D game. By the end of this part, you’ll have Gnome’s Well That Ends Well, a side-scrolling action game (see Figure 4-1 for a sneak peek at what the game looks like).

mgdu 0401
Figure 4-1. The finished product

Game Design

The gameplay of Gnome’s Well is straightforward. The player controls a garden gnome, who’s being lowered by an attached rope into a well. At the bottom of the well there’s some treasure. The catch is that the well is filled with traps that kill the gnome if he touches them.

To begin with, we created a very rough sketch showing how the game would look. We used OmniGraffle, an excellent diagramming application, but the choice of tool doesn’t especially matter—pen and paper is just as easy, and can often be better. The goal is to get a very rough sense of how the game will be put together, as quickly as possible. You can see our Gnome’s Well sketch in Figure 4-2.

mgdu 0402
Figure 4-2. The rough concept sketch for the game

Once we decided on what the game was going to be, we started deciding the overall architecture. We started by working out what the visible objects were, and how they’d relate to one another. At the same time, we began thinking about how the “invisible” components would work—how input would be collected, and how the game’s internal managers would communicate with each other.

Finally, we also considered the visuals of the game. We approached an artist friend of ours, and asked him to draw a picture of gnomes trying to get down a well and being menaced by traps. This gave us an idea of what the main character could look like, and set the overall tone of the game: a silly, cartoony, slightly violent game starring greedy gnomes. You can see this final sketch in Figure 4-3.

Note

If you don’t know any artists, draw it yourself! Even if you don’t think that your skills are very good, any thoughts on what the game will look like are better than no thoughts at all.

mgdu 0403
Figure 4-3. The concept art for the gnome character

Once we’d done this preliminary design, it was possible to start working out the things that needed to be implemented: how the gnome would move around within the game, how the interface needed to be set up to make it work, and how the game objects would need to be linked together.

To get the gnome down the well, the player is given three buttons: one that increases the length of the rope, one that decreases it, and a third that displays the game’s menu. By holding on the button that increases the rope length, the gnome is lowered down the well. To avoid the traps on the way down the well, the player tilts their device left and right. This moves the gnome left and right.

The gameplay is primarily the result of 2D physics simulation. The gnome is a “ragdoll”—a collection of pieces that are connected via joints, with each piece an independently simulated rigid body. This means that, when connected to the top of the well via the Rope object, the gnome will dangle correctly.

The rope is made in a similar way: it’s a collection of rigid bodies, all connected to each other with joints. The first link in the chain is connected to the top of the well, and is connected to the second link via a rotating joint. This second joint is connected to the third, the third to the fourth, and so on, until the last link, which is connected to the gnome’s ankle. To lengthen the rope, more links are added to the top of the rope, and to shorten it, links are removed.

The rest of the gameplay is handled through very straightforward collision detection:

  • If any part of the gnome touches a trap object, the gnome is dead, and a new gnome is created. Additionally, a ghost sprite will be created that travels up the well.

  • If the treasure is touched, the gnome’s sprites are updated to show that he’s holding the treasure.

  • If the top of the well (an invisible object) is touched, and if the gnome is holding the treasure, the player wins the game.

In addition to the gnome, traps, and treasure, the game’s camera has a script running that keeps its position linked to the vertical position of the gnome, but also keeps the camera from showing anything above the top of the well or below the bottom of the well.

The way that we’ll build this game is as follows (don’t worry—we’ll walk you through it one step at a time):

  1. First, we’ll create the gnome, using temporary stick-figure images. We’ll set up the ragdoll, and connect the sprites.

  2. Next, we’ll set up the rope. This will involve the first large amount of code, since the rope will be generated at runtime, and will need to support extending and contracting the rope.

  3. Once the rope is set up, the input system will be created. This system will receive information about how the device is being tilted, and make it available to other parts of the game (in particular, the gnome.) At the same time, we’ll also build the game’s user interface, and create the buttons that extend and contract the rope.

  4. With the rope, gnome, and input system in place, we can begin actually creating the game itself. We’ll implement the traps and the treasure, and start playing the game proper.

  5. From there, it’s just a matter of polish: the gnome’s sprites will be replaced with more complex ones, particle effects will be added, and audio will be added to the whole thing.

By the end of this chapter, the game will be functionally complete, but not all of the art will be in there. You’ll be adding that later on, in Chapter 7. See Figure 4-4 for a look at where things will be at.

Note

Over the course of this project, you’ll end up adding lots of components to game objects, and tweaking the values of properties. There are a lot more components than the ones we’re telling you to change, so feel free to play around with the settings for anything you wish to change; otherwise, you can leave everything with the default settings.

Let’s get started!

mgdu 0404
Figure 4-4. The state the game will be in when we’re done with the first, unpolished version

Creating the Project and Importing Assets

We’ll start by creating the project in Unity, and do a little bit of setup. We’ll also import some of the assets that will be needed in the early stages; as we progress, more will be imported.

  1. Create the project. Choose File → New Project, and create a new project called GnomesWell. In the New Project dialog (see Figure 4-5), make sure you choose 2D, not 3D, and make sure that there are no asset packages marked for importing. You just want to make an empty project.

mgdu 0405
Figure 4-5. Creating the project
  1. Download the assets. Download the art, sound, and other resources we’ve packaged for the project from https://www.secretlab.com.au/books/unity, and unzip them into an appropriate folder on your computer. You’ll be importing these assets into your project as you continue.

  2. Save the scene as Main.scene. You may as well save the scene now, so that hitting Command-S (or Ctrl-S on a PC) will instantly save your work. The first time you save, you’ll be asked for the name of the scene and where to put it—put it in the Assets folder.

  3. Create the folders for your project. To keep things organized, it’s a good idea to create folders for different categories of assets. Unity is fine with you just keeping everything in one folder, but doing that can make finding assets more tedious than it needs to be. Create the following folders by right-clicking the Assets folder in the Project tab and choosing Create → Folder:

    Scripts

    This folder will contain the C# code for the game. (By default, Unity likes to put any new code files in the root Assets directory; you’ll need to move them into this Scripts folder yourself.)

    Sounds

    This folder will contain both the music and the sound effects.

    Sprites

    This folder will contain all of the sprite images. There are many of these, so they’ll be stored in subfolders.

    Gnome

    This folder will contain the prefabs needed for the gnome character, as well as additional, related objects like the rope, particle effects, and the ghost.

    Level

    This folder will contain prefabs for the level itself, including the background, walls, decorative objects, and traps.

    App Resources

    This folder will contain resources used by the app as a whole: its icon and its splash screen.

    When you’re done, the Assets folder should look like Figure 4-6.

mgdu 0406
Figure 4-6. The Assets folder, with the folders created
  1. Import the prototype gnome assets. The prototype gnome is the rough version of the gnome that we’ll build first. We’ll later replace it with more polished sprites.

    Locate the Prototype Gnome folder in the assets you downloaded, and drag it into the Sprites folder in Unity (see Figure 4-7).

mgdu 0407
Figure 4-7. The prototype sprites for the Gnome

We’re now ready to begin constructing the gnome.

Creating the Gnome

Because the gnome will be composed of multiple independently moving objects, we need to first create an object that will act as the container for each of the parts. This object will also need to be given the Player tag, because the collision detection system that’s used to detect when the gnome touches a trap, treasure, or the level exit will need to know if that object was the special Player object. To build the gnome, follow these steps:

  1. Create the Prototype Gnome object. Create a new empty game object by opening the GameObject menu and choosing Create Empty.

    Name this new object “Prototype Gnome”, and then set the tag of the object to Player, by selecting “Player” from the Tag drop-down list at the top of the Inspector.

    Note

    The Prototype Gnome object’s Position, which you can see in the Transform component near the top of the Inspector, should at zero on the X, Y and Z axes. If it’s not, you can click on the gear menu at the top-right of the Transform and choose Reset Position.

  1. Add the sprites. Locate the Prototype Gnome folder that you added earlier, and drag and drop each of the sprites into the scene, except for Prototype Arm Holding with Gold, which won’t be used until later.

    Note

    You’ll need to drag and drop each one individually—if you select all of the sprites and try to drag them all in at once, Unity will think that you’re trying to drag a sequence of images in, and will create an animation.

    When you’re done, you should have six new sprites in the scene: Prototype Arm Holding, Prototype Arm Loose, Prototype Body, Prototype Head, Prototype Leg Dangle, and Prototype Leg Rope.

  2. Set the sprites as children of the Prototype Gnome object. In the Hierarchy, select all of the sprites that you just added, and drag and drop them onto the empty Prototype Gnome object. When you’re done, the hierarchy should look like that shown in Figure 4-8.

mgdu 0408
Figure 4-8. The hierarchy, with the gnome sprites attached as child objects of the Prototype Gnome object
  1. Position the sprites. Once the sprites are added, they need to be positioned correctly—the arms, legs, and head need to be attached to the body. Within the Scene view, select the Move tool by either clicking on it in the toolbar or pressing T.

    Use the Move tool to rearrange the sprites so that they look like Figure 4-9.

    Additionally, make all of these sprites use the Player tag, just like the parent object. Finally, make sure that the Z position of each of the objects is zero. You can see the Z position in the Position field of each object’s Transform component, at the top of the Inspector.

mgdu 0409
Figure 4-9. The prototype gnome’s sprites
  1. Add Rigidbody 2D components to the body parts. Select all of the body part sprites, and click Add Component in the Inspector. Type Rigidbody in the search field, and add a Rigidbody 2D (see Figure 4-10).

    Warning

    Make sure you add “Rigidbody 2D” components, and not the regular “Rigidbody.” Regular rigidbody components do their simulation in 3D space, which isn’t what you want for this game.

    Additionally, make sure you only add the Rigidbody 2D on the sprites. Don’t add a rigidbody to the parent Prototype Gnome object.

mgdu 0410
Figure 4-10. Adding Rigidbody 2D components to the sprites
  1. Add colliders to the body parts. Colliders define the physical shape of an object. Because they’re visually different shapes, different body parts will need different shaped colliders:

    1. Select the arm and leg sprites, and add BoxCollider2D components.

    2. Select the head sprite, and add a CircleCollider2D component. Leave its radius as is.

    3. Select the Body sprite, and add a CircleCollider2D. Once you’ve added it, go to the Inspector for the collider, and reduce the collider’s radius by about half to fit the Body sprite.

The gnome and its body parts are now all ready to be linked together. The linkages between the body parts will all be done using the HingeJoint2D joint, which allows objects to rotate around a point, relative to one another. The legs, arms, and head will all be linked to the body. To configure the joints, follow these steps:

  1. Select all of the sprites, except for the body. The body won’t need any joint of its own—the other bodies will be connecting to it via their joints.

  2. Add a HingeJoint2D component to all of the selected sprites. This is done by clicking the Add Component button at the bottom of the Inspector, and choosing Physics 2D → Hinge Joint 2D.

  3. Configure the joints. While you still have the sprites selected, we’ll set up a property that will be the same for all of the body parts: they’ll all be connected to the Body sprite.

    Drag the Prototype Body from the Hierarchy pane into the “Connected Rigid Body” slot. This will make the objects linked to the body. When you’re done, the settings for the hinge joints should look like Figure 4-11.

mgdu 0411
Figure 4-11. The initial hinge joint settings
  1. Add limits to the joints. We don’t want the objects to rotate full circles, but instead want to add limits on how far they can rotate. This will prevent odd-looking behavior, like the leg appearing to move through the body.

    Select the arms and the head, and turn on Use Limits. Set the Lower Angle to -15, and the Upper Angle to 15.

    Next, select the legs, and also turn on Use Limits. Set the Lower Angle to -45, and the Upper Angle to 0.

  2. Update the pivot points for the joints. We want the arms to rotate at the shoulder, and the legs to rotate at the hips. By default, the joints will rotate around the center of the object (see Figure 4-12), which will look odd.

    mgdu 0412
    Figure 4-12. The anchor points of the hinge joints start at incorrect positions

    To fix this, you need to update both the position of the joint’s Anchor, as well as its Connected Anchor. The Anchor is the point at which the body that has the joint will rotate, and the Connected Anchor is the point at which the body that the joint is connected to will rotate. In the case of the gnome’s joints, we want the Connected Anchor and the Anchor to both be at the same position.

    When an object that has a hinge joint is selected, both the Anchor and Connected Anchor appear in the scene view: the Connected Anchor is shown as a blue dot, and the Anchor is shown as a blue circle.

    Select each of the body parts that have hinge joints, and move both the Anchor and the Connected Anchor to the correct pivot point. For example, select the right arm, and drag the blue dot to the shoulder location to move the Connected Anchor.

    Moving the Anchor is slightly trickier, since by default it’s in the center, and dragging the center of the object makes Unity move the entire object. To move the Anchor, you first need to manually adjust the location of the Anchor by modifying the numbers in the Inspector—this will change the Anchor’s location in the scene view. Once it’s out of the center, you can drag it to the correct location, just like you do with the Connected Anchor (see Figure 4-13.)

    Repeat this process for both arms (connecting at the shoulder), both legs (connecting at the hip), and the head (connecting at the base of the neck).

Now we’ll add the joint that will connect to the Rope object. This will be a SpringJoint2D attached to the gnome’s right leg, which will allow for free rotation around the joint’s anchor point, and will limit the distance that the body will be allowed to be from the end of the rope. (We’ll create the rope in the next section.) Spring joints work just like springs in the real world: they’re bouncy, and can be stretched a little.

In Unity, they’re controlled by two main properties: distance and frequency. The distance refers to the “preferred” length of the spring: the distance that the spring “wants” to return to after being squashed or stretched. The frequency refers to the amount of “stiffness” the string has. Lower values mean looser springs.

mgdu 0413
Figure 4-13. The anchor points of the left arm, in the correct position; notice how the dot has a ring surrounding it, indicating that both the Connected Anchor and the Anchor are in the same place

To set up springs for use in the Rope, follow these steps:

  1. Add the rope joint. Select the “Prototype Leg Rope.” This should be the top-right leg sprite.

  2. Add the spring joint to it. Add a SpringJoint2D to it. Move its Anchor (the blue circle) so that it’s near the end of the leg. Don’t move the Connected Anchor (that is, move the blue circle, not the blue dot). The anchor positions on the Gnome can be seen in Figure 4-14.

  3. Configure the joint. Turn off Auto Configure Distance, and, set the joint’s Distance to 0.01, and the Frequency to 5.

mgdu 0414
Figure 4-14. Adding the spring joint that will connect the leg to the rope—the joint’s Anchor is near the toe
  1. Run the game. When you do, the gnome will dangle from the middle of the screen.

The last step is to scale the gnome down, so that it will be shown at the right size alongside the other level objects.

  1. Scale the gnome. Select the parent Gnome object, and change its X and Y scale values to 0.5. This will shrink the gnome by half.

The gnome’s now ready to go. It’s time to add the rope!

Rope

The rope is the first piece of the game that actually requires code. It works like this: the rope is a collection of game objects that each have rigid bodies and spring joints. Each spring joint is linked to the next Rope object, and that object is linked to the next, all the way up to the top of the rope. The top Rope object is linked to a rigidbody that’s fixed in place, so that it stays put. The end of the rope will be attached to one of the gnome’s components: the Rope Leg object.

To create the rope, we first need to create an object that will be used as the template for each of the rope segments. We’ll then create an object that uses this segment object along with some code to generate the entire rope. To prepare the Rope Segments, follow these steps:

  1. Create the Rope Segment object. Create a new empty game object, and name it Rope Segment.

  2. Add a body to the object. Add a Rigidbody2D component. Set its Mass to 0.5, so that the rope has a bit of heft to it.

  3. Add the joint. Add a SpringJoint2D component. Set its Damping Ratio to 1, and its Frequency to 30.

Note

Feel free to play with other values as well. We found that these values lead to a decently realistic rope. Game design is all about fiddling with numbers.

  1. Create a prefab using the object. Open the Gnome folder in the Assets pane, and drag the Rope Segment object from the Hierarchy Pane into the Assets pane. This will create a new prefab file in that folder.

  2. Delete the original Rope Prefab object. It won’t be needed anymore—you’re about to write code that creates multiple instances of the Rope Segment, and connects them up into a rope.

We’ll now create the Rope object itself:

  1. Create a new empty game object, and name it “Rope”.

  2. Change Rope’s icon. Because the rope won’t have any visible presence in the scene view when the game’s not running, you’ll want to set an icon for it. Select the newly created Rope object, and click on the cube icon at the top-left of the Inspector (see Figure 4-15).

    Choose the red rounded-rectangle shape, and the Rope object will appear in the scene as a red pill-shaped object (see Figure 4-16).

mgdu 0415
Figure 4-15. Selecting an icon for the Rope object
mgdu 0416
Figure 4-16. With an icon selected, the Rope object appears in the scene
  1. Add a rigidbody. Click the Add Component button, and add a Rigidbody2D component to the object. Once you’ve added this rigidbody, change the Body Type to Kinematic in the Inspector. This will freeze the object in place, and will mean that it doesn’t fall down—which is what we want.

  2. Add a line renderer. Click the Add Component button again, and add a LineRenderer. Set the Width for the new line renderer to 0.075, which will give it a nice, thin, rope-like look. Leave the rest of the line renderer’s settings as the default values.

Now that you’ve set up the rope’s components, it’s time to write the script that controls them.

Coding the Rope

Before we can write the code itself, we need to add a script component. To do so, follow these steps:

  1. Add a Rope script to it. This script doesn’t exist yet, but Unity will create the file for it. Select the Rope object, and click the Add Component button.

    Type Rope; you won’t see any components appear, because Unity doesn’t have any components named Rope. What you will see is a New Script option (see Figure 4-17). Select it.

    mgdu 0417
    Figure 4-17. Creating the Rope.cs file

    Unity will offer to create a new script file. Ensure that the language is set to C Sharp, and that Rope is spelled with a capital R. Click Create and Add. Unity will create the Rope.cs file, and will also attach a Rope script component to the Rope object.

  2. Move Rope.cs to the Scripts folder. By default, Unity puts new scripts in the Assets folder; to keep things tidy, move it into Scripts.

  3. Add the code to the Rope.cs file. Open Rope.cs by double-clicking on it, or by opening the file in the text editor of your choice.

    Add the following code to it (we’ll explain what it does in a moment):

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

  // The connected rope.
  public class Rope : MonoBehaviour {

      // The Rope Segment prefab to use.
      public GameObject ropeSegmentPrefab;

      // Contains a list of Rope Segment objects.
      List<GameObject> ropeSegments = new List<GameObject>();

      // Are we currently extending or retracting the rope?
      public bool isIncreasing { get; set; }
      public bool isDecreasing { get; set; }

      // The rigidbody object that the end of the rope
      // should be attached to.
      public Rigidbody2D connectedObject;

      // The maximum length a rope segment should be (if we
      // need to extend by more than this, create a new rope
      // segment).
      public float maxRopeSegmentLength = 1.0f;

      // How quickly we should pay out new rope.
      public float ropeSpeed = 4.0f;

      // The LineRenderer that renders the actual rope.
      LineRenderer lineRenderer;

      void Start() {

          // Cache the line renderer, so we don't have to look
          // it up every frame.
          lineRenderer = GetComponent<LineRenderer>();

          // Reset the rope, so that we're ready to go.
          ResetLength();

      }

      // Remove all rope segments, and create a new one.
      public void ResetLength() {

          foreach (GameObject segment in ropeSegments) {
              Destroy (segment);

          }

          ropeSegments = new List<GameObject>();

          isDecreasing = false;
          isIncreasing = false;

          CreateRopeSegment();

      }

      // Attaches a new rope segment at the top of the rope.
      void CreateRopeSegment() {

          // Create the new rope segment.
          GameObject segment = (GameObject)Instantiate(
          ropeSegmentPrefab,
          this.transform.position,
          Quaternion.identity);

          // Make the rope segment be a child of this object,
          // and make it keep its world position
          segment.transform.SetParent(this.transform, true);

          // Get the rigidbody from the segment
          Rigidbody2D segmentBody = segment
            .GetComponent<Rigidbody2D>();

          // Get the distance joint from the segment
          SpringJoint2D segmentJoint =
            segment.GetComponent<SpringJoint2D>();

          // Error if the segment prefab doesn't have a
          // rigidbody or spring joint - we need both
          if (segmentBody == null || segmentJoint == null) {
          Debug.LogError("Rope segment body prefab has no " +
            "Rigidbody2D and/or SpringJoint2D!");
              return;
          }

          // Now that it's checked, add it to the start of the
          // list of rope segments
          ropeSegments.Insert(0, segment);

          // If this is the *first* segment, it needs to be
          // connected to the gnome

          if (ropeSegments.Count == 1) {
              // Connect the joint on the connected object to
              // the segment
              SpringJoint2D connectedObjectJoint =
                connectedObject.GetComponent<SpringJoint2D>();

              connectedObjectJoint.connectedBody
                = segmentBody;

              connectedObjectJoint.distance = 0.1f;

              // Set this joint to already be at the max
              // length
              segmentJoint.distance = maxRopeSegmentLength;
          } else {
              // This is an additional rope segment. We now
              // need to connect the previous top segment to
              // this one

              // Get the second segment
              GameObject nextSegment = ropeSegments[1];

              // Get the joint that we need to attach to
              SpringJoint2D nextSegmentJoint =
                nextSegment.GetComponent<SpringJoint2D>();

              // Make this joint connect to us
              nextSegmentJoint.connectedBody = segmentBody;

              // Make this segment start at a distance of 0
              // units away from the previous one - it will
              // be extended.
              segmentJoint.distance = 0.0f;
          }

          // Connect the new segment to the
          // rope anchor (i.e., this object)
          segmentJoint.connectedBody =
            this.GetComponent<Rigidbody2D>();
      }

      // Called when we've shrunk the rope, and
      // we need to remove a segment.
      void RemoveRopeSegment() {

          // If we don't have two or more segments, stop.
          if (ropeSegments.Count < 2) {
              return;
          }

          // Get the top segment, and the segment under it.
          GameObject topSegment = ropeSegments[0];
          GameObject nextSegment = ropeSegments[1];

          // Connect the second segment to the rope's anchor.
          SpringJoint2D nextSegmentJoint =
            nextSegment.GetComponent<SpringJoint2D>();

          nextSegmentJoint.connectedBody =
            this.GetComponent<Rigidbody2D>();

          // Remove the top segment and destroy it.
          ropeSegments.RemoveAt(0);
          Destroy (topSegment);

      }

      // Every frame, increase or decrease
      // the rope's length if necessary
      void Update() {

          // Get the top segment and its joint.
          GameObject topSegment = ropeSegments[0];
          SpringJoint2D topSegmentJoint =
              topSegment.GetComponent<SpringJoint2D>();

          if (isIncreasing) {

              // We're increasing the rope. If it's at max
              // length, add a new segment; otherwise,
              // increase the top rope segment's length.

              if (topSegmentJoint.distance >=
                maxRopeSegmentLength) {
                    CreateRopeSegment();
              } else {
                  topSegmentJoint.distance += ropeSpeed *
                  Time.deltaTime;
              }

          }

          if (isDecreasing) {

              // We're decreasing the rope. If it's near zero
              // length, remove the segment; otherwise,
              // decrease the top segment's length.

              if (topSegmentJoint.distance <= 0.005f) {
                  RemoveRopeSegment();
              } else {
                  topSegmentJoint.distance -= ropeSpeed *
                      Time.deltaTime;
              }

          }

          if (lineRenderer != null) {
              // The line renderer draws lines from a
              // collection of points. These points need to
              // be kept in sync with the positions of the
              // rope segments.

              // The number of line renderer vertices =
              // number of rope segments, plus a point at the
              // top for the rope anchor, plus a point at the
              // bottom for the gnome.
              lineRenderer.positionCount
                = ropeSegments.Count + 2;

              // Top vertex is always at the rope's location.
              lineRenderer.SetPosition(0,
                this.transform.position);

              // For every rope segment we have, make the
              // corresponding line renderer vertex be at its
              // position.
              for (int i = 0; i < ropeSegments.Count; i++) {
                  lineRenderer.SetPosition(i+1,
                        ropeSegments[i].transform.position);
              }

              // Last point is at the connected
              // object's anchor.
              SpringJoint2D connectedObjectJoint =
                connectedObject.GetComponent<SpringJoint2D>();
              lineRenderer.SetPosition(
                ropeSegments.Count + 1,
                connectedObject.transform.
                    TransformPoint(connectedObjectJoint.anchor)
              );
          }
      }
  }

This is a large piece of code, so let’s step through each part of it:

      void Start() {

          // Cache the line renderer, so we don't
          // have to look it up every frame.
          lineRenderer = GetComponent<LineRenderer>();

          // Reset the rope, so that we're ready to go.
          ResetLength();

      }

When the Rope object first appears, its Start method is called. This method calls ResetLength, which will also be called when the gnome dies. Additionally, the lineRenderer variable is set up to point toward the line renderer component attached to the object:

      // Remove all rope segments, and create a new one.
      public void ResetLength() {

          foreach (GameObject segment in ropeSegments) {
              Destroy (segment);
          }

          ropeSegments = new List<GameObject>();

          isDecreasing = false;
          isIncreasing = false;

          CreateRopeSegment();
      }

The ResetLength method deletes all rope segments, resets its internal state by clearing the ropeSegements list and the isDecreasing/isIncreasing properties, and finally calls CreateRopeSegment to create a fresh new rope:

      // Attaches a new rope segment at the top of the rope.
      void CreateRopeSegment() {

          // Create the new rope segment.
          GameObject segment = (GameObject)Instantiate(
              ropeSegmentPrefab,
              this.transform.position,
              Quaternion.identity);

          // Make the rope segment be a child of this object,
          // and make it keep its world position.
          segment.transform.SetParent(this.transform, true);

          // Get the rigidbody from the segment
          Rigidbody2D segmentBody
            = segment.GetComponent<Rigidbody2D>();

          // Get the distance joint from the segment
          SpringJoint2D segmentJoint =
              segment.GetComponent<SpringJoint2D>();

          // Error if the segment prefab doesn't have a
          // rigidbody or spring joint - we need both
          if (segmentBody == null || segmentJoint == null) {
              Debug.LogError(
                "Rope segment body prefab has no " +
                "Rigidbody2D and/or SpringJoint2D!"
                );

              return;
          }

          // Now that it's checked, add it to the start of
          // the list of rope segments
          ropeSegments.Insert(0, segment);

          // If this is the *first* segment, it needs to be
          // connected to the gnome

          if (ropeSegments.Count == 1) {
              // Connect the joint on the connected object to
              // the segment
              SpringJoint2D connectedObjectJoint =
                  connectedObject.GetComponent<SpringJoint2D>();

              connectedObjectJoint.connectedBody =
                segmentBody;
              connectedObjectJoint.distance = 0.1f;

              // Set this joint to already be at the max
              // length
              segmentJoint.distance = maxRopeSegmentLength;
          } else {
              // This is an additional rope segment. We now
              // need to connect the previous top segment
              // to this one

              // Get the second segment
              GameObject nextSegment = ropeSegments[1];

              // Get the joint that we need to attach to
              SpringJoint2D nextSegmentJoint =
                  nextSegment.GetComponent<SpringJoint2D>();

              // Make this joint connect to us
              nextSegmentJoint.connectedBody = segmentBody;

              // Make this segment start at a distance of
              // 0 units away from the previous one - it
              // will be extended.
              segmentJoint.distance = 0.0f;
          }

          // Connect the new segment to the rope
          // anchor (i.e., this object)
          segmentJoint.connectedBody =
              this.GetComponent<Rigidbody2D>();
      }

CreateRopeSegment creates a new copy of the Rope Segment object, and adds it to the top of the rope chain. As part of doing this, it disconnects the current top of the rope (if one exists), and reconnects it to the newly created segment. It then connects the new segment to the Rigidbody2D attached to the Rope object itself.

If this new segment is the only rope segment created so far, it attaches itself to the connectedObject rigidbody. This variable will be set up to be the gnome’s leg:

      // Called when we've shrunk the rope, and
      // we need to remove a segment.
      void RemoveRopeSegment() {

          // If we don't have two or more segments, stop.
          if (ropeSegments.Count < 2) {
              return;
          }

          // Get the top segment, and the segment under it.
          GameObject topSegment = ropeSegments[0];
          GameObject nextSegment = ropeSegments[1];

          // Connect the second segment to the rope's anchor.
          SpringJoint2D nextSegmentJoint =
              nextSegment.GetComponent<SpringJoint2D>();

          nextSegmentJoint.connectedBody =
              this.GetComponent<Rigidbody2D>();

          // Remove the top segment and destroy it.
          ropeSegments.RemoveAt(0);
          Destroy (topSegment);

      }

RemoveRopeSegment works in the opposite way. The top segment is deleted, and the segment underneath it is connected to the Rope rigidbody. Note that RemoveRopeSegment doesn’t do anything if there’s only a single rope segment, which means that the rope will not vanish entirely if retracted all the way:

      // Every frame, increase or decrease
      // the rope's length if necessary
      void Update() {

          // Get the top segment and its joint.
          GameObject topSegment = ropeSegments[0];
          SpringJoint2D topSegmentJoint =
              topSegment.GetComponent<SpringJoint2D>();

          if (isIncreasing) {

              // We're increasing the rope. If it's at max
              // length, add a new segment; otherwise,
              // increase the top rope segment's length.

              if (topSegmentJoint.distance >=
                  maxRopeSegmentLength) {

                  CreateRopeSegment();

              } else {

                  topSegmentJoint.distance += ropeSpeed *
                      Time.deltaTime;

              }

          }

          if (isDecreasing) {

              // We're decreasing the rope. If it's near zero
              // length, remove the segment; otherwise,
              // decrease the top segment's length.

              if (topSegmentJoint.distance <= 0.005f) {
                  RemoveRopeSegment();
              } else {
                  topSegmentJoint.distance -= ropeSpeed *
                      Time.deltaTime;
              }

          }

          if (lineRenderer != null) {
              // The line renderer draws lines from a
              // collection of points. These points need to
              // be kept in sync with the positions of the
              // rope segments.

              // The number of line renderer vertices =
              // number of rope segments, plus a point at the
              // top for the rope anchor, plus a point at the
              // bottom for the gnome.
              lineRenderer.positionCount =
                ropeSegments.Count + 2;

              // Top vertex is always at the rope's location.
              lineRenderer.SetPosition(0,
                this.transform.position);

              // For every rope segment we have, make the
              // corresponding line renderer vertex be at its
              // position.
              for (int i = 0; i < ropeSegments.Count; i++) {
                  lineRenderer.SetPosition(
                      i+1,
                      ropeSegments[i].transform.position
                  );
              }

              // Last point is at the connected
              // object's anchor.
              SpringJoint2D connectedObjectJoint =
                connectedObject.GetComponent<SpringJoint2D>();

              var lastPosition = connectedObject
                  .transform
                  .TransformPoint(
                      connectedObjectJoint.anchor
                  );

              lineRenderer.SetPosition(
                ropeSegments.Count + 1,
                position
              );
          }
      }

Every time the Update method is called (that is, every time the game redraws the screen), the rope checks to see if isIncreasing or isDecreasing is true.

If the check reveals that isIncreasing is true, then the rope gradually increases the distance property of the top rope segment’s spring joint. If this property is greater than or equal to the maxRopeSegment variable, then a new rope segment is created.

Conversely, if isDecreasing is true, the distance property is decreased. If this value is near zero, then the top rope segment is removed.

Finally, the LineRenderer is updated so that the vertices that define the visual position of the line match the location of the rope segment objects.

Configuring the Rope

Now that the Rope’s code has been set up, we can now make the objects in the scene use it. To do so, follow these steps:

  1. Configure the Rope object. Select the Rope game object. Drag the Rope Segment prefab into the rope’s Rope Segment Prefab slot, and drag the gnome’s Rope Leg object into the rope’s Connected Object slot. Leave everything else as the default values, which were defined in the Rope.cs file. When you’re done, the Rope’s inspector should look like Figure 4-18.

  2. Run the game. The gnome will now be dangling from the Rope object, and you’ll see the line connecting the gnome to a point slightly above it.

The configured Rope object
Figure 4-18. The configured Rope object

There’s one step left for the Rope—we need to set up a material for the Line Renderer to use:

  1. Create the material. Open the Assets menu, and choose Create → Material. Name the new material Rope.

  2. Set up the Rope material. Select the new Rope material, and open the Shader menu in the Inspector. Choose Unlit → Color.

    The inspector will change to show the parameters for the new shader, which will be a single color slot. Change this color to be a dark brown by clicking on the color and picking a new one from the pop-up window.

  3. Make the Rope use the new material. Select the Rope object, and open the Materials property. Drag and drop the Rope material you just created into the Element 0 slot.

  4. Run the game again. The rope will now be brown.

Wrapping Up

At this point, the bare-bones structure of the game is starting to take shape. We’ve got the two most important parts of the game functioning: a ragdoll gnome, and the rope from which it is suspended.

In the next chapter, we’ll start creating the systems that implement gameplay using these objects. It’s going to be great.

Get Mobile Game Development with Unity now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.