7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
  1. Code
  2. Games

Build a Stage3D Shoot-'Em-Up: Sprite Test

Scroll to top
Read Time: 41 mins
This post is part of a series called Shoot-'Em-Up.
Create a Simple Space Shooter Game in HTML5 With EaselJS
Build a Stage3D Shoot-'Em-Up: Interaction

In this tutorial series (part free, part Premium) we'll create a high-performance 2D shoot-em-up using the new hardware-accelerated Stage3D rendering engine. We will be taking advantage of several hardcore optimization techniques to achieve great 2D sprite rendering performance. In this part, we'll build a high-performance demo that draws hundreds of moving sprites on-screen at once.

Final Result Preview

Let's take a look at the final result we will be working towards: a high-performance 2D sprite demo that uses Stage3D with optimizations that include a spritesheet and object pooling.

Introduction: Flash 11 Stage3D

If you're hoping to take your Flash games to the next level and are looking for loads of eye-candy and amazing framerate, Stage3D is going to be your new best friend.

The incredible speed of the new Flash 11 hardware accelerated Stage3D API is just begging to be used for 2D games. Instead of using old-fashioned Flash sprites on the DisplayList or last-gen blitting techniques as popularized by engines such as FlashPunk and Flixel, the new breed of 2D games uses the power of your video card's GPU to blaze through rendering tasks at up to 1000x the speed of anything Flash 10 could manage.

Although it has 3D in its name, this new API is also great for 2D games. We can render simple geometry in the form of 2D squares (called quads) and draw them on a flat plane. This will enable us to render tons of sprites on screen at a silky-smooth 60fps.

We'll make a side-scrolling shooter inspired by retro arcade titles such as R-Type or Gradius in ActionScript using Flash 11's Stage3D API. It isn't half as hard as some people say it is, and you won't need to learn assembly language AGAL opcodes.

In this 6-part tutorial series, we are going to program a simple 2D shoot-'em-up that delivers mind-blowing rendering performance. We are going to build it using pure AS3, compiled in FlashDevelop (read more about it here). FlashDevelop is great because it is 100% freeware - no need to buy any expensive tools to get the best AS3 IDE around.

Step 1: Create a New Project

If you don't already have it, be sure to download and install FlashDevelop. Once you're all set up (and you've allowed it to install the latest version of the Flex compiler automatically), fire it up and start a new "AS3 Project."

Create an .AS3 project using FlashDevelopCreate an .AS3 project using FlashDevelopCreate an .AS3 project using FlashDevelop

FlashDevelop will create a blank template project for you. We're going to fill in the blanks, piece-by-piece, until we have created a decent game.

Step 2: Target Flash 11

Go into the project menu and change a few options:

  1. Target Flash 11.1
  2. Change the size to 600x400px
  3. Change the background color to black
  4. Change the FPS to 60
  5. Change the SWF filename to a name of your choosing
Project propertiesProject propertiesProject properties

Step 3: Imports

Now that our blank project is set up, let's dive in and do some coding. To begin with, we will need to import all the Stage3D functionality required. Add the following to the very top of your Main.as file.

Step 4: Initialize Stage3D

The next step is to wait for our game to appear on the Flash stage. Doing things this way allows for the future use of a preloader. For simplicity, we will be doing most of our game in a single little class that inherits from the Flash Sprite class as follows.

After setting some stage-specific properties, we request a Stage3D context. This can take a while (a fraction of a second) as your video card is configured for hardware rendering, so we need to wait for the onContext3DCreate event.

We also want to detect any errors that may occur, especially since Stage3D content does not run if the HTML embed code that loads your SWF doesn't include the parameter "wmode=direct". These errors can also happen if the user is running an old version of Flash or if they don't have a video card capable of handling pixel shader 2.0.

Step 5: Handle Any Events

Add the following functions that detect any events that might be triggered as specified above. In the case of errors due to running old Flash plugins, in future versions of this game we might want to output a message and remind the user to upgrade, but for now this error is simply ignored.

For users with old video cards (or drivers) that don't support shader model 2.0, the good news is that Flash 11 is smart enough to provide a software renderer. It doesn't run very fast but at least everyone will be able to play your game. Those with decent gaming rigs will get fantastic framerate like you've never seen in a Flash game before.

The event handling code above detects when Stage3D is ready for hardware rendering and sets the variable context3D for future use. Errors are ignored for now. The resize event simply updates the size of the stage and batch rendering system dimensions.

Step 6: Init the Sprite Engine

Once the context3D has been received, we are ready to start the game running. Continuing with Main.as, add the following.

This function creates a sprite rendering engine (to be implemented below) on the stage, ready to use the full size of your flash file. We then add the entity manager and batched geometry system (which we will discuss below). We are now able to give a reference to the entity manager to our stats GUI class so that it can display some numbers on screen regarding how many sprites have been created or reused. Lastly, we start listening for the ENTER_FRAME event, which will begin firing at a rate of up to 60 times per second.

Step 7: Start the Render Loop

Now that everything has been initialized, we are ready to play! The following function will be executed every single frame. For the purposes of this first tech demo, we are going to add one new sprite on stage each frame. Because we are going to implement an object pool (which you can read more about in this tutorial) instead of inifinitely creating new objects until we run out of RAM, we are going to be able to reuse old entities that have moved off screen.

After spawning another sprite, we clear the stage3D area of the screen (setting it to pure black). Next we update all the entities that are being controlled by our entity manager. This will move them a little more each frame. Once all sprites have been updated, we tell the batched geometry system to gather them all up into one large vertex buffer and bast them on screen in a single draw call, for efficiency. Finally, we tell the context3D to update the screen with our final render.

That's it for the inits! As simple as it sounds, we have now created a template project that is ready to blast out an insane number of sprites. We are not going to use any vector art. We aren't going to put any old-fashioned Flash sprites on the stage apart from the Stage3D window and a couple of GUI overlays. All the work of rendering our in-game graphics is going to be handled by Stage3D, so that we can enjoy improved performance.

Going Deeper: Why Is Stage3D So Fast?

Two reasons:

  1. It uses hardware acceleration, meaning that all drawing commands are sent to the 3D GPU on your video card in the same way that XBOX360 and PlayStation3 games get rendered.
  2. These rendering commands are processed in parallel to the rest of your ActionScript code. This means that once the commands are sent to your video card, all rendering is done at the same time as other code in your game is running - Flash doesn't have to wait for them to be finished. While pixels are being blasted onto your screen, Flash gets to do other things like handle the player input, play sounds and update enemy positions.
  3. That said, many Stage3D engines seem to get bogged down by a few hundred sprites. This is because they have been programmed without regard to the overhead that each draw command adds. When Stage3D first came out, some of the first 2D engines would draw each and every sprite individually in one giant (slow and inefficient) loop. Since this article is all about extreme optimization for a next-gen 2D game with fabulous framerate, we are going to implement an extremely efficient rendering system that buffers all geometry into one big batch so we can draw everything in only one or two commands.

    How to Be Hardcore: Optimize!

    Hardcore gamedevs love optimizations. In order to blast the most sprites on screen with the fewest number of state changes (such as switching textures, selecting a new vertex buffer, or having to update the transform once for each and every sprite on screen), we are going to take advantage of the following three performance optimizations:

    1. object pooling
    2. spritesheet (texture atlas)
    3. batched geometry

    These three hardcore gamedev tricks are the key to getting awesome FPS in your game. Let's implement them now. Before we do, we need to create some of the tiny classes that these techniques will make use of.

    Step 8: The Stats Display

    If we're going to be doing tons of optimizations and using Stage3D in an attempt to achieve blazingly fast rendering performance, we need a way to keep track of the statistics. A few little benchmarks can go a long way to prove that what we're doing is having a positive effect on the framerate. Before we go farther, create a new class called GameGUI.as and implement a super-simple FPS and stats display as follows.

    Step 9: The Entity Class

    We are about to implement an entity manager class that will be the "object pool" as described above. We first need to create a simplistic class for each individual entity in our game. This class will be used for all in-game objects, from spaceships to bullets.

    Create a new file called Entity.as and add a few getters and setters now. For this first tech demo, this class is merely an empty placeholder without much functionality, but in later tutorials this is where we will be implementing much of the gameplay.

    Step 10: Make a Spritesheet

    An important optimization technique we are going to use is the use of a spritesheet - sometimes referred to as a Texture Atlas. Instead of uploading dozens or hundreds of individual images to video RAM for use during rendering, we are going to make a single image that holds all the sprites in our game. This way, we can use a single texture to draw tons of different kinds of enemies or terrain.

    Using a spritesheet is a considered a best practice by veteran gamedevs who need to ensure their games run as fast as possible. The reason it speeds things up so much is much the same as the reason why we are going to use geometry batching: instead of having to tell the video card over and over to use a particular texture to draw a particular sprite, we can simply tell it to always use the same texture for all draw calls.

    This cuts down on "state changes" which are extremely costly in terms of time. We no longer need to say "video card, start using texture 24... now draw sprite 14" and so on. We just say "draw everything using this one texture" in a single pass. This can increase performance by an order of magnitude.

    For our example game we will be using a collection of legal-to-use freeware images by the talented DanC, which you can get here. Remember that if you use these images you should credit them in your game as follows: "Art Collection Title" art by Daniel Cook (Lostgarden.com).

    Using Photoshop (or GIMP, or whatever image editor you prefer), cut and paste the sprites your game will need into a single PNG file that has a transparent background. Place each sprite on an evenly-spaced grid with a couple pixels of blank space between each. This small buffer is required to avoid any "bleeding" of edge pixels from adjacent sprites that can occur due to bilinear texture filtering that happens on the GPU. If each sprite is touching the next, your in-game sprites may have unwanted edges where they should be completely transparent.

    For optimization reasons, GPUs work best with images (called textures) that are square and whose dimensions are equal to a power of two and evenly divisible by eight. Why? Because of the way that the pixel data is accessed, these magic numbers happen to align in VRAM in just the right way to be fastest to access, because the data is often read in chunks.

    Therefore, ensure that your spritesheet is either 64x64, 128x128, 256x256, 512x512 or 1024x1024. As you might expect, the smaller the better - not just in terms of performance but because a smaller texture will naturally keep your game's final SWF smaller.

    Here is the spritesheet that we will be using for our example. "Tyrian" art by Daniel Cook (Lostgarden.com).

    The spritesheet image
    Right-click to download

    Step 11: The Entity Manager

    The first optimization technique we're going to take advantage of to achieve blazing performance is the use of "object pools". Instead of constantly allocating more ram for objects like bullets or enemies, we're going to make a reuse pool that recycles unused sprites over and over again.

    This technique ensures that RAM use stays very low and GC (garbage collection) hiccups rarely occur. The result is that framerate will be higher and your game will run smoothly no matter how long you play.

    Create a new class in your project called EntityManager.as and implement a simple recycle-on-demand mechanism as follows.

    Step 12: Set Boundaries

    Our entity manager is going to recycle entities when they move off the left edge of the screen. The function below is called during inits or when the resize event is fired. We add a few extra pixels to the edges so that sprites don't suddenly pop in or out of existence.

    Step 13: Set Up the Sprites

    The entity manager runs this function once at startup. It creates a new geometry batch using the spritesheet image that was embedded in our code above. It sends the bitmapData to the spritesheet class constructor, which will be used to generate a texture that has all the available sprite images on it in a grid. We tell our spritesheet that we're going to use 64 different sprites (8 by 8) on the one texture. This spritesheet will be used by the batch geometry renderer.

    If we wanted, we could use more than one spritesheet, by initializing additional images and batches as required. In the future, this might be where you create a second batch for all terrain tiles that go underneath your spaceship sprites. You could even implement a third batch which is layered on top of everything for fancy particle effects and eye candy. For now, this simple tech demo only needs a single spritesheet texture and geometry batch.

    Step 14: The Object Pool

    This is where the entity manager increases performance. This one optimization (an object reuse pool) will allow us to only create new entities on demand (when there aren't any inactive ones that can be reused). Note how we reuse any sprites that are currently marked as inactive, unless they are all currently being used, in which case we spawn a new one. This way, our object pool only every holds as many sprites as are even visible at the same time. After the first few seconds that our game has been running, the entity pool will remain constant - rarely will a new entity need to be created once there are enough to handle what's going on on-screen.

    Continue adding to EntityManager.as as follows:

    The functions above are run whenever a new sprite needs to be added on screen. The entity manager scans the entity pool for one that is currently not in use and returns it when possible. If the list is fully of active entities, a brand new one needs to be created.

    Step 15: Simulate!

    The final function that is the responsibility of our entity manager is the one that gets called every frame. It is used to do any simulation, AI, collision detection, physics or animation as required. For the current simplistic tech demo, it simply loops through the list of active entities in the pool and updates their positions based on velocity. Each entity is moved according to their current velocity. Just for fun, they are set to spin a little each frame as well.

    Any entity that goes past the left side of the screen is "killed" and is marked as inactive and invisible, ready to be reused in the functions above. If an entity touches the other three screen edges, the velocity is reversed so it will "bounce" off that edge. Continue adding to EntityManager.as as follows:

    Step 16: The Sprite Class

    The final step to get everything up and running is to implement the four classes that make up our "rendering engine" system. Because the word Sprite is already in use in Flash, the next few classes will use the term LiteSprite, which is not just a catchy name but implies the lightweight and simplistic nature of this engine.

    To begin, we will create the simple 2D sprite class that our entity class above refers to. There will be many sprites in our game, each of which is collected into a large batch of polygons and rendered in a single pass.

    Create a new file in your project called LiteSprite.as and implement some getters and setters as follows. We could probably get away with simply using public variables, but in future versions changing some of these values will require running some code first, so this technique will prove invaluable.

    Each sprite can now keep track of where it is on screen, as well as how big it is, how transparent, and what angle it is facing. The spriteID property is a number used during rendering to look up which UV (texture) coordinate needs to be used as the source rectangle for the pixels of the spritesheet image it uses.

    Step 17: The Spritesheet Class

    We now need to implement a mechanism to process the spritesheet image that we embedded above and use portions of it on all our rendered geometry. Create a new file in your project called LiteSpriteSheet.as and begin by importing the functionality required, defining a few class variables and a constructor function.

    The class constructor above is given a BitmapData for our spritesheet as well as the number of sprites that are on it (in this demo, 64).

    Step 18: Chop It Up

    Because we are using a single texture to store all of the sprite images, we need to divide the image into several parts (one for each sprite on it) when rendering. We do this by assigning different coordinates for each vertex (corner) of each quad mesh used to draw a sprite.

    These coordinates are called UVs, and each goes from 0 to 1 and represents where on the texture stage3D should start sampling pixels when rendering. The UV coordinates and pixel rectangles are stored in an array for later using during rendering so that we don't have to calculate them every frame. We also store the size and shape of each sprite (which in this demo are all identical) so that when we rotate a sprite we know its radius (which is used to keep the pivot in the very centre of the sprite).

    Step 19: Generate Mipmaps

    Now we need to process this image during the init. We are going to upload it for use as a texture by your GPU. As we do so, we are going to create smaller copies that are called "mipmaps". Mip-mapping is used by 3d hardware to further speed up rendering by using smaller versions of the same texture whenever it is seen from far away (scaled down) or, in true 3D games, when it is being viewed at an oblique angle. This avoids any "moiree" effects (flickers) than can happen if mipmapping is not used. Each mipmap is half the width and height as the previous.

    Continuing with LiteSpriteSheet.as, let's implement the routine we need that will generate mipmaps and upload them all to the GPU on your video card.

    Step 20: Batched Geometry

    The final hardcore optimization we are going to implement is a batched geometry rendering system. This "batched geometry" technique is often used in particle systems. We are going to use it for everything. This way, we can tell your GPU to draw everything in one go instead of naively sending hundreds of draw commands (one for each sprite on screen).

    In order to minimize the number of draw calls and rendering everything in one go, we will be batching all game sprites into a long list of (x,y) coordinates. Essentially, the geometry batch is treated by your video hardware as a single 3D mesh. Then, once per frame, we will upload the entire buffer to Stage3D in a single function call. Doing things this way is far faster than uploading the individual coordinates of each sprite separately.

    Create a new file in your project called LiteSpriteBatch.as and begin by including all the imports for functionality it will need, the class variables it will use, and the constructor as follows:

    Step 21: Batch Parent and Children

    Continue by implementing getters and setters and functionality for handling the addition of any new sprites to the batch. The parent refers to the sprite stage object used by our game engine, while the children are all the sprites in this one rendering batch. When we add a child sprite, we add more data to the list of verteces (which supplies the locations on screen of that particular sprite) as well as the UV coordinates (the location on the spritesheet texture that this particular sprite is stored at). When a child sprite is added or removed from the batch, we set a boolean variable to tell our batch system that the buffers need to be re-uploaded now that they have changed.

    Step 22: Set Up the Shader

    A shader is a set of commands that is uploaded directly to your video card for extremely fast rendering. In Flash 11 Stage3D, you write them in a kind of assembly language called AGAL. This shader needs only be created once, at startup. You don't need to understand assembly language opcodes for this tutorial. Instead, simply implement the creation of a vertex program (which calculates the locations of your sprites on screen) and a fragment program (which calculates the color of each pixel) as follows.

    Step 23: Move the Sprites Around

    Just before being rendered, each sprite's vertex coordinates on screen will have most likely changed as the sprite moves around or rotates. The following function calculates where each vertex (corner of the geometry) needs to be. Because each quad (the square that makes up one sprite) has four vertices each, and each vertex needs an x, y and z coordinate, there are twelve values to update. As a little optimization, if the sprite is not visible we simply write zeroes into our vertex buffer to avoid doing unnecessary calculations.

    Step 24: Draw the Geometry

    Finally, continue adding to the LiteSpriteBatch.as class by implementing the drawing function. This is where we tell stage3D to render all the sprites in a single pass. First, we loop through all known children (the individual sprites) and update the verterx positions based on where they are on screen. We then tell stage3D which shader and texture to use, as well as set the blend factors for rendering.

    What is a blend factor? It defines whether or not we should use transparency, and how to deal with transparent pixels on our texture. You could change the options in the setBlendFactors call to use additive blanding, for example, which looks great for particle effects like explosions, since pixels will increase the brightness on screen as they overlap. In the case of regular sprites, all we want is to draw them at the exact color as stored in our spritesheet texture and to allow transparent regions.

    The final step in our draw function is to update the UV and index buffers if the batch has changed size, and to always upload the vertex data because our sprites are exected to be constantly moving. We tell stage3D which buffers to use and finally render the entire giant list of geometry as if it were a single 3D mesh, so that it gets drawn using a single, fast, drawTriangles call.

    Step 25: The Sprite Stage Class

    The final class required by our fancy (and speedy) hardware-accelerated sprite rendering engine is the sprite stage class. This stage, much like the traditional Flash stage, holds a list of all the batches that are used for your game. In this first demo, our stage will only be using a single batch of sprites, which itself only uses a single spritesheet.

    Create one last file in your project called LiteSpriteStage.as and begin by creating the class as follows:

    Step 26: The Camera Matrix

    In order to know exactly where on screen each sprite needs to go, we will track the location and size of the rendering window. During our game's initializations (or if it changes) we create a model view matrix which is used by Stage3D to transform the internal 3D coordinates of our geometry batches to the proper on-screen locations.

    Step 27: Handle Batches

    The final step in the creation of our Stage3D game demo is to handle the addition and removal of geometry batches as well as a loop that calls the draw function on each batch. This way, when our game's main ENTER_FRAME event is fired, it will move the sprites around on screen via the entity manager and then tell the sprite stage system to draw itself, which in turn tells all known batches to draw.

    Because this is a heavily optimized demo, there will only be one batch in use, but this will change in future tutorials as we add more eye candy.

    Step 28: Compile and Run!

    We're almost done! Compile your SWF, fix any typos, and check out the graphical goodness. You should have a demo that looks like this:

    Screenshot of our final .SWF in action. Sprite-o-licious!Screenshot of our final .SWF in action. Sprite-o-licious!Screenshot of our final .SWF in action. Sprite-o-licious!

    If you are having difficulties compiling, note that this project needs a class that was made by Adobe which handles the compilation of AGAL shaders, which is included in the source code .zip file download.

    Just for reference, and to ensure that you've used the correct filenames and locations for everything, here is what your FlashDevelop project should look like:

    The project window - what each file is named.

    Tutorial Complete: You Are Awesome

    That's it for tutorial one in this series! Tune in next week to watch the game slowly evolve into a great-looking, silky-smooth 60 FPS shoot-em-up. In the next part, we will implement player controls (using the keyboard to move around) and add some movement, sounds and music to the game.

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

    Thanks for reading. See you next week. Good luck and HAVE FUN!

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.