Technical highlight: Animations
If you like it, I will periodically post some technical highlights about the inner workings of Super Tilt Bro. It aims to clarify the magic happening when you press a button, to explain some future changes in the engine and to be a nice reading.
In this first highlight, we will see how characters are animated. What is good about it and what are the limitations that will need to be worked on.
Some vocabulary
Player and character are two very important words, but are often interchangeable. When talking about Super Tilt Bro's development, it is useful to have a clear definition for them. So, in this highlight we will use:
A
character is a fantasy being.
Context: The character is Sinbad, a pirate, orc and mascot of the project Ogre3D.
A
player is the human being pressing buttons on the controller.
Context: The winner player mocks his opponent.
An
avatar is the game entity that can be manipulated.
Context: The avatar is described by some variables and shown on screen.Any of them could be called “character”, “player” or both
Animation, how does it work?
Each frame, the engine processes two important steps: it computes avatars' states, then places sprites to show the new state.
Each frame, the engine computes a new state, then place sprites on screen
After the first step, we know the position and animation of each avatar as well as how long this particular animation is played. The only thing left to do is to find the appropriate animation frame, it tells which and where sprites are to be placed on screen. In the code, an animation looks like this:
; Frame 1
ANIM_FRAME_BEGIN(3)
ANIM_HURTBOX($f8, $07, $0a, $0f) ; left, right, top, bottom
ANIM_SPRITE($08, TILE_CHRASHING_SINBAD_1_SIDE, $01, $f0) ; Y, tile, attr, X
ANIM_SPRITE($08, TILE_CHRASHING_SINBAD_1_HEAD, $00, $f8)
ANIM_SPRITE($08, TILE_CHRASHING_SINBAD_1_BODY, $00, $00)
ANIM_SPRITE($08, TILE_CHRASHING_SINBAD_1_SIDE, $41, $08)
ANIM_FRAME_END
; Frame 2
ANIM_FRAME_BEGIN(3)
ANIM_HURTBOX($f8, $07, $06, $0d)
ANIM_SPRITE_FOREGROUND($08, TILE_CHRASHING_SINBAD_2_SIDE, $01, $f3) ; Y, tile, attr, X
ANIM_SPRITE_FOREGROUND($08, TILE_CHRASHING_SINBAD_2_MIDDLE, $01, $fc)
ANIM_SPRITE_FOREGROUND($08, TILE_CHRASHING_SINBAD_2_SIDE, $41, $05)
ANIM_SPRITE($06, TILE_CRASHED_SINBAD_HEAD, $00, $f8)
ANIM_SPRITE($06, TILE_CRASHED_SINBAD_BODY, $00, $00)
ANIM_FRAME_END
; Frame 3
ANIM_FRAME_BEGIN(13)
ANIM_HURTBOX($f8, $07, $08, $0f)
ANIM_SPRITE($08, TILE_CRASHED_SINBAD_HEAD, $00, $f8) ; Y, tile, attr, X
ANIM_SPRITE($08, TILE_CRASHED_SINBAD_BODY, $00, $00)
ANIM_FRAME_END
; End of animation
ANIM_ANIMATION_END
As we can see, hitboxes and hurtboxes are described with the animation. We also discriminate between standard sprites and foreground sprites, allowing FXs to always be shown on top of the avatar. Indeed, in Super Tilt Bro., when an avatar is flipped sprites' priority is inverted, actually using animation data as a 3D model. This technique allows notably the character to keep being left-handed, regardless of its watching direction. Impact effects shall nonetheless unconditionally be in foreground.
Layers of sprites, that's basic 3D
No depth for SPRITE_FOREGROUND, the impact is always on top of the avatar
What works well
The format used to store animations allows free sprites placing. There is no artificial limitation over what the NES can do. Moreover, we can describe some depth.
Hitboxes in animation data are easy to work with. We cannot forget to modify hitboxes when we modify data and generally are safe from bugs where hitboxes are out of sync with displayed animation.
What can be improved
The animation engine directly uses avatars' states. It is unusable in another game project or, worst, in the menus. It would be better if animated sprites had their own state, the game engine would just have to maintain such states for each avatar and let the animation engine work from there.
Animation is now the number one CPU consumer in this game. Each game frame, the animation engine must find the good animation frame and search it implies to parse all previous animation frames. If we had a state for animated sprites, it could be fixed by storing a pointer to the current frame.
Handling of 3D implies to fix the number of sprites used by the animation to the biggest frame it could show. The simplest way to do this is to reserve lots of sprites for each animation, forbidding to have many avatars on screen.
The end
It was the first technical highlight, certainly far from perfect, so please tell me what you'd love to see and help shape nexts. Do you prefer something shorter, or more in depth? More pictures maybe? Something completely different? Tell me what you want and I will adapt the format.