From The Mana World
Line 117: Line 117:
class Animation
class Animation
{
{
int nframes;
    int nframes;
Img **frames;
    Img **frames;
int *times;
    int *times;
int totalTime;
    int totalTime;
int start;
    int start;
   
public:
public:
Animation ( int n, Img **i, int *t )
    Animation ( int n, Img **i, int *t )
{
    {
int c;
        int c;
   
nframes = n;
        nframes = n;
frames = new Img* [n];
        frames = new Img* [n];
times = new int [n];
        times = new int [n];
totalTime = 0;
        totalTime = 0;
       
for ( c = 0; c < n; c ++ ) {
        for ( c = 0; c < n; c ++ ) {
frames[c] = i[c];
            frames[c] = i[c];
times[c] = t[c];
            times[c] = t[c];
totalTime += t[c];
            totalTime += t[c];
}
        }
}
    }
   
inline void start ( int t )
    inline void start ( int t )
{
    {
start = t;
        start = t;
}
    }
 
   
Img *getFrame ( int t )
    Img *getFrame ( int t )
{
    {
int i;
        int i;
int it
        int it
       
if ( ! totalTime ) {
        if ( ! totalTime ) {
t = 0;
            t = 0;
} else {
        } else {
t = (t-start) % totalTime;
            t = (t-start) % totalTime;
}
        }
           
// searches the current frame
        // searches the current frame
for ( i = 0, it = 0; (it + times[i]) < t; i ++, it += times[i] );
        for ( i = 0, it = 0; (it + times[i]) < t; i ++, it += times[i] );
       
return frames[i];
        return frames[i];
}
    }
};
};
Line 176: Line 176:


while ( 1 ) {
while ( 1 ) {
draw ( a.getFrame(clock()) );
    draw ( a.getFrame(clock()) );
}
}
</pre>
</pre>


An advantage of this class is that the animation speed is indipendent to FPS and it's very easy to use.
An advantage of this class is that the animation speed is indipendent to FPS and it's very easy to use.

Revision as of 15:10, 18 March 2006

This article is currently only a proposal

The features or design guidelines described in this article are only a proposal made by one or some persons. It has not been evaluated or accepted by the core development team yet. Feel free to add your personal opinion about them or make counter proposals.

After a mini-meeting between me and Doener we refined the model. Each animation will be defined by an xml document, here follows an example:

<?xml version="1.0"?>
<animation name="player">
	<spritemap name="misc" src="player-male-misc.png" width="64" height="64" />
	<spritemap name="stab" src="player-male-stab.png" width="64" height="64" />
	<spritemap name="bow" src="player-male-bow.png" width="64" height="64" />
 
	<action name="stand">
		<frame spritemap="misc" index="0" delay="0" />
	</action>
 
	<action name="walk">
		<frame spritemap="misc" index="0" delay="20" />
		<frame spritemap="misc" index="1" delay="20" />
	</action>
 
	<action name="bow-attack">
		<sequence spritemap="bow" start="0" end="5" delay="20" />
	</action>
</animation>

So if you want to load the playerset you just load player.xml and it takes care of loading all related images. Of course delays are defined in milliseconds.

Some attempt by Bjørn

A sprite is an object which can carry several animations, hence I call the root element the sprite. Also, I think we should seperately define animations in several directions. For now this example is based on the current playerset image, but by using multiple imagesets this could of course be split up.

Each frame and frames element can optionally have attributes x and y to specify an offset from the default drawing position for that frame. This will allow the animation of for example the hairset (or any equipment) to reuse the same frames with different offsets, and be defined exactly like below.

In the bow animation I've chosen a shortcut to specify different delays for different frames. I am undecided yet whether this is a nice feature or whether it'll be better to require multiple frame elements in such cases.

For the dead frame I have specified no direction attribute, which makes this animation independent of direction. Also, it includes an experimental suggestion on specifying that a random frame should be chosen.

<sprite name="player">
 <imageset name="base" src="player-male-base.png" width="64" height="64" />

 <animation name="stand" direction="down">
  <frame imageset="base" index="0" />
 </animation>
 <animation name="stand" direction="left">
  <frame imageset="base" index="18" />
 </animation>
 <animation name="stand" direction="up">
  <frame imageset="base" index="36" />
 </animation>
 <animation name="stand" direction="right">
  <frame imageset="base" index="54" />
 </animation>

 <animation name="walk" direction="down">
  <frames imageset="base" start="1" end="6" delay="10" />
 </animation>
 ...

 <animation name="sit" direction="down">
  <frame imageset="base" index="7" />
 </animation>
 ...

 <animation name="bow-attack" direction="down">
  <frames imageset="base" start="13" end="17" delays="5,5,5,10,50" />
 </animation>
 ...

 <animation name="dead">
  <random>
   <frame imageset="base" index="8" />
   <frame imageset="base" index="26" />
   <frame imageset="base" index="44" />
   <frame imageset="base" index="62" />
  </random>
 </animation>
</sprite>

After reflecting on this idea (and the above suggestion) I think a first issue to solve is to reduce the amount of duplicated definitions. For example, at the moment every monster uses the exact same frame size and animation frames. Hence, it would be interesting if the defined sprite could take the actual imageset it uses as a parameter instead of specifying this on its own. This way we can define a monster like:

<sprite name="monster-walking-default">
 <imageset name="base" />
 ...
</sprite>

<being name="Scorpion">
 <sprite ref="monster-walking-default">
  <with-imageset name="base" src="scorpion-base.png" width="60" height="60" />
 </sprite>
</being>

<being name="Red Scorpion">
 <sprite ref="monster-walking-default">
  <with-imageset name="base" src="scorpion-red-base.png" width="60" height="60" />
 </sprite>
</being>

Defining an empty imageset (without src attribute) will make the engine require this imageset to be passed as a parameter whenever the spriteset is referenced.

I'm not convinced yet that the name attribute of the sprite element is really necessary. I think I would prefer each sprite to be defined in its own file. However it could be attractive to define multiple things in the same file, but in that case I think we will have to read everything when the client is launched, and only graphics and imagesets can be created lazily (only when necessary). Because the other data consists of just numbers and strings, this should not be really a problem.

An implementation idea by Peoro

An idea to manage the animations should be a class based on a timer which choose automatically the current frame. Who needs animations (players, monsters and npcs) has only to start the animanion and to request the frame to draw it. Each instance of Animation class keep its own start-time (or a personal timer), its total time, number of frames, and the time of every frame. The Animation class has to provide at least a function to start the animation and another one to get the current frame; other usefull functions should pause and restart the animation and allow to jump to a random frame.

Here a simple example of a prototype of an Animation class:

class Animation
{
    int nframes;
    Img **frames;
    int *times;
    int totalTime;
    int start;
    
public:
    Animation ( int n, Img **i, int *t )
    {
        int c;
    	
        nframes = n;
        frames = new Img* [n];
        times = new int [n];
        totalTime = 0;
        
        for ( c = 0; c < n; c ++ ) {
            frames[c] = i[c];
            times[c] = t[c];
            totalTime += t[c];
        }
    }
    
    inline void start ( int t )
    {
        start = t;
    }
    
    Img *getFrame ( int t )
    {
        int i;
        int it
        
        if ( ! totalTime ) {
            t = 0;
        } else {
            t = (t-start) % totalTime;
        }
            
        // searches the current frame
        for ( i = 0, it = 0; (it + times[i]) < t; i ++, it += times[i] );
        
        return frames[i];
    }
	
};

And here how to use this class:

Img * imgs [] = { img1, img2, img3 };
int times [] = { 20, 20, 30 };

Animation a ( 3, imgs, times );

a.start ( clock() );

while ( 1 ) {
    draw ( a.getFrame(clock()) );
}

An advantage of this class is that the animation speed is indipendent to FPS and it's very easy to use.