teaching machines

Honors 104.502 Lab 5 – Robot Platformer

March 11, 2016 by . Filed under honors gamedev, labs, spring 2016.

Today we make a little platformer. We’ll investigating using spritesheets, animations, and physics as we make a robot move around to collect its missing washers.

Checkpoint 1

Person A types. Create a new project.

Robot

Let’s start by getting our robot in the game, much as we did in the last lecture. First, add an empty game object and name it Robot. Give it a SpriteRenderer.

Download this asset package containing just the robot spritesheets, and import it by clicking Assets / Import Package / Custom Package.

Expand the assets to find the robot’s idle spritesheet. Drag the first frame into the Robot’s SpriteRenderer so we can see it in the scene view.

Playtest. Does the robot appear?

Idle Animation

Let’s make the robot idle more naturally. Click on all the frames of the idle animation and drag them onto the robot object. This does a couple of things automatically for us: it creates an animator controller in the assets (which is used to drive the animation state machine), it creates an animator component on the robot object (which is used to link the object to the animator controller’s state machine), and it creates an idle animation (which is one of the states of the animation state machine). Since it is the first animation we’ve added, we will automatically transition into it when the game starts.

Playtest. Does the robot idle?

Platform

Add a cube and scale it to make a platform beneath the robot. Don’t worry about its color for now.

Add a Rigidbody2D component to the robot.

Playtest. The robot should fall through the platform. Do you recall what we did to fix this? First, remove the 3D box collider from the cube and add a 2D one. Second, add a 2D box collider to the robot. Does the platform catch the robot now?

Movement

Let’s now make the robot move. Add a RobotController script to the robot object. Give it a private Rigidbody2D variable:

private new Rigidbody2D rigidbody;

I added the keyword new here because without it we get a warning that another rigidbody has been declared elsewhere and that, by introducing a second one, we are clobbering the original. By saying new, we sign off on that clobbering.

We need to query for the robot object’s rigidbody component. Start is a reasonable place to do this:

rigidbody = GetComponent<Rigidbody2D>();

Now add a FixedUpdate function. Except in name, this function looks identical to Update, but it’s where we do (most) physics manipulation. Add a public float named maximumSpeed and assign it a non-zero value in the Inspector.

float oomph = Input.GetAxis("Horizontal");
rigidbody.velocity = new Vector2(oomph * maximumSpeed, rigidbody.velocity.y);

Playtest. Does the robot fall over? How did we fix that? Freeze the rotation in the robot’s rigidbody constraints. We don’t want feet friction to send our robot spinning when we move.

Running

Now let’s make the robot run. Select all the frames of the robot’s run spritesheet and drag them onto the robot game object. This action will add a second animation to the animator state machine.

To switch to this animation when the robot starts moving, we add a parameter to the state machine, which will drive a transition from one state to another. Click Window / Animator. Click on Parameters in the left panel and click on the + to add a new float. Name it Speed.

Add a transition by right-clicking on the idle state and selecting Make Transition. Drag the transition to the run state. Select the transition and in the Inspector uncheck Has Exit Time. (If this is checked, we will transition out of idle automatically when a timer expires. We only want to transition when the Speed parameter changes.)

In the Conditions, click +. Transition when Speed exceeds 0.

Now, back in RobotController, we must inform the Animator when the robot’s speed actually does exceed 0. First add a private Animator variable:

private Animator animator;

As you did with the rigidbody, use GetComponent to assign this variable a value in Start. Then, in FixedUpdate, set the parameter according the horizontal speed—ignoring the direction:

animator.SetFloat("Speed", Mathf.Abs(oomph));

Playtest. Does the robot run? Does it stop running? Probably not.

In the Animator window, right-click on the run state and make a transition back to idle. Select the transition, uncheck Has Exit Time, and add a condition to transition when speed drops below some low threshold.

Flipping Directions

Try running left. The robot moves backwards. Let’s fix that. When we hit A or the left cursor key, we want to give the robot a “face left.” Instead of creating a second set of assets that face left, we’ll just apply a negative horizontal scale. First, we need to add some state to our robot so that we can detect a flip of direction. Add a private bool variable named isFacingRight. In Start, set it to true because that’s the direction the robot is born facing.

Second, add a Flip function to invert the scale applied to the robot via its transform component:

void Flip() {
  isFacingRight = !isFacingRight;
    
  Vector3 scale = transform.localScale;
  scale.x = -scale.x;
  transform.localScale = scale;
}

Third, we need to add some logic in FixedUpdate to call this function. Here’s our logic in pseudocode:

if (oomph points left and robot's facing right) or
   (oomph points right and robot's facing left)
  flip robot

Translate this into C#.

Playtest. Does the robot flip directions?

Checkpoint 2

Person B types.

Camera

When the robot moves, we want the camera to follow it. Unity’s hierarchical organization makes supporting this straightforward. Drag the Main Camera object onto the robot. Set camera’s position to (0, 0, 0) so that it’s centered within its parent robot. Now, when the robot moves, since the camera is a child of the robot, the camera will stay locked on to it.

Textured Platforms

Let’s make the platform a little more exciting. Import this image, shared by user Sogomn on opengameart.org:

grass_tile

Unity classifies images in a few ways. In 2D, the default classification is sprite, which more or less means that the image itself represents an entire game object. In our case, our platform already is a complete object thanks to the 3D cube model. We want to apply our image to that existing model. We can’t apply sprites in this way. However, we can apply it if we designate the image as a texture. Select the grass tile image and change its texture type to Texture.

Create a new material for this texture in the project assets. Select the material and set the shader to Unlit / Texture. Drag the grass tile texture into the texture slot in the Inspector. To apply the material to the platform, drag it onto the platform game object.

How does it look? If you’ve scaled the platform, it probably doesn’t look good. This is what I see:

Screen Shot 2016-03-11 at 7.30.43 AM

It’s stretched and upside down. To fix the stretching, we need to tile the image across the platform. We are currently trying to apply the square texture across the entire rectangular platform. Click on the material and set the Tiling X parameter to the ratio between the platform’s X and Y scale factors. After this change, I now see:

Screen Shot 2016-03-11 at 7.34.58 AM

The right side looks good. But the rest is smeared. Click on the grass tile texture. Set its Wrap Mode to Repeat instead of Clamp. Now I see this:

Screen Shot 2016-03-11 at 7.36.18 AM

Almost there. Select the material again and the Tiling Y parameter to -1. How’s it look now?

Jumping

Let’s make the robot jump. We’re going to go the minimalist approach for this. Our minimalism will allow for double-, triple-, even infinite-jumping.

In Update, add a check for the Jump button (spacebar by default) being pressed and add some force:

if (Input.GetButtonDown("Jump")) {
  rigidbody.AddForce(new Vector2(?, ?));
}

What values lead to reasonable jumping?

Elevator

Let’s give the robot a reason to jump. We don’t want to waste a mechanic!

Duplicate your platform so that you have at least three platforms, arranged side to side with a slight gap between them. We’ll make the middle one an elevator that goes up and down.

In the assets, create a new Animator Controller and name it Elevator Animator Controller. Drag it onto the middle platform.

Now select the elevator object and click on Window / Animation to open the Animation window. Click Create to create a new animation named Lift. At time 0, move the elevator to a low position. A keyframe should be automatically added. At a later time, move the elevator to a high position. Check that a keyframe was automatically added.

At a still later time, move the elevator back to its original location. If you don’t get this exact, the animation will hiccup when it loops back around to the beginning. Good news! We can get exactness pretty simply by copying and pasting keyframes. Select the top rhombus at the first keyframe and copy it with Control/Command-C. Click on the timeline where you want the final keyframe to appear. Hit Control/Command-V to paste in the copied keyframe.

Playtest. Does the elevator move up and down?

Washers

Platformers tend to have collectibles. Coins seem kind of silly for a robot (for a human too, now that I think about it), so let’s have it collect washers instead. Download and import this spritesheet that I made using Blender and ImageMagick:

washer

Unlike the robot spritesheets, this one hasn’t already been sliced into frames. Let’s fix that. Click on the image in the assets and change Sprite Mode to Multiple. Click on the Sprite Editor button.

Click Slice and switch Type to Grid by Cell Size. Each frame is 128×128. Use these dimensions to set the Pixel Size values but leave the rest of the values at their defaults. Click Apply.

You should see that the sheet has been sliced up in the assets. Select all frames and drag them into the scene. Unity will automatically create a game object with a sprite renderer and an animator playing through this spritesheet.

Playtest. Do you see a spinning washer?