-Character
animation- |
Sure,
its quite easy to load models and stuff. It takes minimal effort to load
an MD2 model, and build switch animation sequences to produce a realistic
looking character. But, if you've tried to really use the model interactively,
you'll find a lot of problems crop up. For example, take the jump animation.
If you simply did what is indicated in the following pseudo code, |
whenever the jump key is pressed
Model.AnimationSequence:=jump;
|
You'll
find that as long as the jump key is held, the character will remain in
the air, and will not complete the jump, as is to be expected. You'll find
yourself writing a lot of special conditions and stuff to handle animations. |
What
we would need is a method to handle animations and basically let the model
itself worry about the other stuff like keeping track of whether to stay
crouched, or jumped, etc. Animation sequences of a model, are states. A
model enters an animation when a condition is satisfied, and exits from
it when another condition is satisfied. So a character animation table would
look very much like a state diagram, depicted below. |
|
This
is a simple state diagram consisting of three animation states, stand, jump
crouch and crouchstand. As you can see, a model in a jumped state cannot
stay jumped. The only transition a jumped model can make is back to the
stand state. This is rather simplified, and putting more states makes the
diagram much more complicated. So, once we agree that we really could do
with a state machine for representing character animation, we should see
what that involves. |
Every
state in our diagram, has one or more states to which it can transition.
So, our basic state data structure would look sorta like this |
TState=record
ID:integer;
Name:string;
ChangeAllowed:boolean;
NextStates:array of integer;
Key:word;
Sequence:integer;
end;
|
TStateEnterEvent=procedure (State:integer) of object;
|
The
event declared would be the mechanism by which the program would find out
that a state change has actually taken place. The state class updation would
take place as follows. |
nextstate:=FindState(nextstateid);//nextstateid is
//simply requested
//it need not exist
//for example, jump-jump
//is invalid
//perform sanity checks like there are no states, etc
if not CanTransition then exit;//CanTransition indicates whether
//a particular state can be
//interrupted. For example, you
//can interrupt a stand state, but
//cannot interrupt a jump state
if states[nextstate].id=currentstate.id then exit;
if nextstate=-1 then //this means the next state requested
//was not found. So, we have to go to
//the current state's default successor,
//which is the 1st item in the next
//states array.
begin
currentstate:=states[currentstate].nextstates[0].id;
CanTransition:=states[currentstate].nextstates[0].CanTransition;
//call the OnEnterState event here
exit;
end;
//if we have come this far it means that
//we've got a valid successor
currentstate:=states[nextstate].id;
CanTransition:=states[currentstate].nextstates[0].CanTransition;
|
This
would be the essence of the Update method of the TStateDiagram class. Here,
the input parameter to this method would be only the next requested state.
But who translates the keys/commands into state numbers? To better understand
a flexible way of maintaining animated characters, take a look at the following
hierarchy. |
|
The
input subsystem is, for the purposes of this discussion the keyboard, but
can be anything, even a set of animation commands from a file. The Keymap,
is the part of the hierarchy that converts the keycodes or whatever else
into a set of unique values that the state diagram knows (In the given TState
structure, its equivalent would be the Key field). The reason to have a
Keymap is that, the actual events that trigger a certain value can be altered
easily. For example, you could reassign keys that correspond to certain
actions without having to change a whole lot of other things. TAnimatedCharacter
is the class that would own a TStateDiagram, and is responsible for managing
it. I would be better to have TAnimated character simply control mesh objects,
who are separate entities. However, one might abandon that level of abstraction,
and put the mesh handling code inside TAnimatedCharacter. TAnimatedCharacter,
is also responsible for handling the events that originate from TStateDiagram,
like OnStateEnter. This might be a bit of a pain to implement initially.
But once you are done the results will be worth it, believe me. |
The
download accompanying this article is not source code as you might expect.
This demo uses an older version of Lucifer
for its implementation, and the source will be presented after the conversion
to the new version of Lucifer is complete.
However, I have uploaded an EXE demo, where the state diagrams for the bots
are built by script. This gives total customisability to the bot maker,
while leaving lesser work for the programmer and greater flexilbility to
the framework. The bot demo is only console driven, so commands will have
to be typed in. The command list is, |
Command |
Description |
LoadBot(<bot
filename>); |
Loads
a bot script file and executes it. There are two bots supplied with the
example, so you can type LoadBot('bots\was.bot'); or LoadBot('bots\goblin.bot'); |
About; |
Displays
about information |
DrawFPS(<boolean>); |
Displays/hides
the framerate display on the screen. |
Quit; |
quits
the application |
The
bot script files are simply text files, which I suggest you take a look
at. I'll post the source code for a character animation shortly as part
of Lucifer. The new version will be skeletal, with total customisability. |
.>>Download
the demo |