How to Make a Game Like Jetpack Joyride in Unity 2D – Part 2

In the second part of a three part series, you will be generating a series of endless rooms, allowing the user to fly through them. By Mark Placzek.

Leave a rating/review
Download materials
Save for later

This is the second part of the tutorial on how to create a game like Jetpack Joyride in Unity 2D. If you’ve missed the first part, you can find it here.

In the first part of this tutorial series, you created a game with a mouse flying up and down in a room. Oh, and don’t forget the flames shooting from his jetpack! Although the fire is fun to look at, simply adding jetpack flames doesn’t make a good game.

In this part of the tutorial series, you’re going to move the mouse forward through randomly generated rooms to simulate an endless level. You’ll also add a fun animation to make the mouse run when it is grounded.

Getting Started

If you completed the first part of this tutorial series, you can continue working with your own project. Alternatively, you can download the RocketMouse Part 1 Final from the materials at the top or bottom of this tutorial. Unpack that and open the RocketMouse.unity scene contained within.

Making the Mouse Fly Forward

It’s time to move forward — literally! To make the mouse fly forward you will need to do two things:

  • Make the mouse move.
  • Make the camera follow the mouse.

Adding a bit of code will solve both tasks.

Setting the Mouse Velocity

Open the MouseController script and add the following public variable:

public float forwardMovementSpeed = 3.0f;

This will define how fast the mouse moves forward.

Note: By making this a public variable, you’re giving yourself an opportunity to adjust the speed from Unity in and out of play mode without opening the script itself.

Next, add the following code to the end of FixedUpdate:

Vector2 newVelocity = playerRigidbody.velocity;
newVelocity.x = forwardMovementSpeed;
playerRigidbody.velocity = newVelocity;

This code simply sets the velocity x-component without making any changes to the y-component. It is important to only update the x-component, since the y-component is controlled by the jetpack force.

Run the scene! The mouse moves forward, but at some point, the mouse just leaves the screen.

To fix this, you need to make the camera follow the mouse.

Making the Camera Follow the Player

In the Project view, navigate to RW/Scripts and create a new C# Script named CameraFollow. Drag it onto the Main Camera in the Hierarchy to add it as a component.

Open this CameraFollow script and add the following public variable:

public GameObject targetObject;

You will assign the mouse GameObject to this variable in a moment, so that the camera knows which object to follow.

Add the following code to the Update method:

float targetObjectX = targetObject.transform.position.x;
Vector3 newCameraPosition = transform.position;
newCameraPosition.x = targetObjectX;
transform.position = newCameraPosition;

This code simply takes the x-coordinate of the target object and moves the camera to that position.

Note: You only change the x-coordinate of the camera, since you don’t want it to move up or down, following the mouse.

Switch back to Unity and select Main Camera in the Hierarchy. There is a new property in the CameraFollow component called Target Object. You will notice that it is not set to anything.

To set the Target Object, click on mouse in the Hierarchy and, without releasing, drag the mouse to the Target Object field in the Inspector as shown below:

Note: It is important to not release the mouse button, since if you click on the mouse and release the mouse button you will select the mouse character and the Inspector will show the mouse properties instead of Main Camera.
Alternatively, you can lock the Inspector to the Main Camera by clicking the lock button in the Inspector.

Run the scene. This time the camera follows the mouse.

This is a good news / bad news kind of thing – the good news is that the camera follows the mouse, but the bad news is that, well, nothing else does!

You’ll address this in a moment, but first you will need to offset the mouse to the left side of the screen. Why? Unless the player has the reflexes of a cat, you will want to give them a little more time to react and avoid obstacles, collect coins, and generally have fun playing the game.

Adjusting the Camera Offset

Select the mouse in the Hierarchy and set its Position to (-3.5, 0, 0) and run the scene.

Wait — the mouse is still centered on the screen, but this has nothing to do with the mouse position. This happens because the camera script centers the camera at the target object. This is also why you see the blue background on the left, which you didn’t see before.

To fix this, open the CameraFollow script and add a distanceToTarget private variable:

private float distanceToTarget;

Then add the following code to the Start method:

distanceToTarget = transform.position.x - targetObject.transform.position.x;

This will calculate the initial distance between the camera and the target. Finally, modify the code in the Update method to take this distance into account:

float targetObjectX = targetObject.transform.position.x;
Vector3 newCameraPosition = transform.position;
newCameraPosition.x = targetObjectX + distanceToTarget;
transform.position = newCameraPosition;

The camera script will now keep the initial distance between the target object and the actual camera. It will also maintain this gap throughout the entire game.

Run the scene, and the mouse will remain offset to the left.

Generating an Endless Level

Right now playing the game for more than a few seconds isn’t much fun. The mouse simply flies out of the room into a blue space. You could write a script that adds backgrounds, places the floor and the ceiling and finally adds some decorations. However, it is much easier to save the complete room as a Prefab and then instantiate the whole room at once.

Note: In a game like Jetpack Joyride, you’ll often see different areas (aquarium, caves, etc.) that are each their own different Prefab. For the purposes of this game, you’ll stick with one.

Here is an excerpt from the Unity documentation regarding Prefabs:

A Prefab is a type of asset — a reusable GameObject stored in the Project View. Prefabs can be inserted into any number of scenes, multiple times per scene. When you add a Prefab to a scene, you create an instance of it. All Prefab instances are linked to the original Prefab and are essentially clones of it. No matter how many instances exist in your project, when you make any changes to the Prefab you will see the change applied to all instances.

In other words, you add objects to your scene, set their properties, add components like scripts, colliders, rigidbodies and so on. Then you save your object as a Prefab and you can instantiate it as many times as you like with all the properties and components in place.

Creating a Room Prefab

You’ll want your Prefab to contain all the different room elements: the book case, the window, the ceiling, etc. To include all these elements as part of the same Prefab, you’ll first need to add them to a parent object.

To do this, create an Empty GameObject by choosing GameObject ▸ Create Empty. Then select this new GameObject in the Hierarchy, and make the following changes in the Inspector:

  • Rename it to room1.
  • Set its Position to (0, 0, 0).

This is what you should see in the Inspector:

Note: It is important to understand that room1 is placed right in the center of the scene and at the (0, 0, 0) point. This is not a coincidence.

When you add all the room parts into room1 to group them, their positions will become relative to the room1 GameObject. Later when you will want to move the whole room, it will be much easier to position it knowing that setting the position of room1 will move the room’s center to this point.

In other words, when you add objects to room1, its current position becomes the pivot point. So it is much easier if the pivot point is at the center of the group rather then somewhere else.

Move all the room parts (bg, bg (1), bg_window, ceiling, floor, object_bookcase_short1, object_mousehole) into room1, just as you did when you added the jetpack flame particle system to the mouse object.

Note: If you decorated your room with more bookcases or mouse holes, you should also add them to room1.

Create a new folder named Prefabs in the RW directory in the Project browser. Open it and drag room1 from the Hierarchy directly into the Prefabs folder.

That’s it. Now you can see a Prefab named room1 containing all the room parts. To test it, try to drag the room1 Prefab into the scene. You will see how easy it is to create room duplicates using a Prefab.

Note: You can reuse this Prefab not only in this scene, but in other scenes too!

The Idea Behind the Room Generation

The idea behind the generator script is quite simple. The script has an array of rooms it can generate, a list of rooms currently generated, and two additional methods. One method checks to see if another room needs to be added, and the other method actually adds a room.

To check if a room needs to be added, the script will enumerate all existing rooms and see if there is a room ahead, farther then the screen width, to guarantee that the player never sees the end of the level.

  1. The screen width measurement indicates the last room of the level is still well ahead of the player, and certainly not on the screen. Therefore you don’t need to add a room yet.
  2. As the game proceeds, the final room reaches the screen width measure.
  3. A new room is added from the array of available rooms. The screen width measure ensures this all happens off screen so the player never sees the end of the level.

Adding a Script to Generate Rooms

Create a new C# Script in the RW/Scripts folder and name it GeneratorScript. Add this script to the mouse GameObject. Now the mouse should have two script components:

Open this new GeneratorScript by double clicking it in the Project view or in the Inspector.

Then add the following variables:

public GameObject[] availableRooms;
public List<GameObject> currentRooms;
private float screenWidthInPoints;

The availableRooms field will contain an array of Prefabs, which the script can generate. Currently you have only one Prefab (room1). But you can create many different room types and add them all to this array, so that the script could randomly choose which room type to generate.

Note: The final project that you can download at the end of Part 3 contains multiple room types as well as other improvements, but right now it is easier to work with only one room Prefab.

The currentRooms list will store instanced rooms, so that it can check where the last room ends and if it needs to add more rooms. Once the room is behind the player character, it will remove it as well.
The screenWidthInPoints field is just required to cache the screen size in points.

Now, add the following code to the Start method:

float height = 2.0f * Camera.main.orthographicSize;
screenWidthInPoints = height * Camera.main.aspect;

Here you calculate the size of the screen in points. The screen size will be a used to help determine if you need to generate a new room, as described above.

The Method to Add a New Room

Add the following AddRoom method to your GeneratorScript:

void AddRoom(float farthestRoomEndX)
    int randomRoomIndex = Random.Range(0, availableRooms.Length);
    GameObject room = (GameObject)Instantiate(availableRooms[randomRoomIndex]);
    float roomWidth = room.transform.Find("floor").localScale.x;
    float roomCenter = farthestRoomEndX + roomWidth * 0.5f;
    room.transform.position = new Vector3(roomCenter, 0, 0);

This method adds a new room using the farthestRoomEndX point, which is the rightmost point of the level so far. Here is a description of every line of this method:

  1. Picks a random index of the room type (Prefab) to generate.
  2. Creates a room object from the array of available rooms using the random index chosen above.
  3. Since the room is just an Empty GameObject containing all the room parts, you cannot simply take its size. Instead, you get the size of the floor inside the room, which is equal to the room’s width.
  4. In order to set the new room to its correct location, you need to calculate where its center should be. Take the furthest edge of the level so far, and add half of the new room’s width. By doing this, the new room will start exactly where the previous room ended.
  5. This sets the position of the room. You need to change only the x-coordinate since all rooms have the same y and z coordinates equal to zero.
  6. Finally, you add the room to the list of current rooms. It will be cleared in the next method, which is why you need to maintain this list.

Now take a short break; the next method is going to be a bit bigger!

Checking if a New Room is Required

Ready for some more code? Add the following GenerateRoomIfRequired method:

private void GenerateRoomIfRequired()
    List<GameObject> roomsToRemove = new List<GameObject>();
    bool addRooms = true;
    float playerX = transform.position.x;
    float removeRoomX = playerX - screenWidthInPoints;
    float addRoomX = playerX + screenWidthInPoints;
    float farthestRoomEndX = 0;
    foreach (var room in currentRooms)
        float roomWidth = room.transform.Find("floor").localScale.x;
        float roomStartX = room.transform.position.x - (roomWidth * 0.5f);
        float roomEndX = roomStartX + roomWidth;
        if (roomStartX > addRoomX)
            addRooms = false;
        if (roomEndX < removeRoomX)
        farthestRoomEndX = Mathf.Max(farthestRoomEndX, roomEndX);
    foreach (var room in roomsToRemove)
    if (addRooms)

It only looks scary, but in fact it is quite simple. Especially if you keep in mind the ideas previously described:

  1. Creates a new list to store rooms that need to be removed. A separate list is required since you cannot remove items from the list while you are iterating through it.
  2. This is a flag that shows if you need to add more rooms. By default it is set to true, but most of the time it will be set to false inside the first foreach loop.
  3. Saves player position. (You'll mostly only use the x-coordinate when working with the mouse's position though).
  4. This is the point after which the room should be removed. If room position is behind this point (to the left), it needs to be removed. You need to remove rooms, since you cannot endlessly generate rooms without removing them after they are not needed. Otherwise you will simply run out of memory.
  5. If there is no room after the addRoomX point, then you need to add a room, since the end of the level is closer than the screen width.
  6. In farthestRoomEndX, you store the point where the level currently ends. You will use this variable to add a new room if required, since a new room should start at that point to make the level seamless.
  7. In the foreach loop you simply enumerate currentRooms. You use the floor to get the room width and calculate the roomStartX (the point where the room starts, i.e. the leftmost point of the room) and roomEndX (the point where the room ends, i.e. the rightmost point of the room).
  8. If there is a room that starts after addRoomX then you don’t need to add rooms right now. However there is no break instruction here, since you still need to check if this room needs to be removed.
  9. If the room ends to the left of the removeRoomX point, then it is already off the screen and needs to be removed.
  10. Here you simply find the rightmost point of the level. This is the point where the level currently ends. It is used only if you need to add a room.
  11. This removes rooms that are marked for removal. The mouse GameObject already flew through them and thus they need to be removed.
  12. If at this point addRooms is still true then the level end is near. addRooms will be true if it didn’t find a room starting farther than the screen's width. This indicates that a new room needs to be added.

Phew, that was a lot of code — but you’ve made it!

You will need to periodically execute GenerateRoomIfRequired. One way to accomplish this is with a Coroutine.

Add the following to the GeneratorScript:

private IEnumerator GeneratorCheck()
    while (true)
        yield return new WaitForSeconds(0.25f);

The while loop will ensure any code will continue to be executed whilst the game is running and the GameObject is active. Operations involving List<> can be performance limiting; therefore, a yield statement is used to add a 0.25 second pause in execution between each iteration of the loop. GenerateRoomIfRequired is only executed as often as it is required.

To kick off the Coroutine, add the following code to the end of the Start method in GeneratorScript:


Setting the Script Options and Enjoying

Return to Unity and select the mouse GameObject in the Hierarchy. In the Inspector, find the GeneratorScript component.

Drag room1 from the Hierarchy to the Current Rooms list. Then open the Prefabs folder in the Project view and drag room1 from it to Available Rooms.

As a reminder, the availableRooms property in the GeneratorScript is used as an array of room types that the script can generate. The currentRooms property is room instances that are currently added to the scene.

This means that availableRooms or currentRooms can contain unique room types that are not present in the other list.

Here is an animated GIF demonstrating the process. Note that I’ve created one more room type called room2, just to demonstrate what you would do in case you had many room Prefabs:

Run the scene. Now the mouse can endlessly fly through the level.

Note that rooms are appearing and disappearing in the Hierarchy while you fly. For even more fun, run the scene and switch to the Scene view without stopping the game. Select the mouse in the Hierarchy and press Shift-F to lock the scene camera to the mouse. Now zoom out a little (Use the scroll wheel, or hold Alt+right-click, then drag). This lets you see how rooms are added and removed in real time.

Animating the Mouse

Right now the mouse is very lazy. It doesn’t want to move a muscle and simply lets the jetpack drag it on the floor. However, the price of jetpack fuel is quite expensive, so it is better for the mouse to run while on the ground.

To make the mouse run, you’re going to create an animation and modify the MouseController script to switch between animations while on the ground or in the air.

Creating Animations

Click the disclosure triangle beside the mouse_sprite_sheet to display all of the mouse animation frames.

Note: The flying animation consists of one sprite - the sprite that you used to create the mouse - so you already had all the frames for this animation.

To work with animations, you will need to open the Animation window, if you don’t have it opened already. Choose Window ▸ Animation to open the Animation view.

Place it somewhere so that you can see both the Animation view and the Project view. I prefer placing it on top, next to the Scene and the Game views, but you can place it anywhere you like.

Before you create your first animation, create an Animations folder in the RW directory in the Project view and make sure it is selected. Don't forget that most of new files in Unity are created in the folder that is currently selected in the Project view.

Next, select the mouse GameObject in the Hierarchy, since new animations will be added to the most recently selected object in the Hierarchy.

In the Animation window, you will be prompted to create an Animator and an Animation Clip to begin. Click Create and name the first animation run. Create a second clip called fly by selecting [Create New Clip] in the dropdown menu at the top left corner, to the left of the Samples property.

Note the three new files created in the Project view. In addition to the two fly and run animations, there is also a mouse animator file. Select the mouse in the Hierarchy. In the Inspector, you will see that that an Animator component was automatically added to it.

Adding Run Animation Frames

First, you’re going to add frames to the run animation. Make sure both the Animation view and the Project view are visible. In the Animation view, select the run animation.

In the Project view, open the Sprites folder and expand the mouse_sprite_sheet.

Select all the run animation frames: mouse_run_0, mouse_run_1, mouse_run_2, mouse_run_3. Drag the frames to the Animation view's timeline as shown below:

Here is how the timeline should look like after you have added the frames.

Adding the Fly Animation Frame

Believe it or not, the fly animation consists of only one frame. Select the fly animation in the Animation view.

In the Project view find the mouse_fly sprite and drag it to the timeline, just as you did with the run animation. But this time you only need to add one sprite.

Why would someone want to create an animation with only one frame? This makes it much easier to switch between the running and flying mouse states using the Animator transitions. You’ll see this in a moment.

Adjusting the Animator and Run Animation Settings

Run the scene. You will notice something is not quite right: Your poor mouse is stuck in a perpetual insane sprint! Sadly, the GIF can't fully represent the insanity here.

Since the run animation was added first, the Animator component set it as the default animation. Therefore the animation starts playing as soon as the scene runs. To fix the animation speed, select the run animation in the Animation view and set the Samples property to 8 instead of 60.

Select the mouse GameObject in the Hierarchy and find the Animator component in the Inspector. Select the Update Mode dropdown box and change Normal to Animate Physics.

As the game is using physics, it's a good idea to keep animations in sync with physics.

Run the scene. Now the mouse should be walking at a sensible rate.

However, the mouse continues to walk even while it is in the air. To fix this, you need to create some animation transitions.

Creating Animation Transitions

To use the Animator Transitions mechanism, you’re going to need one more Unity window. In the top menu, choose Window ▸ Animator to add the Animator view, and ensure you have the mouse GameObject selected in the Hierarchy. Currently you have two animations there: run and fly. The run animation is orange, which means that it is the default animation.

However, there is no transition between the run and fly animations. This means that the mouse is stuck forever in the run animation state. To fix this you need to add two transitions: one from run to fly, and another back from fly to run.

To add a transition from run to fly, right-click the run animation and select Make Transition, then hover over the fly animation and left-click on it.

Similarly, to add a transition from fly to run, right-click the fly animation. Select Make Transition, and this time hover over the run animation and left-click.

Here is the process of creating both transitions:

This has created two unconditional transitions, which means that when you run the scene the mouse will first play its run state, but after playing the run animation one time, the mouse will switch to the fly state. Once the fly state is completed, it will transition back to run and so forth.

Switch to the Animator while the scene is running. You will see that there is a constant process of transitioning between the animations:

Adding a Transition Parameter

To break this vicious circle, you need to add a condition that controls when the fly animation should transition to the run animation and vice versa.

Open the Animator view and find the Parameters panel in the top left corner, which is currently empty. Click the + button to add a parameter, and in the dropdown select Bool.

Name the new parameter isGrounded.

Select the transition from run to fly to open transition properties in the Inspector. In the Conditions section, click the plus to add isGrounded and set its value to false.

While you are here, you’ll prevent against any lag or transition between the animation states. Uncheck Has Exit Time and click the disclosure arrow to expand the Settings for the transition. Set the Transition Duration to 0.

Do the same with the transition from fly to run, but this time set the isGrounded value to true.

This way the mouse state will change to fly when isGrounded is false, and to run when isGrounded is true.

While you still have yet to pass in the parameters, you can test the transitions right now. Run the scene, then make sure the Animator view is visible and check/uncheck the isGrounded while the game is running.

Checking if the Mouse is Grounded

There are many ways to check if the game object is grounded. The following method is great because it provides visual representation of the point where the ground is checked, and it can be quite useful when you have many different checks, such as ground check, ceiling check, or others.

What gives this method visual representation is an Empty GameObject added as a child of the player character, as shown below.

Create an Empty GameObject, then drag it over the mouse GameObject in the Hierarchy to add it as a child object. Select this GameObject in the Hierarchy and rename it to groundCheck. Set its Position to (0, -0.7, 0).

To make it visible in the scene, click on the icon selection button in the Inspector and set its icon to the green oval. You can really choose any color, but green is truly the best.

Here is what you should get in the end:

The MouseController script will use the position of this Empty GameObject to check if it is on the ground.

Using Layers to Define What is Ground

Before you can check that the mouse is on the ground, you need to define what is ground. If you don’t do this, the mouse will walk on top of lasers, coins and other game objects with colliders.

You’re going to use the LayerMask class in the script, but to use it, you first must set the correct Layer to the floor object.

Open the Prefabs folder in the Project view and expand the room1 Prefab. Select the floor inside the Prefab.

In the Inspector, click on the Layer dropdown and choose the Add Layer... option.

This will open the Tags & Layers editor in the Inspector. Find the first editable element, User Layer 8, and enter Ground in it. All previous layers are reserved by Unity.

Next, select the floor within the room1 Prefab once again and set its Layer to Ground.

Checking if the Mouse is Grounded

To make the mouse automatically switch states, you will have to update the MouseController script to check if the mouse is currently grounded, then let the Animator know about it.

Open the MouseController script and add the following instance variables:

public Transform groundCheckTransform;
private bool isGrounded;
public LayerMask groundCheckLayerMask;
private Animator mouseAnimator;

The groundCheckTransform variable will store a reference to the groundCheck Empty GameObject that you created earlier. The isGrounded variable denotes if the mouse is grounded, while the groundCheckLayerMask stores a LayerMask that defines what is the ground. Finally, the mouseAnimator variable contains a reference to the Animator component.

Note: It is better to cache components you get by GetComponent in an instance variable, since GetComponent is slower.

To cache the Animator component add the following line of code to Start:

mouseAnimator = GetComponent<Animator>();

Now add UpdateGroundedStatus:

void UpdateGroundedStatus()
    isGrounded = Physics2D.OverlapCircle(groundCheckTransform.position, 0.1f, groundCheckLayerMask);
    mouseAnimator.SetBool("isGrounded", isGrounded);

This method checks if the mouse is grounded and sets the Animator parameter as follows:

  1. To check if the mouse GameObject is grounded, you create a circle of radius 0.1 at the position of the groundCheck object you added to the scene. If this circle overlaps any object that has a Layer specified in groundCheckLayerMask then the mouse is grounded.
  2. This code actually sets the isGrounded parameter of the Animator which then triggers the animation.

Finally, add a call to UpdateGroundedStatus at the end of the FixedUpdate method:


This calls the method with each fixed update, ensuring that the ground status is consistently checked.

Setting the MouseController Script Parameters for Ground Check

There is only one small step left to make the mouse automatically switch between flying and running. Open Unity and select the mouse GameObject in the Hierarchy.

Search for the Mouse Controller script component. You will see two new parameters exposed in the Inspector:

Click the Ground Check Layer Mask dropdown and select the Ground layer. Drag the groundCheck from the Hierarchy to the Ground Check Transform property.

Run the scene.

Enabling and Disabling the Jetpack Flames

Although you cured the mouse of laziness, you haven’t cured its wastefulness. The jetpack is still firing, even when the mouse is on the ground. Think of the carbon emissions, people!

Fortunately, you only need to add a few tweaks in the code to fix this.

Open the MouseController script and add the following jetpack variable to store a reference to the particle system.

public ParticleSystem jetpack;

Then add the following AdjustJetpack method:

void AdjustJetpack(bool jetpackActive)
    var jetpackEmission = jetpack.emission;
    jetpackEmission.enabled = !isGrounded;
    if (jetpackActive)
        jetpackEmission.rateOverTime = 300.0f;
        jetpackEmission.rateOverTime = 75.0f;

This method disables the jetpack’s emission when the mouse is grounded. It also decreases the emission rate when the mouse is falling down, since the jetpack might still be active, but not at full strength.

Add a call to this method to the end of FixedUpdate:


As a reminder, the jetpackActive variable is true when you the left mouse button is depressed and false when released.

Now switch back to Unity and drag the mouse's jetpackFlames from the Hierarchy to the Jetpack property of the MouseController component.

Run the scene.

Now the jetpack has three different states: It’s disabled when the mouse is grounded, full strength when going up, and runs at a decreased emission rate when the mouse is going down. Things are looking pretty good!

Where to Go From Here?

Enjoying the tutorial series so far? You can download the final project for this part using the materials link at the top or bottom of this tutorial.

The final part of this series adds all the “fun” stuff: lasers, coins, sound effects, and much more!

If you want to know more about Prefabs, the Unity documentation is a good place to start.

If you have any comments, questions or issues, please post them below!