Pushbutton Engine – Animating Bitmap Sprites

In this Pushbutton Engine instalment we’re going to be looking at how you can add animations to your player using sprite sheets, although this can apply equally to non-player characters (NPCs). For now we’re only going to be looking at Bitmap based animations – SWF based animations I’ll save for another time.

If you’re not too sure what a sprite sheet is they are a single image that contains all of the images used to animate the character on screen. Take the examples below of a simple UFO sprite sheet:

Simple UFO sprite sheet

As you can see it consists of 25 images in sequence and if you let your eye play from left to right, from top left to bottom right, you can see what the basic concept is – the UFO sweeps from side to side. Pretty simple really. This isn’t the most efficient way of creating a sprite sheet but it’s to illustrate a point so it’ll do for now.

With a sprite sheet created you just need to animate it based on the ‘cells’ of the sheet. To do this your game engine needs to divide up the sprite sheet by the number of items it contains and at the relevant time interval copy and draw the new ‘cell’ to the display, replacing the old image. This in turn provides the animation – exactly like those old flicker books we all created to prevent boredom in Science or Math. Simple really :p. Below is that UFO sprite sheet animating so you can see the process in action:

[kml_flashembed publishmethod=”dynamic” fversion=”10.0.0″ replaceId=”simpleUFODemo” movie=”http://blog.flashgen.com/swfs/simpleUfoDemo.swf” width=”100″ height=”100″ targetclass=”flashmovie”]

Get Adobe Flash player

[/kml_flashembed]

Not all sprite sheets are uniform in their dimensions. Let’s add a few more terms to the mix to avoid confusion as we progress. Sprite sheets are usually used to provide animation ‘cells’ for individual assets – this isn’t always the case but the majority of the time this rings true. The sprite engine then divides these sheets by the amount of items in each row and how many columns it contains. So taking the UFO sprite sheet above you have 5 rows and 5 columns

Another form of graphic ‘sheet’ is a Texture Atlas (sometimes referred to as a tile sheet). Texture atlases are generally used to map an image (texture) to a 3D model, therefore they can contain non-uniform content that is ‘packed in to a single atlas using the minimum spare as efficiently as possible. These usually have some form of descriptor file that provides the co-ordinates of each piece for ease of reference. Texture Atlases are also used within 2D games but more as an approach to optimisation due to hardware restrictions – memory management due to limited free memory is a common reason.

In PBEs case sprite sheets fall in to the uniform dimension category, i.e. each element within the sprite sheet has exactly the same dimensions.

Sounds Like A Lot Of Work?
If you were going to start from scratch building a sprite sheet animator it would be a chunk of time to get it up and running. However this article isn’t about writing your own sprite engines it about leveraging Pushbutton Engine to do this for you. Luckily this type of functionality is pretty simple and like most things in PBE, it can be achieved in a few different ways. I’m not going to cover all of these off as it’s better to start with the principle, understand the theory and put that in to practice in my opinion. Hopefully this article will get you some way there in that process.

So, back to animating your sprite sheet. The first thing to do is create a new Flash Builder ActionScript project (as I’m using the Flex 4.1 SDK for this project), although Flex Builder 3 will work equally well. You’ll need to create a new Pushbutton scene and the basic building blocks of your ‘Hero’ object. If you’ve been following the series along you should be able to use teh example project from the previous article, if not, you can grab the example project for this one at the end of this article. Either way here is what your code should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package
{
	import com.flashgen.gaming.resources.SpriteResources;
	import com.pblabs.engine.PBE;
	import com.pblabs.engine.entity.IEntity;
	import com.pblabs.engine.entity.PropertyReference;
	import com.pblabs.engine.entity.allocateEntity;
	import com.pblabs.rendering2D.SimpleSpatialComponent;
 
	import com.pblabs.rendering2D.ui.SceneView;
 
	import flash.display.Sprite;
	import flash.geom.Point;
 
	[SWF(width="800", height="600", frameRate="30", backgroundColor="0xCCCCCC")]
	public class Main extends Sprite
	{
		private var _resources		:SpriteResources;
 
		public function Main()
		{
			PBE.startup(this);
 
			_resources = new SpriteResources();
 
			PBE.addResources(_resources);
 
			initializeScene();
			initializeHero();
			initializeBackground()
 
		}
 
		private function initializeBackground():void
		{
			var _bg		:IEntity = allocateEntity();
 
			var _spatial		:SimpleSpatialComponent = new SimpleSpatialComponent();
			_spatial.position = new Point(0, 0);
 
			_bg.addComponent(_spatial, "Spatial");
 
			var _renderer	:SpriteRenderer = new SpriteRenderer();
			_renderer.fileName = "../../../../assets/sprites/starfield.png";
			_renderer.layerIndex = 1;
			_renderer.scene = PBE.scene;
 
			_renderer.positionProperty = new PropertyReference("@Spatial.position");
 
			_bg.addComponent(_renderer, "Renderer");
 
			_bg.initialize();
		}
 
 
		private function initializeHero():void
		{
			var _hero	:IEntity = allocateEntity();
 
 
			// Add dimensions to our hero
			var _spatial	:SimpleSpatialComponent = new SimpleSpatialComponent();
			_spatial.size = new Point(50, 50);
			_spatial.position = new Point(0, 0);
 
			_hero.addComponent(_spatial, "Spatial");
 
			_hero.initialize();
		}
 
 
		private function initializeScene():void
		{
			var _sceneView	:SceneView = new SceneView();
			_sceneView.width = 800;
			_sceneView.height = 600;
 
			_sceneView.name = "MainView";
 
			PBE.initializeScene(_sceneView);
		}
	}
}


Before we get stuck in to code, let’s just recap what is present. Firstly there is the constructor and within this there are calls to the initializeScene(), initializeHero() and initializeBackground() methods. You don’t need to worry about the first and last method calls as they are here as boilerplate code for the project. We’ll be mainly concentrating our efforts within the initlizeHero() method for this article.

You may also have noticed a reference to something called SpriteResources(). I’ll be going in to more detail as to what that is shortly but let’s talk about your Hero briefly. If you review the initializeHero() method you can see that your hero has dimensions but no form. In the previous articles you’d have implemented a SimpleShapeRenderer or SpriteRenderer. However, today we’re going to use the SpriteSheetRenderer() class instead as this allows us to define and interact with, yup, you guessed it, a sprite sheet. All flippant comments aside this isn’t a big change from using the SpriteRenderer but it does provide more functionality as you progress with your games development using PBE.

Declaring Sprite Sheets
The first thing you need to do is create an instance of the SpriteSheetRenderer(). This is identical to the creation of any asset renderer in PBE as the code below shows:

1
2
3
4
5
6
7
8
9
// Render our hero
var _renderer		:SpriteSheetRenderer = new SpriteSheetRenderer();
_renderer.positionProperty = new PropertyReference("@Spatial.position");
_renderer.spriteSheet = _idleSpriteSheet;
_renderer.spriteIndex = 0;
_renderer.layerIndex = 10;
_renderer.scene = PBE.scene;
 
_hero.addComponent(_renderer, "Renderer");

As you can see once you have defined the SpriteSheetRenderer() instance you need to provide it with a few key pieces of information (I’ve included a couple of the defaults for explanatory purposes only). The two properties I do want to draw your attention to are:

1
2
_renderer.spriteSheet = _idleSpriteSheet;
_renderer.spriteIndex = 0;


The spriteIndex property allows you to indicate which image within your sprite sheet you want displayed as the initial image, the default value for this is 0 (zero). The spriteSheet property holds a reference to a SpriteSheetComponent() – in this case one called _idleSpriteSheet so let’s look at the _idleSpriteSheet instance to see what it contains.

1
2
var _idleSpriteSheet	:SpriteSheetComponent = new SpriteSheetComponent();
_idleSpriteSheet.imageFilename = "../../../../assets/spritesheets/ufoIdle.png";


As you can see it only needs one property (imageFileLocation) to be set with the location (as a String) of the sprite sheet image it should use. So far so good. YOu might have spotted that while you have assigned the sprite sheet to the SpriteSheetRenderer() at no point did you tell it how many rows or columns it contains. To do this you need to declare an instance of CellCountDivider(), which allows you to declare the rows (yCount) and columns (xCount) of your sprite sheet. You then assign it to the divider property of your SpriteSheetRenderer().

1
2
3
4
5
6
var _idleDivider	:CellCountDivider = new CellCountDivider();
_idleDivider.xCount = 5;
_idleDivider.yCount = 5;
 
// Assign it to our SpriteSheetRenderer instance;
_idleSpriteSheet.divider = _idleDivider;


However there is an underlying problem (beyond my long relative paths). If you were to test this now you’d see that the background (starfield.png) isn’t displayed, nor is the UFO* – in fact you’ll likely be looking at a white browser window. The reason for this is that the assets didn’t load in time to be rendered – This isn’t a fault with PBE it’s that I haven’t provided any loaders, and I’m not going to either as there is an alternative approach – embedding your resources.

Now only you can make the value call of whether to embed or load assets, but if the file size isn’t excessive I’d advocate embedding. Purely to avoid loading errors or network disconnects that could leave your game in limbo.

*Your UFO isn’t displaying because it is still missing a few bits so don’t panic. You’ll be adding those in a bit.

Working With Resource Bundles
You will likely recall my brief reference to SpriteResource() located in the constructor. This is a custom class that extends the Pushbutton Engine class ResourceBundle() and allows you to marshall all of your assets that need embedding in to a single class that will register them once PBE is initialized. Resource bundle classes are fairly simple in their structure and I’ll be talking more about the types of assets that can be embedded within them in a future article, but for the moment you only need to think about adding graphical assets as resources.

Declaring Your Resource Bundle(s)
Below is the initial information stored in the SpriteResource() class. You can see that the background (starfield.png) has already been added to the bundle. Notice that you use the [Embed] metadata tag to tell the compiler that this asset needs to be merged in to the SWF and not referenced externally.

1
2
3
4
5
6
7
package com.flashgen.gaming.resources
{
	import com.pblabs.engine.resource.ResourceBundle;
 
	[Embed(source="../../../../assets/sprites/starfield.png")]
	public var fg_starfield	:Class;
}


If you’ve not used asset embedding before, the process is quite straight forward. First you declare a variable of type Class() – the variable name in this case can be anything you wish, (I’ve called mine fg_starfield), as long as it follows standard naming conventions – next you place an [Embed] metadata tag above it and define your assets location as its source attribute. In the code above it’s: assets/sprites/starfield.png (obviously if you have placed your assets in a different folder structure you’ll need to use that as the value of the source attribute.

Initializing Resources
Now that you have your resource bundle you need to instantiate it within your game. To do this you just need to declare a new instance of it after you have invoked the PBE.startUp() method. Now you can instantiate it in one of two ways. Either just by declaring new SpriteResources(); or by assigning it to a variable which in turn is passed in to the main PBE instance via the PBE.addResources(). I’ve prefer the latter (as shown below) as it is clear what is happening, but it’s up to you which you use.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private var _resources		:SpriteResources;
 
public function Main()
{
	PBE.startup(this);
 
	_resources = new SpriteResources();
 
	PBE.addResources(_resources);
 
	initializeScene();
	initializeHero();
	initializeBackground()
}

It Doesn’t Work
One thing you need to make sure is included when using resource bundles is that the compiler arguments are updated to include the following; note the += (plus equals), if you just include = (equals) it will override the built in metadata and replace it with only these – which could cause issues:

1

-keep-as3-metadata+=TypeHint,EditorData,Embed

If you’re not sure how to update the compiler arguments in Flash Builder just follow these steps:

  • Right click on your project and choose properties from the bottom of the context menu
  • When the Properties dialog opens select ActionScript Compiler from the tree on the hand side
  • In the Additional Compiler Arguments text field add -keep-as3-metadata+=TypeHint,EditorData,Embed
  • Apply and close the dialog

Make sure you clean the project before testing again. If it worked you should only have a SWF (and maybe a few empty folders) in your bin-debug folder. When you test it this time the background should appear.

Animating Your Sprites
Now you’ve embedded your assets you can finally set about animating your ‘Hero’. To do this you’re going to need two more classes that actually deal with this: AnimationController()and AnimationControllerInfo().

AnimationController() as the name implies, deals with controlling all of the animations that may be applied to the target entity. It does this by storing references to each animation through a reference to its associated AnimationControllerInfo() instance, which in turn stores a reference to the SpriteSheetComponent() and CellCountDivider() instances used by it. This may sound complicated, but it isn’t – it’s just easier to show an example than explain it in a single paragraph.

Here is the AnimationController() instance with the basic properties assigned values. AS you can see it only requires a few bits of information when initialized:

1
2
3
4
5
6

var _animationController	:AnimationController = new AnimationController();
_animationController.spriteSheetReference = new PropertyReference("@Renderer.spriteSheet"); 
_animationController.currentFrameReference = new PropertyReference("@Renderer.spriteIndex");
_animationController.defaultAnimation = "idle";
 
_hero.addComponent(_animationController, "Animator");

As with most components you can pass references in to it from other components to update them as other property values are changed. In this case I’ve passed in a values for the spriteSheetReference and currentFrameReference that both refer back to the actual SpriteSheetRenderer() that was declared as the actual renderer for the ‘Hero’. The last property to get set is the defaultAnimation. This allows you to define which of your animations run when the AnimationController() is ‘ticking over’. The default value for this is idle so you can leave this out, but I included it just to show you that it was accessible should you wish to use it.

With the actual AnimationController() in place you now need to give it some information about the animation you want it to run. In this case you’re only dealing with the default animation, but it could be a death animation, or one related to user input (We’ll be looking at that in the next article). For now though let’s look at providing the information needed to animate that UFO when it is just sitting in space – this is where the AnimationControllerInfo() class comes in.

The AnimationControllerInfo() class deals with the settings for a specific animation. This is in turn passed in to the AnimationController() by name reference – in this case it is using the default name of idle. You only need to set the spriteSheet property on the your AnimationControllerInfo() instance. However I’ve tweaked a couple of the more commonly used properties as well in the code below.

1
2
3
4
5
6

var _idleAnimation	:AnimationControllerInfo = new AnimationControllerInfo();
_idleAnimation.frameRate = 5;
_idleAnimation.loop = true;
_idleAnimation.spriteSheet = _idleSpriteSheet;
 
_animationController.animations["idle"] = _idleAnimation;

The frameRate property allows you to increase or decrease the speed your animation plays. Obviously external factors can compromise this but in general it operates as expected. The loop property allows you to set the animation to continually loop or only play through once, by default this is set to true. Once you have set the required properties on your AnimationControllerInfo() instance you then need to pass it to the AnimationController()’s animations property, which is an array that is indexed by name values not numeric indices. After all of that your UFO should now be animating nicely in the middle of the screen just like the one below:

[kml_flashembed publishmethod=”dynamic” fversion=”10.0.0″ replaceId=”animated_ufo” movie=”http://blog.flashgen.com/swfs/animatedUfoDemo.swf” width=”200″ height=”200″ targetclass=”flashmovie”]

Get Adobe Flash player

[/kml_flashembed]

For the code hungry amongst you here is the complete code for this article (you can also download the source files from the Related Files section below).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package
{
	import com.flashgen.gaming.resources.SpriteResources;
	import com.pblabs.engine.PBE;
	import com.pblabs.engine.entity.IEntity;
	import com.pblabs.engine.entity.PropertyReference;
	import com.pblabs.engine.entity.allocateEntity;
	import com.pblabs.rendering2D.AnimationController;
	import com.pblabs.rendering2D.AnimationControllerInfo;
	import com.pblabs.rendering2D.SimpleSpatialComponent;
	import com.pblabs.rendering2D.SpriteRenderer;
	import com.pblabs.rendering2D.SpriteSheetRenderer;
	import com.pblabs.rendering2D.spritesheet.CellCountDivider;
	import com.pblabs.rendering2D.spritesheet.SpriteSheetComponent;
	import com.pblabs.rendering2D.ui.SceneView;
 
	import flash.display.Sprite;
	import flash.geom.Point;
 
	[SWF(width="200", height="200", frameRate="30", backgroundColor="0xCCCCCC")]
	public class Main extends Sprite
	{
		private var _resources		:SpriteResources;
 
		public function Main()
		{
			PBE.startup(this);
 
			_resources = new SpriteResources();
 
			PBE.addResources(_resources);
 
			initializeScene();
			initializeHero();
			initializeBackground()
 
		}
 
		private function initializeBackground():void
		{
			var _bg		:IEntity = allocateEntity();
 
			var _spatial		:SimpleSpatialComponent = new SimpleSpatialComponent();
			_spatial.position = new Point(0, 0);
 
			_bg.addComponent(_spatial, "Spatial");
 
			var _renderer	:SpriteRenderer = new SpriteRenderer();
			_renderer.fileName = "../../../../assets/sprites/starfield.png";
			_renderer.layerIndex = 1;
			_renderer.scene = PBE.scene;
 
			_renderer.positionProperty = new PropertyReference("@Spatial.position");
 
			_bg.addComponent(_renderer, "Renderer");
 
			_bg.initialize();
		}
 
 
		private function initializeHero():void
		{
			var _hero	:IEntity = allocateEntity();
 
 
			// Add dimensions to our hero
			var _spatial	:SimpleSpatialComponent = new SimpleSpatialComponent();
			_spatial.size = new Point(50, 50);
			_spatial.position = new Point(0, 0);
 
			_hero.addComponent(_spatial, "Spatial");
 
 
			// Render our hero
			var _renderer		:SpriteSheetRenderer = new SpriteSheetRenderer();
			_renderer.positionProperty = new PropertyReference("@Spatial.position");
			_renderer.spriteSheet = _idleSpriteSheet;
			_renderer.spriteIndex = 0;
			_renderer.layerIndex = 10;
			_renderer.scene = PBE.scene;
 
			_hero.addComponent(_renderer, "Renderer");
 
			// Add animation controller and animations
			var _animationController	:AnimationController = new AnimationController();
			_animationController.spriteSheetReference = new PropertyReference("@Renderer.spriteSheet"); 
			_animationController.currentFrameReference = new PropertyReference("@Renderer.spriteIndex");
			_animationController.defaultAnimation = "idle";
 
			// Create Idle Animation
			var _idleSpriteSheet	:SpriteSheetComponent= new SpriteSheetComponent();
			_idleSpriteSheet.imageFilename = "../../../../assets/spritesheets/ufoIdle.png";
 
			var _idleDivider	:CellCountDivider = new CellCountDivider();
			_idleDivider.xCount = 5;
			_idleDivider.yCount = 5;
			_idleSpriteSheet.divider = _idleDivider;
 
			var _idleAnimation	:AnimationControllerInfo = new AnimationControllerInfo();
			_idleAnimation.frameRate = 5;
			_idleAnimation.loop = true;
			_idleAnimation.spriteSheet = _idleSpriteSheet;
			_animationController.animations["idle"] = _idleAnimation;
 
			_hero.addComponent(_animationController, "Animator");
			_hero.initialize();
		}
 
		private function initializeScene():void
		{
			var _sceneView	:SceneView = new SceneView();
			_sceneView.width = 200;
			_sceneView.height = 200;
 
			_sceneView.name = "MainView";
 
			PBE.initializeScene(_sceneView);
		}
	}
}


So where do you go from here? Well, once your animations have been registered with the AnimationController() instance you can switch between them by dispatching an associated event, but I’ll save that for the next article where we’ll be looking at animations that are triggered by user input.

Summary
In this article you’ve seen how to add animations based off of sprite sheets, the core classes that deal with them and the process by which you assign an animation to the AnimationController(). You’ve also seen how to embed resources in to your game through the use of the ResourceBundle() class and registering them when your game initializes.

Related Files
PBE004_AnimatingBitmapSprites.zip

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *