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

Build a Stage3D Shoot-'Em-Up: Score, Health, Lives, HUD and Transitions

by
Gift

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

This post is part of a series called Shoot-'Em-Up.
Activetuts+ Workshop #5: Frantic 2 - Critique
Pixel-Level Collision Detection Based on Pixel Colors

In this part of the series, we’re adding gameplay elements such as health, score, and lives, the GUI elements to display them, and game logic transitions to deal with dying, game overs, level changes, and the final credits screen.


Also available in this series:

  1. Build a Stage3D Shoot-’Em-Up: Sprite Test
  2. Build a Stage3D Shoot-’Em-Up: Interaction
  3. Build a Stage3D Shoot-’Em-Up: Explosions, Parallax, and Collisions
  4. Build a Stage3D Shoot-'Em-Up: Terrain, Enemy AI, and Level Data
  5. Build a Stage3D Shoot-’Em-Up: Score, Health, Lives, HUD and Transitions
  6. Build a Stage3D Shoot-’Em-Up: Full-Screen Boss Battles and Polish

Final Result Preview

Let's take a look at the final result we will be working towards: a hardware-accelerated shoot-em-up demo that includes everything from parts one to four of this series, plus gameplay elements such as health, score, lives, the GUI elements to display them, and game logic transitions to deal with dying, game overs, level changes, and the final credits screen.



Introduction: Welcome to Level Five!

Let's continue to make a side-scrolling shooter inspired by retro arcade titles such as R-Type or Gradius in actionscript.

In the first part of this series, we implemented a basic 2D sprite engine that achieves great performance through the use of Stage3D hardware rendering and as several optimizations.

In the first part, we implemented a title screen, the main menu, sound and music, and an input system so that the player can control their spaceship using the keyboard.

In the third part, we added all the eye-candy: a particle system complete with sparks, flying debris, shockwaves, engine fire trails and tons of explosions. We also added accurate timers, collision detection and an R-Type inspired orbiting "power orb" companion that circles the player's ship.

And in the fourth part, we added A.I. (artificial intelligence) to our enemies by creating several different behaviors and movement styles, a level data parsing mechanism that allowed the use of a level editor, and a terrain background layer.

In this part, we are going to finally make this game fully playable from start to finish. When the player is hit they will take damage, and when they run out of health they are going to die in a firey explosion. If they die too many times it will be game over for them. If they make it to the end of a level's terrain, they will move on to the next level - unless they've cleared the entire game. When that happens, we'll display a "credits screen" which congratulates the player for a job well done.

The game will also track the player's high score, and all sorts of visual feedback (such as level transition messages, a health bar and even more eye candy) will help to add to the fun-factor. We will have taken what was a mere game demo and fleshed it out into a complete game!


Step 1: Open Your Existing Project

We're going to be building on the source code written in the previous tutorials, much of which will not change. If you don't already have it, be sure to download the source code from part four (download here). Open the project file in FlashDevelop (info here) and get ready to upgrade your game! This source code will work in any other AS3 compiler, from CS5.5 to Flash Builder, as long as you target Flash 11.


Step 2: Implement Game Saves

In the same way that browsers have regular cookies, Flash can also store temporary information that can be access in subsequent visits. Since this game is inspired by old-school arcade shooters, it seems only natural that it includes a high score display.

In order to save the player's current high score, we'll take advantage of the SharedObject package in Flash. You can read more about it in this tutorial. Basically, it serves as a simplistic repository of data - we can store anything there.

The one caveat is that - just like browser cookies - users can clear them in their security settings and they might even be turned off due to privacy concerns. Therefore, they cannot be relied upon and serve only as a little fun extra that we can add to our game when available. This way, if a player returns to the game their old high score will still be visible. We will also be saving which level the player reached, which would be handy in a future version of the game to implement an unlocking "level select" screen.

Create a brand new file in your project called GameSaves.as and start off by creating a new SharedObject instance in the class constructor as follows:


Step 3: Store Highscore and Level Data

Continuing with GameSaves.as, we will implement get and set functions that either access the saved data or write new values to the Flash "cookie" respectively. We need to ensure that errors are completely ignored and valid data is returned, just in case the user is running with privacy settings set on high.

That's it for the high score save game system. You could flesh this out to allow users to save their game at any point in a longer experience, and as you might imagine all sorts of interesting stats could be saved, from the number of shots fired, total playing time or total distance travelled to the ratio of hits to misses. For this example game, we're just going to use this new class to update the high score display in our brand new GUI heads-up-display, which we will implement below.


Step 4: Add a Heads-Up-Display

Almost every well-polished game needs to display information to the player as an overlay that sits on top of the action. This is often merely the player's score, and perhaps a health bar. We're going to create a great-looking HUD (heads-up-display) GUI overlay system that will show all sorts of information and should help to add some visual flair and polish to our game, such as:

  • The framerate (FPS)
  • How much RAM is being used
  • How many sprites are currently in our entity batch
  • (As well as how many have been reused)
  • The all-time high score
  • A health bar that decreases as the player gets damaged
  • The player's current score/li>
  • How many lives the player has remaining until the game is over.

This is how it is going to look:



Step 5: Upgrade the GUI Class

We are going to perform a complete re-write of the existing GameGUI.as, so open that file in your project and replace the minimalistic debug stats display from previous tutorials with this more feature-rich HUD class as follows. To begin with, we need to embed a fancy font and the background for our HUD, as well as create several new class variables.


Step 6: Embed a Font

You can choose any truetype font that you're allowed to use and embed it into your .swf. Take note of the unicodeRange parameter of the embed below. This is important to cut down the size of your flash file - it serves to eliminate the potentially thousands of glyphs that your game won't use such as international symbols, accented characters and more.


Step 7: Embed an Overlay Image

The hudOverlay below is a simple .PNG file that was created in Photoshop and is 50% transparent, so that the game can be seen underneath:



Step 8: GUI Class Vars

Continuing with GameGUI.as, add some TextField objects and data variables that we will use to hold and display the HUD stats.


Step 9: GUI Inits

During the class constrcutor for our fancy new HUD, we need to setup a TextFormat objects that we be reused for variout displays. These objects are used to store the style information such as font, text size and color. We then spawn a bunch of text fields on screen at the appropriate locations along with the hud overlay image. Finally, we start listening for the ENTER_FRAME event so that we can update the HUD during gameplay.


Step 10: Format the Values

Continuing with GameGUI.as, implement some handy formatting routines that will do things like pad the score with zeroes as seen in most arcade-style games, as well as create a simple "health bar" by converting a number into a a string of horizontal lines. Since the player is going to start out with 100 health and die when it reaches zero, and since the GUI happens to fit 13 characters nicely, we simply update the health text as approprite.

One important optimization that has been made is the use of temporary data variables that store the previously set state for each text field. This way, even if the game blindly sends a steady stream of updates to our GUI class, the on-screen representations only change when the value is actually different. This has a massive effect upon the framerate. For example, it could be many thousands of frames until the health or number of lives changes - there's no need to touch that HUD item until it does.

The transitionTf text area is formatted differently than the regular HUD text. Firstly, it is not added to the stage during the inits, since it will not be visible initially. It is rendered in a larger font, centered, and has a red glow. It will be used in the middle of the screen during level transitions, when the player has died, or when the game over screen needs to be displayed. Since it is usually offscreen, its appearance is controlled by our main game class. It will look like this:



Step 11: Render the GUI

The final routine to implement for our new HUD display GUI is the render loop which is called every frame. Most of the time, this function will do nothing and return control to our main game. Once per second we'll update the framerate, memory and entity stats, and only when values have changed will the updateScore function touch the screen.

We're done with the newly upgraded GameGUI.as. This simple addition really brings our game from mere sprite demo to something that looks like a real videogame. All we need to do now is implement the systems required to actually changes these values - a way for te player to store health and score, plus a way for enemies to destroy the player.


Step 12: Upgrade the Entity Class

We are now going to upgrade our game so that entities retain a record of their current health, score, level and number of lives. The Player is an entity, and for the most part these new stats will only be used by the game on the player, but in future versions of our game we could give different enemies varying amounts of health and damage.

Open the existing file Entity.as and add a few new class variables as follows. Note that we are NOT going to include the entire code listing for the entity class here since we're only making one change at the very top of the file. All the A.I. routines, collision detection code and the like remain unchanged.


Step 13: Add Some More Levels

Since we are going to implement transition between multiple levels, we need more than one level in our game. We also need to determine how "long" a level is so that our game knows when the player has reached the end of it. Open the existing GameLevels.as and make a few changes as follows.

To begin with, we need to embed some more level data. Using OGMO (or the level editor of your choice - even hand-coded .CSV data if you wish) create a few more levels. Since we're testing transitions, the example levels, which are included in the source code .zip file above, are intentially extremely short - only a couple screens long. For your real game, you will naturally want to design much larger levels. If we did that here, however, it would take too long to test out all the new functionality we're programming.


Step 14: Upgrade the Level Parsing

Apart from some new levels being embeded above, we are tracking the maximum length found in the level during the parsing stage. We need to do it this way because some rows of level data may be trimmed (when spaces to the far right are left blank). Therefore, we remember the "longest known" row of level data and compare it with the current row so that we are sure to see every tile in our map during gameplay.

These two simple changes to the level parsing class are all that are needed for the rest of our new gameplay functionality.


Step 15: Upgrade the Title Screen

Just for fun, I've created a brand new title screen spritesheet texture. After some Google searching, I learned that the invented name of the game, Kaizen - which was simply the first three letters in my last name and the word Zen - is in fact a real word!

In Japanese, Kaizen can be roughly translated to mean "continuous improvement" - what an apt title for a game that has been iteratively developed in small steps over the course of this tutorial series! Because it is a real Japanese phrase, and since arcade shoot-em-ups were often created in Japan in the heyday of the arcade era, the characters were added to the sprite. Additional minor changes to the controls menu item were also in order.

Here is the newly upgraded titlescreen spritesheet texture:


There are only two functions in GameMenu.as that have changed, where we account for the slightly different size of the logo.


Step 16: Upgrade the Entity Manager

Because EntityManager.as calls each entity's collision function when appropriate, we need to take into account our new entity properties for the player such as health. Almost everything remains as-is except that we will be upgrading the collision checking routine, the level changing function and the render loop.

Open EntityManager.as and upgrade it as follows. We only need to change three functions. The first step is to change checkCollisions() to not grant the player any points when they destroy something during the main menu. This can happen between multiple plays, since after the game is first run the player entity exists and is simply hidden at gameover until the start of the subsequent game.


Step 17: Invulnerability

One important gameplay change that really adds to the polish is to "debounce" the player's collisions. Debouncing is a coding term that refers to avoiding multiple successive calls to a function that are extremely close in time. In this case, imagine a situation where the player is about to be hit by an enemy bullet while surrounded by dozens of nearby threats. If we don't debounce the hit event, the player could conceivably start from 100% health, get hit ten times in a single frame, and suffer from an "insta kill".

A far better approach is to register the very first hit, subtract some health, and then switch to a temporary invulnerability state for a few seconds, which gives the player enough time to get out of harm's way. As per genre conventions, after getting hit we make the player "flicker" in and out by cycling the opacity of the player's sprite to indicate to the player that his or her ship is no longer susceptible to damage. After a few seconds, we switch back to being vulnerable.

As an extra bit of eye-candy and user feedback, we spawn a large number of explosions nearby to the player so that there is even more visual feedback. After all, getting damaged is a major event in the game and warrants an even bigger explosion. We then reduce the player's health which will eventually be reflected in the heads-up-display GUI. If the player's health goes below zero, reduce the number of lives and trigger a death transition (something that we will implement later on).

Continuing with the checkCollisions() function in EntityManager.as, code these upgrades as follows.


Step 19: Hide Things When Required

The update() function needs only minor tweaks to account for the fact that we do not want the orb or its particle trail to be visible after the game over occurs. Finally, one extra line is added to the changeLevels() function to ensure that levels start scrolling from the beginning location.

That's all we need to upgrade in the entity manager class. The last step required is to upgrade the main game class to take all these awesome new features into account.


Step 20: Upgrade the Game Itself!

We need to implement several major changes to the existing Main.as, so it is included here in full to avoid confusion. New sections are marked with a // v5 comment to help you skip over sections that remain unchanged.

To begin with, we're going to be working with points so we need to make one subtle change to our imports. All of the inits in our game are identical to those from the previous tutorial, apart from the creation of an instance of our fancy new GameSaves class.


Step 21: Transition Logic

This next function makes a huge impact on the game. We are going to implement the handleTransitions function. Our render loop checks the player's state each frame and if the player entity has any time remaining in its transitionTimeLeft property, an appropriate message is displayed via our upgraded game GUI class. A large glowing red message will be displayed in the center of the screen telling the player either what level they just cleared, that they died, or that the game is over.

Just for fun, if all levels have been cleared and none remain, a special game state of -1 is set which means that it is time to "roll the credits". This is another genre convention of most videogames: just like the end of a Hollywood movie, once the credits start rolling you know that you've reached the end. Add the transition logic function to Main.as as follows:


Step 22: Upgrade the Player Logic

Now that our gameplay can boast player death, gameover states, and health, our entity AI function that is run for the player sprite needs to be upgraded to take advantage of all this new stuff. We are going to add a few nice little upgrades to our game here.

Firstly, we want to ensure that players can't fire when they're dead. Secondly, as an additional bit of visual feedback and as a warning of impending doom, when the player's health is nearly depleted we will spawn a sorts of sparks. This will add to the tension and is sure to communicate to the player that they should be extra careful. Thirdly, as implemented above, just after being hit the player is invulnerable for a few seconds; during this time we want to flicker the opacity of the player's sprite to communicate this invulnerability.

When the player is almost dead, this is what it will look like:


Continuing with Main.as, upgrade the player logic function as follows:


Step 23: Simplify the Input Handler

We just moved the gun firing code to the playerLogic function. Therefore, we need to make one small change to the existing processInput function to account for this upgrade. Remove the obsolete shooting code.


Step 24: Upgrade the Game Start

Continuing with Main.as make a few adjustments to the startGame function. In particular, we need to account for the fact that the player (and orb companion) are only spawned on the first game and thereafter are simply hidden when not needed. Additionally, now that we have implemented a game state transition mechanism, we simply trigger one by setting the player's transitionTimeLeft property and let our transition handler, which we coded above, take care of everything.


Step 25: Handle Player State Changes

Now that the player can die and the game can end, our render loop below is going to check to see whether any of these new actions need to be processed.

Firstly, when the game ends we use our new GameSaves class to record the current high score so that if the player visits the site that hosts our game at a later date it remembers their best score. Secondly, we also need to start checking the player's state in order to trigger this new game over if all lives have been lost. Game over can also occur when the player "beats" the game.


Step 26: Handle Map Changes

Just like the player state checking above, we also need to check to see if it is time to load the next map, or if the game has been cleared and no more maps remain. Continue adding to Main.as as follows:


Step 27: Upgrade the Render Loop

The final set of upgrades we need to make is to the render loop, which is the onEnterFrame() function which is run every frame up to 60 times a second. We have removed the old debug GUI display from previous tutorials, since we now have a better FPS display as part of our fancy new heads-up-display GUI class. We also check the player and map state to determine when it is time to trigger a transition.


Step 28: Compile and Play!

We're done! Compile your project, fix any typos, and run the game. If you're having trouble with the code you typed in or just want the instant gratification of everything in one place, remember that you can download the full source code here.

Here are a few tips if you experience problems. If you do use FlashBuilder, be sure to include "-default-frame-rate 60" in your compiler options to ensure you get the best performance. If you are using Linux or a Mac, you can compile this from the command-line (or in a makefile) using something similar to "mxmlc -load-config+=obj\shmup_tutorial_part4Config.xml -swf-version=13", depending on your working environment. Remember that since we're using Flash 11 you will need to be compiling using the latest version of the Flex compiler and playerglobal.swc. Most importantly, remember that your Flash embed HTML has to include "wmode=direct" to enable Stage3D. This source has only been tested using FlashDevelop on Windows, and the tips above have been kindly submitted by your fellow readers.

Once everything compiles and runs properly you should see something that looks like this: a fast-action Stage3d shoot-em-up game complete with parallax scrolling terrain, tons of enemies to destroy, sounds, music and last but not least, a silky-smooth 60 frames per second framerate!



Part Five Complete: Prepare for Level Six!

That's it for tutorial number five in this series. We can now boast a detailed game world filled with things that can actually destroy the player, plus all sorts of fancy GUI elements like the high score and a health meter to give it a true arcade feel. We give the player a lot more in-game feedback now, whether in the form of "LEVEL COMPLETE" messages, sparks flying from our ship when we are about to die, or a period of innulnerability after we get hit so we have a chance to recover before being bombarded by the next wave of deadly enemies. Our game is now quite challenging.

We've taken what was initially a mere tech demo and brought it to the point that it really feels like a game, with a beginning, middle and end: a main menu, level transitions and the final credits. Congratulations, brave warrior! You've made it to the final boss.

In the next and final tutorial in this series, we will get to add that final layer of polish and call the game complete. Many coders have likely heard the old expression, "when you think you are 90% done, you are really only 50% done" or perhaps more commonly, "the devil's in the details." In addition to minor upgrades here and there, polish aplenty and subtle tweaks and optimizations, we are going to implement an EPIC BOSS BATTLE!

I'd love to hear from you regarding this tutorial. I warmly welcome all readers to get in touch with me via twitter: @McFunkypants, my blog mcfunkypants.com or on Google+ any time. In particular, I'd love to see the games you make using this code and I'm always looking for new topics to write future tutorials on. Get in touch with me any time.

If you have enjoyed these tutorials thus far, perhaps you'd like to learn more about Stage3D? If so, why not buy my Stage3D book?

Good luck and HAVE FUN!

Advertisement