Animation is the process of changing pictures to create the illusion of movement. We will using it to bring our hero alive, make him walk and breath. It will also be used in our world, moving water, wind blowing but in this case only a camp fire burning.
Coding the logic for sprite animations is complicated, a format for storing the animation data and playing it requires some complicated frame rate logic. Thankfully OpenFL has an open source library Spritesheet, it supplies importers for creating animation data and an AnimatedSprite
display object for playing them.
Game object components
First some refactoring, in our world tiles and entities can both be animated and most likely will need some other functionality as well. Currently all component logic belongs to the Entity
class which although works, it should be spread throughout all child classes so all can benefit from it. Moving component logic to the base class will mean all game objects have the option of having an of the defined components.
Input, move and graphics components are now exposed and the update method will call them each step if defined. Not every game object will make use of every component. You could argue an array of components would be more useful. Add any number of components to a game object and let it run. There are some issues with that approach. First order matters, input must always run first and graphics should always be updated last. How will you gaurentee order? Most importantly components have a public interface, how will you find components to interface with it if they are all in an array? An array of components can work but the biggest benefit right now is keeping this KISS. This will be broken out as needed.
Update loop
The simple update loop we had before is no more. It has been moved into the Map
class and expanded to support updating all game objects in our world. First it runs the update for all tiles on each tile. Now not every tile will recieve an update, only tiles which make use of it. Then last all entities in the game are updated.
This is the start of the update loop. Every game has one of these. The OnEnterFrame
is called every frame update to move the game one tick. In the case start a movement phase if the proper key is pressed, move the hero a little bit and update the graphics component to properly represent the game state. Before any of this happens a delta has to be computed which is from the last frame tick to the current time. This is used to figure out the amount of time that passed since the last tick. Now of course the frame rate of a game is set to 30 but the computer can of course lag and slow down. Which means it has to keep up. This value is currently only used for the new AnimatedSprite
to figure out which frame to show.
Defining animations
The spritesheet library makes it very easy to define animations. It supports many importers for Zoe, Sparrow, Animo and Texture Packer. Here we will be using the basic BitmapImporter. It splits the given bitmapData into a sprite sheet which can then be used in animations.
In the EntityStore
class some behaviors are defined for our hero. A behavior is just a single animation sequence, it needs a name and frames to play. The true
tells the behavior to loop and the frameRate is used to determine the speed of the animation. The speed of the animation for move is much faster than idle.
Graphics component
In the previous tutorials all the graphics handling was done inside the Entity
class because it was quick and dirty. Now it’s time to refactor that out into its own component. This new GraphicsComponent
class is responsible for displaying the correct animation based on the gameObjects state and positioning the sprite where the gameObject is located.
In the update method first thing it does is figure out which animation to show. There is one animation for each direction and state. So if the character decides to move up, the move_up
animation will be shown while moving and then the idle_up
afterwards. A false
is passed in on every animation show to prevent the animation from restarting. Remember the update method is called every frame which means this code will always be restarting the animation before it could show a different frame.
Finally the sprites position is made to match the gameObjects. The last line is the magic where the animated sprite displays the next frame of the animation.
From game logic to game view
You can see in the graphics component it is completely decoupled from the game object. The game object never talks to the graphics component directly, the component has to read the state of the game object every frame and figure out the correct thing to display.This is a great architecture because the game logic should always be decoupled from game view.
So what game logic determines the orientation
of a game object? It can be changed in any of the components but never directly inside the game object. All implmentation is kept inside components because if you put logic inside the game object or entity you are essentially saying all game objects will always be like this. Which is almost never true in a game, even if that was true it would make for a very boring game because games are most fun with different mechanics and the best way to create different mechanics is to push that logic inside a component for easy swapping.
This may surprise you but the InputComponent
is the one to decide the orientation because the input is basically the user saying “I want to go this way.” The MoveComponent
might then go in that direction. It’s also nice to break up game object orientation and movement because who is to say moving down will always make the game object look down. For example an entity can be pushed back which involves a movement but doesn’t change orientation.
Tile animations
Tile animations were tricky. Only some tiles are animated so it doesn’t make sense to add every tile to the update loop. Animated tiles need a special animated sprite to render the animations but regular tiles just need a bitmap to render. Animation data needs to be defined on the same TileData
where regular tiles are define. The best solution is to define all tile animations in one spritesheet and render all tiles in an animated sprite even if it isn’t animated. Of course a graphics component can be created to handle both cases but it didn’t seem necessary.
In the TileSheet
class a spritesheet is created to hold all animations. It can also double as a way of getting frames for non-animated tiles. A new behavior is added to define an animation, the name of the bitmap is used as an easy reference for starting the animation.
The TileLayer
class gets a new array to hold all tiles which need an update every frame. The setTile
method will add or remove the tile from the update array depending on if the tile needs it.
The TileData
class needs a way of holding animation data about the tile. The best way of doing this is to the spritesheet from the Tilesheet
class. The issue now is getting individual frames for non-animated tiles. The spritesheet class hold all animation and bitmap sprites but there is no easy way of getting the bitmapData. The solution was to implement a new setter bitmapData to easily retrieve that data.
In the Tile
class the set tileData method is changed to handle animated tiles. Special care was put in the ensure the tile can be changed any time without its state getting wonky. Right after setting the tileData it will start playing its animation. The Std.string
is a special method for converting anything into a string. It also has methods for converting to int.
Thats the end of the tutorial remember to checkout the source.