An animation study

I decided to do a small study to gather information on the subject of animation in 2d games. This thread is aimed to people with average knowledge of programming that basicly just want to have fun doing 2d games with a certain complexity without being bogged by programming details. It’s not a geek exercise and It’s not a basic, simplistic (and most time useless) solution so it requires some atention.

Most of what i remenber from animations comes from the previous projects (indie and retro stuff) i have been and the little knowledge i have gathered moding games and working with tools like 3dstudio and blender.

First the vocabulary used in this thread:

animation - this is usualy seen as a time function that will animate same property of an object (an object is also called an entity in this context).

animation channel - an animation applied to a certain property of an entity like an image, sound, movement, image scale, etc…

entity - this is usualy a movable object with a set of properties that can or cannot be animated like position, color, visibility, image, etc

ipo curve - this is a blender term to mean a animation curve or time/prop graphic to represent an animation that repeats itself, from 0 to maxkey (a time value).

ipo key - this is a value between 0 and maxkey for which there is a value defined for a certain property.

An animation is something like this, in the example i’m animating several properties of a life-form entity on the surface of a planet. This includes a small shacking, sounds and images syncronized together.

The I legend in the graph means a place where an image key was defined. Other values are constantly interpolated.

WalkImage5|----------------------------------------------------------I******* WalkImage4|-------------------------------------------I**************-------- WalkImage3|-------------------I***********************----------------------- WalkImage2|------I************----------------------------------------------- WalkImage1|I*****------------------------------------------------------------ ----------|0ms---------------------------|100ms------------------------------|200ms

We need to translate this graphic into something we can use in a program. We need a table of key/value pairs and to associate with this table the naimation name, the maximum value for a key, what type of property value refers to (an image), and what is the interpolation type (constant interpolation as you can see from the graph, straight lines until next key). Making key values in milliseconds makes the game independent of CPU speed.

I guessed the key values to be close to the ones in the graph.

Name=walking Value=image Interpolation=constant MaxKey=200ms Key-----+Value------ 0ms-----|WalkImage1 20ms----|WalkImage2 80ms----|WalkImage3 120ms---|WalkImage4 180ms---|WalkImage5 --------+----------

Another channel for foostep sounds. The legend S means a sound alias name key:

FootStep2-|------S------------------------------------S---------------------- FootStrong|-------------------S---------------------------------------------- FootStep1-|S---------------------------------------------------------S------- ----------|0ms---------------------------|100ms------------------------------|200ms

And table:

Name=walking Value=sound Interpolation=none MaxKey=200ms Key-----+Value------ 0ms-----|FootStep1 20ms----|FootStep2 80ms----|FootStrong 120ms---|FootStep2 180ms---|FootStep1 --------+----------

It’s a strange way of walking all-right, but its a lively one that avoids monotony in animation. Animations which doesn’t have an associated soundtrack and have frames all with the same duration add to boredoom.

I will add a certain unbalance subtle effect to the middle of the longest step by translating the image a litle then back to the origin. The value of this animation is the offset distance for the current posistion of the animation. R means a relative translation on y axis:

1---------|-------------------R******---------------------------------------- 0---------|R******************-------R***-----R****************************** -1--------|------------------------------R****------------------------------- ----------|0ms---------------------------|100ms------------------------------|200ms

Name=walking Value=y-relative-translation Interpolation=constant MaxKey=200ms Key-----+Value------ 0ms-----|0px 80ms----|1px 90ms----|0px 110ms---|-1px 120ms---|0px --------+----------

An animation function is infinite (time is not a limit) so the ipo only represents the start of the animation so the rest is repeated an infinite number of times.

There are several ways to represent the way a animation function repeats:

loop -> when the animation reaches the end of the ipo curve repeat from the beginning.

pingpong (sometimes mentioned as loopback) -> same as loop except that when it reaches the end it doesn’t jump to the beginning but repeats backwards until it reaches the beginning and the repeats forward again and so on.

once -> like it says, if time goes beyond the max key value it uses the last value for the rest of the animation function

once_at_every_keyframe -> is only activated once at every keyframe, good for sounds that continue to play by the hardware without the game intervention

once with retrocess -> like once but when reaches the end do the animation backwards and then stop

Now let’s talk a bit about getting values from an animation table. Interpolation is used to compute time values that lie between the key values.

Example: we want to know what would be the image to select for an animation. 500ms have passed since the animation was started. I’ts not hard. it only takes some basic calculations.

To calculate this value using the animation function (image = animation(“walking”, “image”, time passed) ) we need to know:

  • the time (taken from the system clock) when the animation has started
  • the current time from system clock
  • the animation table that associates keys to values (ipo)
  • the method used to repeat the animation (loop, ping-pong, once, etc)

To compute the animation value to use follow these steps:

1- find the animation delta = currenttime - animstarttime
2- next step depends on the repetition method
2.1- for repeat once, if delta > ipo maxkey do nothing, animation has stoped, return the last animation value
2.2- for loop, find t = delta / maxkey
2.3- for ping-pong, find t = delta / maxkey and if int(delta) is odd do an extra step of t = makkey - t
3- now use t to interpolate the value (constant interpolation)
3.1- for this we need to search the key table for the walking animation and images
3.2- we do a linear search on the table and find a key k1 such that k1 <= t < k2 and k2 comes next after k1 (we find the interval where t is in) then use the value of k1 for the value of t (the image to use)

Thats it for obtaining the value of the animation function.

An animation package will solve some problems usualy associated with loading and managing resources besides providing the usual animation services:

  • a way to store resources in memory in a centralized way. That is one single copy for each resource, several links to it.
  • a way to store a animation functions and use them.
  • a way to compute animation values.
  • a data file format to define resource alias names to store animation tables themselves (hardcoded animation data is hugly).

With this we can code a game loop that will compute the animations for the each entity and redraw the scene. The most flexible way for us to do this is to use an animation just like a math function:


// Initialize animation
animSystem.setStartTime(sometimevalue);
int WALKING_ANIM = animSystem.getAnimationId("walking");
int IMAGE_PROP = animSystem.getPropertyId("sound");
int SOUND_PROP = animSystem.getPropertyId("image");
// relative translation on x axis
int RTX_PROP = animSystem.getPropertyId("rtx");
...

// Inside the game loop

// Get one animation
animSystem.updateCurrentTime(currenttime);
int imgid = animSystem.GetAnimValue(WALKING_ANIM, IMAGE_PROP);
int sndid = animSystem.GetAnimValue(WALKING_ANIM, SOUND_PROP);
float trx = animSystem.GetAnimValue(WALKING_ANIM, RTX_PROP);

// Get the resources from the anim system
MyImageClass image = (MyImageClass)animSystem.getResource(imgid);
MySoundClass sound = (MySoundClass)animSystem.getResource(sndid);

We do the drawing ourselves in each frame and get the position values from each entity AI. We disturb our position x value with trx, like this (x += trx) for the effect.

When we use an AI framework, we give the AI the input of whats happening in the world from the perspective and sensorial capabilities of some AI, then query the AI for state changes (reactions) of that entity and get whatever animations we should use from the entity. If we should move or keep the same position, if we should fire, etc.

But the fundamental problem remains: how to handle animations inside the game loop.

There are some fundamental issues to be solved here. Here’s some hints on how to solve them:

  • the game loop needs to keep track of current frame time and the last frame time
  • the game loop will track several entities at once, query their states, and decide if they should move, fire or whatever. Each entity should have complete information that should be needed to do an animation step (entities have their animation data inside them).
  • alternatively we can have an HashMap<Entity,EntityInfo>. We can associate extra information to each entity that will be used only in the context of a game cycle.
  • in a multiple scenario entities will collide with each other, with the environment and with static objects that are marked as colidable. To solve this problem the game should have a collision simulation algorithm (maybe a CollisionHandler class).
  • To avoid the collision simulation algorithm to be called so many times each entity AI can have some sort of path finding behavior, with a vison system and perhaps a messaging system to dialog with nearby entities and negotiate how to avoid collision.
  • its important to have game logic loops separated from rendering loops.

For more info see this site with great info:

fivedots.coe.psu.ac.th/~ad/jg/

The chapter that interests for this subject:

fivedots.coe.psu.ac.th/~a…index.html

There are some tricks that are more important for gameplay and for visual effect than for anything else. The animation system does not need to be perfect and sometimes it’s better not to be. It’s not pretty when the game keeps switching animations so fast that the game appears to be out of control. The visual beauty of animations is lost and a the player gets a feeling of not having enough control of his avatar.

This is more serious when there are multiple foes attacking the player at the some time. Remenber Diablo in those situations where you become so much bogged with enemies that you couldn’t do anything. Controls become almost non-responsive with several enemies hiting the player at the same time. When the player actualy got an oportunity to cast a spell or hit back the game would perform an attack animation despite the player being constantly hit from many sides and damage being subtracted from player stats.

In the case of multiple hits it’s usualy good to wait for the “being hit” animation to end than to start a new “being hit” animation every time the player gets hit. If the later strategy were used the player would not see a cool animation of “being” hit with a recognizable “haaa damn it” sound but instead the player avatar would look like he was doing some weird caotic break dance while constantly yelling hak hak hak uk hik hak.

When several animations are scheduled to be played at the same time we can:

  • Allways play an animation, combined animation or overlaped animation for at least 200ms second, postpone all others.
  • make animations that are suposed to represent the combination effect of several other animations.
  • overlap animations using an alpha blending over the player entity.
  • simple choose the most important animation and play it to the end, ignoring all others until this one plays.
  • postpone playing an animation for a later time.
  • setup priorities for animations that should not be interrupted by lower priority animations.

This has nothing to do with AI or animations. It’s about gameplay. Sometimes we need to do some cheating with our own rules but its for the fun of the player. These policies are handled by the game logic and are outside AI so we may require the AI to be tweaked or adjusted afterwards.

Heres a example for a set of rules to handle Diablo gameplay, sort of :wink: :

  • Standing animation has lower priority than all others.
  • Allways play a normal hit animation to the end, if other normal hit animations are requested.
  • Special hiting animations like lightening, fire attack and cold attack have higher priority than normal hitting animations.
  • Special hiting animations overlap each other with alpha blending.
  • Attack animations can only be interrupted by knockback effects if damage is high above a certain threshold.
  • In case the player collides during knockback the animation should revert to standing. Nothing else except dead can interrupt knockback.
  • Use a simple red color alpha blend to indicate that the player was hit. This effect can be overlaped with the player avatar image (using an alpha mask) for any other animation.

Just to give some examples of rules.

Some notes on the format used to store animations. I will consider a xml format.

The xml format tries to satisfy the two two purposes i described above for an animation system. One is to store animation data and another is to associate resource name alias to resource real names and to ensure there is only one copy of them in memory at any time.

Another important advantage of storing animation data in xml is that the game data becomes independent of the engine.

Heres one way to make a xml animation file for the walking animation:


<?xml version="1.0" encoding="UTF-8"?>

<animations>

<defs import="another xml file with alias names">
<alias type="image" id="walkimage" indexes="1,10" ext="png" location="data/img/a" />
<alias type="sound" id="footstep" indexes="1,2" ext="wav" location="data/img/s" />
<alias type="sound" id="footstrong" ext="wav" location="data/img/s" />
</defs>

<globals />

<animation id="walking" maxkey="200" timeunit="ms" import="tracks to import from another xml file">

<ipo proptype="image" loop="pingpong" interpolation="constant">
<key time="0" value="walkimage" index="1" />
<key time="20" value="walkimage" index="2" />
<key time="80" value="walkimage" index="3" />
<key time="120" value="walkimage" index="4" />
<key time="180" value="walkimage" index="5" />
</ipo>

<ipo proptype="sound" loop="once_at_every_keyframe" interpolation="none">
<key time="0" value="footstep1" />
<key time="20" value="footstep2" />
<key time="80" value="footstrong" />
<key time="120" value="footstep2" />
<key time="180" value="footstep1" />
</ipo>

<ipo proptype="trx" loop="pingpong" interpolation="constant" valueunit="pixels">
<key time="0" value="0" />
<key time="80" value="1" />
<key time="90" value="0" />
<key time="110" value="-1" />
<key time="120" value="0" />
</ipo>

</animation>

</animations>

------*------

This isn’t in any way an expert study on animation. It’s just my own study on the subject. Any corrections or remarks that can bring more light on the subject of making a good animation system are welcome.