Check out part 1 for the project and world setup!
Check out part 2 for implementing controls for our runner!
This is part 3 of a tutorial on writing a 2d running game. Remember the code is on GitHub. Also, a final version based on this tutorial is on Google Play.
Watch Out for the Enemies!
In this section we’ll introduce our enemies, a bunch of running/flying insects. They will all be coming towards our runner in an attempt to hit/stop him. We will have 6 types of enemies: 4 of them will be running so we’ll have to jump over them and the other 2 will be flying, so we’ll have to dodge them. This is the configuration (width x height in meters) I came up with for all enemies:
Running small - 1 x 1
Running wide - 2 x 1
Running long - 1 x 2
Running big - 2 x 2
Flying small - 1 x 1
Flying wide - 2 x 1
The logic for our enemies will be the following: we’ll place an enemy just outside of the right side of the screen and move him towards the left end of the screen. If the runner successfully avoids getting hit by the enemy, we’ll create a new one until one of them hits the runner. The type of enemy to create will be randomly determined.
Since we want to generate different types of enemies, we’ll store their configuration as an enum. In our enums package, create an enum called EnemyType with the following:
The new Constants added:
To obtain a random value from an enum, I created a RandomUtils class inside our utils package that contains a private static RandomEnum class that returns a random value from a given enum (credits go to this SO question):
Since we want to be consistent with our project setup, we have to create an EnemyUserData which extends from our UserData as well as an Enemy which extends from our GameActor. Let’s add an ENEMY value to our UserDataType enum:
Hey! What was that ENEMY_LINEAR_VELOCITY we added to our Constants? Well, remember we are using our custom UserData to store any info regarding our characters’ movement. Our enemies will always run with a constant linear velocity to the left (negative x-direction) and a EnemyUserData class in our box2d package will be the perfect place to store it.
Before creating or EnemyUserData class, I’d like to change something in our UserData class: when we check if a character is in bounds (we’ll add that in a little bit), I need the character’s width in order to calculate it. I want to store this info whenever we create a new UserData object:
Let’s match this new constructor in our RunnerUserData:
And now we can create our EnemyUserData:
Let’s now add our Enemy class that extends from our GameActor. Like it’s mentioned before, the logic for an enemy is to constantly move in the -x direction:
We’ll add a createEnemy function using our WorldUtils to create an Enemy of a random EnemyType. Note that in our World, an enemy will be a KinematicBody, which means it will move but it won’t be affected if it’s hit by another object (the Runner). While we are at it, we also have to change a line in our createRunner function so we store the Runner width and height:
Now, we will add the following logic to our GameStage: when the game is running, check if there are any enemies on screen; if there aren’t any create a new one. Also, when we check if our enemies are out of bounds, we also want to check if our runner is out of bounds to destroy its Body. If an Enemy hits our Runner, we’ll apply an angular impulse to our Runner so the hit looks a bit cartoonish.
Let’s store the angular impulse we just mentioned in our Constants:
And access it through our RunnerUserData object:
Our Runner now needs to apply this angular impulse whenever he’s hit():
Now, add a couple of helper functions in our BodyUtils class: bodyIsEnemy checks whether the given Body belongs to an Enemy and bodyInBounds checks whether the Runner or an Enemy is in our screen boundaries:
Hook it all up in the GameStage:
And we’ve got ourselves a game! Run the game and you’ll get “enemies” of different types moving towards the runner. You have to jump or dodge to avoid getting hit by any of them. The following video shows the game at this point (ignore the title, the original game name was modified before the first release):
At this point, I think enough has been covered to understand the basics of implementing a running game. In the next parts of this guide I will add some more stuff to make the game a little more fun, but I recommend you take it from here and customize it as you wish.
Click here for part 4.
Feel free to drop any questions or comments in the comments section. Cheers!