Hostinicon
GET HOSTING FROM $3.95/MO PLUS A FREE YEAR ON TUTS+ (RRP $180). HURRY OFFER LIMITED. Check it out
Advertisement

Building Frogger with Flixel: Movement, Collision and Deployment

by
Gift

Get a free year on Tuts+ this month when you purchase a Siteground hosting plan from $3.95/mo

This is the second of two tutorials detailing how to use Flixel to build Frogger for the web and AIR on Android.

Introduction

Welcome to the second of two tutorials detailing how to build Frogger for the Web and AIR on Android. In the previous part I talked about how to set up Flixel, change states and we built our first level for Frogger. Unfortunately there isn't much we can do with our game without a player. This tutorial will cover the basics of movement, collision detection, managing game state and deploying our code to Android. There is a lot to cover so let's get started!


Final Result Preview

Here is a preview of what we will build in this tutorial:


Step 1: Creating The Player

Our player represents the last actor class we need to build. Create a Frog class and configure it like this:

create_frog_class

You will also need to add the following code to our new class:

I am not going to go through all the code since it is commented, but here are a few key points to check out. First we setup all the values for the frog's movement in the constructor along with its animations. Next we create a setter to handle changing the frog's orientation when we change directions. Finally we have a few helper methods to manage the update loop, death, restart, and floating.

Now we can add our player to the game level. Open up the PlayState class and put the following code in between the logs and cars we setup in part one.

You will also need to import the Fog class and add the following property:

It is important to place the player at the right depth level in the game. He needs to be above the logs and turtles yet below the cars so when he gets run over so he doesn't show up on top of a car. We can now test that our player is on the level by recompiling the game. Here is what you should see:

added-player

There isn't much we can do with the player until we add some keyboard controls, so let's move onto the next step.


Step 2: Keyboard Controls

In Frogger the player can move left, right, up and down. Each time the player moves, the frog jumps to the next position. In normal tile based games this is easy to set up. We simply figure out the next tile to move to and add to the x or y value until it reaches the new destination. Frogger however adds some complexity to the movement when it comes to the logs and turtles you can float on. Since these objects don't move according to the grid we need to pay extra attention to how the boarders of our level work.

Let's get started with basic controls. Open up the Frog class and add the following block of code to our update() method before super.update():

There is a lot of code here so let's go through it block by block starting with our first conditional. We need a way to know if the game state is set to playing. If you look at the end of the Frog class's constructor you will see that we save a reference to the current play state in a local variable. This is a neat trick we can use to help our game objects read game state from the active FlxState. Since we know the frog is always going to be used in the PlayState it is safe to assume that the correct state of the game is set to PlayState. Now that we have this FlxState we can check the game's actual state before doing anything in the FrogClass.

I just want to take a second to clear up some terminology. The word state has several connotations in our FlxFrogger game. There are FlxStates with represent the current screen or display of the game and then there is game state. Game state represents the actual activity the game is currently in. Each of the game states can be found in the GameState class inside of the com.flashartofwar.frogger.enum package. I will do my best to keep the two uses of state as clear as possible.

So back to our frog. If the game state is set to "playing" then it is safe to detect any movement request for the frog and also update the its animation. You will see later on how we handle death animations and freezing the player based on collisions. Our first block of code determines if the Frog has reached a targetX and targetY position. The second block of code actually handles increasing the frog's x and y values.

Now we can talk about how to actually move the frog. This is the next conditional within out targetX/Y block. Once a keystroke has been detected and the new targetX/Y values have been set we can immediately validate that we are moving. If so we need to play the frog hop sound and set the frog isMoving flag to true. If these values have not changes we are not moving. After this is set we can handle movement logic in the last conditional.

Finally we test if isMoving is true and see what direction we need to move based on which way the frog is facing. The frog can only move in one direction at a time so this makes it very easy to set up. We also call the play() method to animate the frog. The last thing you should know is how we calculate the moveX and moveY vales. If you look in the constructor you will see that we determine that the frog will move X number of pixels over Y number of frames. In this case we want our animation to last 8 frames so in order to move the 40 pixels we need each time, we will move by 5px each frame for 8 frames. This is how we get the smooth hop animation and keep the player from continually pressing the keys and interrupting the animation.


Step 3: Car Collision Detection

Flixel handles all of the collision logic we will need for us. This is a huge time saver and all you have to do is ask the FlxU class to detect any overlapping sprites. What is great about this method is that you can test FlxSprites or FlxSpriteGroups. If you remember we set up our cars in their own carGroup which will make it incredibly easy to test if they have collided with the frog. In order to get this started we need to open up the PlayState and add the following code:

This includes a little more of the core game state logic which we will fill out in the next few steps but let's take a look at the code under the "Do collision detections" comment. As you can see we have a new class called FlxU which helps manage collision detection in the game as I had mentioned above. This class accepts targetA, targetB and a callback method. The overlap() method will test all of the children of targetA against the children of targetB then pass the results to your supplied callback method. Now we need to add the carCollision callback to our class:

We are taking a few liberties with this callback since we know that the first param will be a FlxSprite and that the second one is a Frog. Normally this is an anonymous and untyped method when it is called once a collision is detected. One thing you will notice is that we test that the gameStates is set to collision. We do this because when a collision happens the entire game freezes to allow a death animation to play but technically the frog is still colliding with the carGroup. If we didn't add in this conditional we would be stuck in an infinite loop while the animation tried to play.

Now we just need to add the logic for killing the player. This will go in a killPlayer() method and here is the code:

Finally we need to fill in the death() logic in our Frog class. Add the following code to the death method:

Here we are telling the frog sprite to play the die animation we setup in the constructor. Now compile the game and test that the player can collide with any of the cars or truck.

frog_collide_test

Step 4: Restarting After A Death

Now that we have our first set of collision detection in place along with the death animation, we need a way to restart the game level so the player can continue to try again. In order to do this we need to add a way to notify the game when the death animation is over. Let's add the following to our Frog update() method just above where we test if the gameState is equal to playing:

Notice the trailing else, that should run into if (state.gameState == GameStates.PLAYING) so that we are testing for collision then playing state.

collision_test_code

Now we need to go back into our PlayState class and add the following method call to else if (gameState == GameStates.DEATH_OVER):

Now we can add a restart method:

Last thing we need to do is add this code to the Frog Class's restart method.

Now we should be ready to compile and test that all of this works. Here is what you should see, when a car hits the player everything freezes while the death animation plays. When the animation is over everything should restart and the player will show up at the bottom of the screen again. Once you have that set up we are ready to work out the water collision detection.


Step 5: Water Collision Detection

With a solid system in place to handle collision, death animation and restarting it should be really easy to add in the next few steps of collision detection. The water detection is a little different however. Since we always know where the water is, we can test against the player's y position. If the player's y value is greater then where the land ends, we can assume the player has collided with the water. We do this to help cut down on the amount of collision detection we would need to do if we tested each open space of water when the frog lands. Let's add the following to our PlayState under where we test for a car collision:

You will also need to add two properties, the first lets us know where the water begins on the Y axis and the other is a boolean to let us know if the player is floating. We'll be using this floating boolean in the next step.

Determining the start coordinate of the water is easy considering everything is part of the grid. Next we need to add our waterCollision() method:

Compile and test that if we go into the water the player dies.

frog_drown

Next we will look into how to allow the frog to float on the logs and turtles when they are within the water area of the level.


Step 6: Floating Collision Detection

In order to figure out if the Frog can float we need to test for a collision on the logGroup or the turtleGroup. Let's add the following code in our PlayState class just under where we test for the car collision. Make sure it is above the water collision conditional. This is important because if we test for the player in the water before the logs or turtles we could never handle the floating correctly.

Here are the two methods we need to handle a collision:

You will also need to import WrappingSprite and TimerSprite. Once you have that in place we need to go back into our Frog class and add the following to our float() method:

We just added a lot of code and I commented most of it, but I wanted to talk about this last part right here. This code actually handles moving the frog in the same direction with the same speed as the log or turtle the player is on. We use a few tricks to make sure that when the player attempts to move off the floating object they are not overridden by what they are floating on. A big part of game development is state management and using flags such as isMoving to help let other parts of the code know what can and can't be done are a huge help.

Let's compile the game and check out if the player is able to float on logs.

frog_float

One thing you may have noticed is that once you land on a log or turtle you will no longer drown. That is because we need to reset the playerIsFloating flag before we do all of our collision detection. Go back into the PlayState and add the following just before we start testing for the car collision.

So your test block should look like this:

reset_float_state

As I have already mentioned, there is a delicate balance of maintaining state and making sure you set and unset these state flags at the right time in order to save yourself a lot of frustration when building your own games. Now you can do a new compile to make sure everything is working correctly and we can move onto the next step.


Step 7: Home Collision Detection

Adding in collision detection for the home bases should be very straight forward. Add the following collision test to the end of the collision detection code in our PlayState class and above where we test if the player has jumped into the water:

Now we need to create our baseCollision() method to handle what happens when you land on a home:

We will also need to add the following property to our class:

Here you can see we are testing to see if the home has already been landed in, next we test if all of the homes have frogs in them and finally we just trigger restart on a successful landing at the home base. It is important to note that in this tutorial we are not testing for the state of the home. Remember there is a bonus fly and an alligator you could land on. Later if you want to add that in you can do it here.

Now we need to add some logic for when a level has been completed:

We need to add the following code to the restart() method above where we reset the game state:

Finally we need to add a resetBases() method to our PlayState class:

When all of the bases have been landed on we loop through them and call the empty() method which will reset the graphic and landed value. Finally, we need to set the frogs that have been saved to zero. Let's compile the game and test what happens when you land in all the bases. After you land in a home base it should change to a frog icon indicating you have landed there already.

level-complete-freeze

As you can see, when we have saved all the frogs the level freezes because there is no logic to restart the level again. We also need to add some game messaging to let the player know what is going on and to use as a notification system in the game so we can manage when to restart all the animations again. This is what we will add in the next step.


Step 8: Adding Game Messages

A lot happens in the game and one of the best ways to communicate to the player what is going on is by adding in game messaging. This will help inform the player when game pauses to activate a game state such as a level complete or a death. In our PlayState class, add the following code above where we create our home bases in the create() method:

You will also need to add the following properties:

You will also need to import the FlxText class. Now let's go into our update() method and add the following code into the gameState == GameStates.LEVEL_OVER conditional:

The basic idea here is that we use a timer to count down how long a game message should be displayed. When the timer reaches zero we can restart the level. This gives the player some time to rest in between levels. Also we will need to add the next block of code just below where we test the water collision around line 203:

Here we are able to manage the visibility of the game message. In our baseCollision() method we need to add the following code below where we test for target.mode != Home.success conditional around line 317:

Add the following properties which we will actually use in the next step:

Then add this line of code around line 328 inside the conditional just under where we call success() on the target:

This allows us to calculate the total time it has taken to complete a label which we will connect in the next step. Now in resetBases() add the following code at the end just under where we set safeFrogs to 0:

Compile the game and you should now see game status messages when you land in a home or restart the level.

game-messages

Step 9: Game Timer

Now we are ready to add the logic for the game timer. In the PlayState class's create() method add the following code under where we create the bg image:

You will need the following property and constant:

And below the last car added to the carGroup add the following:

You will also need the following properties:

Now let's add the following code in our update function above where we manage hiding the gameMessage on line 234:

And this is the method that gets called when time is up:

Finally we need to reset the timer when the time runs up, the player lands on a home base or the level restarts. We can do this in our restart() method just before we call player.restart():

You can compile the game now to validate all of this works. We just added a lot of code but hopefully the code and comments are straight forward enough that we don't need to explain it too much.

timer_going_down

Step 10: Lives

What game would be complete without lives? In frogger you get 3 lives. Let's add the following function call in our create() method under where we set up the timeAlmostOverWarning = TIMER_BAR_WIDTH * .7 on line 69:

And here are the methods that will manage lives for us:

Also make sure you add the following property:

Again these methods are well commented, but the basic idea is that we keep all of our lives in an array. We start off by adding a life to the array based on the value passed in. To remove lives we simply splice 1 out of the array. This is a quick way to handle lives by taking advantage of some native classes such as the Array. Now we just need to set up some logic to remove a life when you die.

Add the following call to our killPlayer() method above where we set player.death():

Now you can test that when you die a life should be removed from the display.

less_one_life

Make sure you don't go past the total lives or you will get an error. In the next step we will add in some game over logic to prevent this error from happening.


Step 11: Game Over Screen

We can quickly test to see if the player is out of lives and the game is over in our restart() method. Let's replace the entire method with the following:

Here you see we are testing to see if the total lives equal zero and the game state is set to game over. If so we can call the game over method. If not, it is business as usual and the level gets restarted. Now we need to add a gameOver() method:

Now, before we can see this in action, we just need to add a few more lines of code to our update() method to handle removing the game over message and returning the player to the StartState. Look for where we test for gameState == GameStates.GAME_OVER and add the following code into the conditional:

Now you can test the game over message by killing the player 3 times. You should see this before getting thrown back to the StartState.

game_over_message

Now that all of the pieces are in place we can easily add in scoring.


Step 12: Score

We need to add up the score when the player jumps, lands in a base and finishes a level. Flixel makes it easy to remember a score and you can access it at any time by using the FlxG.score property on the FlxG singleton. First we need to create a class to store some score values for us. Create a class called ScoreValues and configure it like this:

create_score_values

Here is the code for the class:

Now go back into the PlayState class and add the following above our gameMessageGroup code in the create() method around line 77:

You will need the following property:

Let's add some scoring to our game in the following places.

After line 407 where we calculate the time left over in the target.mode != Home.Success conditional:

Make sure you import the ScoreValues class. Next we will add the following to our levelComplete() method:

Now we need to go into our Frog class and add the following to our update() method where we set isMoving to true on line 141:

Before we can test this we need to update the score in our game loop. Let's go back into the PlayState class and add the following to our update() method on line 281 just before where we test for gameState == GameStates.DEATH_OVER:

Compile the game and make sure the score is working.

score_working

Step 13: Building for Mobile

Now that everything is in place we can easily compile and deploy FlxFrogger to an android device which has AIR installed. Instead of going through how to set up the pre-release of AIR and the Android SDK I just want to focus on how to get this project ready to compile and deploy. Before we jump in you should check out this excellent post by Karl Freeman on how to get FDT configured to build Air for Android.

You will need to have AIR 2.5 setup in your Flex SDK directory and a copy of the Android SDK on your computer. If you remember in part one our Ant script is pointed to a Flex SDK so we can compile. We need to setup where the Android SDK is so we can deploy our Air apk to the phone. Open up the build.properties file and look for the android.sdk property.

You will need to point this to where you downloaded your Android SDK. Here is what my path looks like:

As you can see I have renamed it to let me know it is the 2.2 build. I like to keep back ups of my SDKs based on the version number so this is a good habit to pick up. Let's open up the Run External Tools Configuration where we set up our original build target in part 1.

Right-click on our build and select "duplicate". Rename the build copy to FlxFrogger Android Build. Now click on it and and let's change the default target to deploy-to-phone.

deploy_to_phone

Now you have a new run for compiling and deploying to an Android phone. If your phone is connected and you would like to test that it works, simply run the Ant task and wait for it to transfer over. Remember you need to have Air installed on your phone for it to run.

One last thing. You may have noticed that there is also a deploy-to-emulator target. You can use this if you want to test with the Android Emulator but be warned that the emulator is incredibly slow. I found it almost unbearable for getting any real sense of how Flash ran on the phone so don't be surprised if you get under 3-4 fps.


Step 14: Touch Controls

By default Flixel is setup to work with the keyboard but on mobile devices you may only have a touchscreen to work with. Setting up touch controls is very easy, we will create our own touch buttons out of FlxSprites in the next step. Create a new class called TouchControls and configure it like this:

create_touch_controls

Go into the Frog class and replace the block of code where we test for key presses with the following code:

As you can see, in addition to testing for key presses we will directly check our TouchControls to see if they have been pressed. You will also need to import the TouchControls class and add the following property:

Now, in order to show touch controls when you are compiling for a mobile device, we are going to use a compiler conditional. Add the following code to the PlayState class in the create() method just before where we set the gameState to playing:

I have added some special comments in here to help keep FDT from throwing an error since it doesn't understand compiler conditionals yet. Here is what the code looks like without these special FDT ignore comments:

Also make sure you add the following property and import TouchControls:

As you can see a compiler conditional is just like a normal if statement. Here we are just testing that if the value of CONFIG::mobile is set to true then we are building for mobile and, if so, it should show the controls. Telling the compiler about this config variable is all handled in our build script so you don't have to worry about anything. Depending on what type of build target you call, the value is changed when compiling. It couldn't be easier. This is a great technique to use when you have mobile specific code you need to execute that you wouldn't want to run in your web based version.

You can test these controls by deploying to the phone, here is what you should see:

touch_controls

If you test in the browser you will not see the controls. Hopefully you can see the power of compiler conditionals and how much of an advantage they will be when deploying content over multiple devices and platforms.


Step 15: Optimizations

Optimizing for mobile is a very time consuming process. Luckily this game runs very well on any Android phone with a 1ghz processor or faster. It also helps that we chose to build a game with a very low frame rate. A few things I have noticed which would help give the impression that the game was actually running faster is to speed up the time it takes to move the frog from tile to tile and also speed up the game time and game over wait delay.

Something else you may want to try is to group objects that need to have collision detection together by row instead of type. So right now all of the cars and trucks are being tested as one large group. Since the frog moves vertically along the grid we could speed up the collision detection by only testing a row at a time. Even though this is a much better approach to handling lots of collision detection, I would be surprised if you gain any extra frames per second. Air on Android executes code surprisingly well, the real bottleneck is in the renderer.

To address the slow Flash player renderer you could downscale the images. We built this game at the full 480 x 800 resolution. If we tweaked this to run at half of the pixel size it would be less overhead for Flash to render and may give us a few extra frames per second. Doing something like this would be incredibly time consuming and may not be worth the extra work. With any type of optimization you should have a set list of devices or computer specs to test against and try to build for the lowest common denominator. When building Flash games for web, desktop and mobile it is always best to start with a mobile version since you will hardly see much change in the desktop and web playback.


Conclusion What's Next?

Right now you have a full Frogger game engine. You can create a new level, move within the level, score and detect when a level is complete. The next thing you should add on your own are additional levels. The game is set up in a clean way that creating new levels based on this template shouldn't require much work. You may want to break out the level creation code into it's own class so you can load up specific levels when you need them.

Also you could add a multiplier to the default speed each object gets when they are created inside of the PlayState so that each new level tells the game actors to move faster and faster.

Finally you could completely re-skin the game and make it your own. There are a lot of Frogger clones out there and since all of the heavy lifting has been done for you, the sky is the limit. If you want to add onto this project and fill in some of the missing features I invite you to fork the source code on GitHub and let me know what additions you make. I'll be happy to add them to the base project and give you credit.

If you run into any problems or have questions, leave a comment below and I will do my best to answer them. Thanks for reading :)

Advertisement