• entries
    707
  • comments
    1173
  • views
    434143

Animation Systems

Sign in to follow this  

77 views

The old animation system was...crappy to say the least. I knew this as I was programming it, but it was working, so I ran with it. As I said, it wasn't very "instance-friendly" and thus had to be done away with. The problem was that it created new sequences on the heap and so I couldn't copy it (well, I could, but I'd have to create more sequences for each copy and copy it's frames; obviously that's not what instancing is about.)

Anyway, the old system was setup like so:

Animation class
Sequence class
Frame class
Offset
Texture
SourceRectangle
// Started with Delay here, but moved
// it to Sequence because I don't feel
// every frame needs a different delay.
Name
Frames
FrameDelay
Loop
Name
Sequences
CurrentSequence
CurrentFrame
LoopOverride // this was very hacked in lol
State
Timer
OnSequenceFinished

To those of you that can already see the problem, I give you a cookie. The problem being that Animation contains both it's data and instance information (CurrentSequence, CurrentFrame, LoopOverride, State, Timer, and OnSequenceFinished.)

So, I took out the memory allocation when allocating sequences, change things up a little bit, and added a fourth class: AnimationController. So, it looks like so now:

Animation class
Sequence class
Frame class
Offset
Texture
SourceRectangle
Name
Frames
FrameDelay
Loop
StaticFrame
Name
Sequences

AnimationController class
CurrentAnimation
CurrentSequence
CurrentFrame
State
Flag
Timer


An example usage (note that this is testbed code and thus isn't very pretty lol):

ulong Temp = Kernel->LoadTexture("Anim01", "Heal2.png", 0);
ulong Temp2 = Kernel->LoadTexture("Anim02", "Fire2.png", 0);

// AddSequence(Name, FrameDelay, Loop, StaticFrameIndex)
TestAnim.AddSequence("Anim1", 0.125f, true, -1);
TestAnim.AddSequence("Anim2", 0.0625, false, -1);

dbMath::Size FrameSize(192, 192);

// AddFrame(SequenceName, Offset, Texture, SourceRectangle)
TestAnim.AddFrame("Anim1", dbMath::Vector2::Zero, Temp, dbMath::Rectangle(192 * 0, 0, FrameSize));
TestAnim.AddFrame("Anim1", dbMath::Vector2::Zero, Temp, dbMath::Rectangle(192 * 1, 0, FrameSize));
TestAnim.AddFrame("Anim1", dbMath::Vector2::Zero, Temp, dbMath::Rectangle(192 * 2, 0, FrameSize));
TestAnim.AddFrame("Anim1", dbMath::Vector2::Zero, Temp, dbMath::Rectangle(192 * 3, 0, FrameSize));

dbMath::Vector2 Offset(-(192.0f / 2.0f), -(192.0f / 2.0f));
TestAnim.AddFrame("Anim2", Offset, Temp, dbMath::Rectangle(192 * 2, 192 * 3, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp, dbMath::Rectangle(192 * 1, 192 * 3, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp, dbMath::Rectangle(192 * 0, 192 * 3, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp, dbMath::Rectangle(192 * 4, 192 * 2, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 1, 192 * 0, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 2, 192 * 0, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 3, 192 * 0, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 4, 192 * 0, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 1, 192 * 1, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 2, 192 * 1, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 3, 192 * 1, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 4, 192 * 1, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 1, 192 * 2, FrameSize));
TestAnim.AddFrame("Anim2", Offset, Temp2, dbMath::Rectangle(192 * 2, 192 * 2, FrameSize));

// SetAnimation(Animation, StartingSequence = "", Playing = false)
TestAnimCntrlr.SetAnimation(&TestAnim, "Anim1", true);



After execution, TestAnimCntrlr is set to TestAnim.Anim1 and is playing (supplying false as the last parameter sets the sequence and sets the frame to the sequence's static frame if available or clears it.)

The system makes a couple assumptions:
1) If a sequence's static frame is less than zero, it assumes that you don't want the animation to be visible when it's stopped.
2) If a sequence's static frame is less than zero, the controller is playing, and the animation is looping (or the controller is overriding it with looping), it assumes you want to play the animation and starts from the beginning.
3) If you tell the controller to stop, it assumes you want to display the sequence's static frame (if available.)
4) When you change sequences it assumes you want to display the sequence's static frame.

The idea is that the engine will contain a animation containing the current level's effects and then the game will tell the engine "Play this effect at this location" and viola, instant effects.
Then, each actor type will have an animation associated with them which would contain their walking animations, walking with weapons, etc and then the actor class itself will contain a controller. This will work fine for this game as I don't plan on adding any more than 5 weapons for the main character and most enemies will only have 1 weapon, if any. A more in-depth game like an RPG would need to couple the animation system with a paper-doll system, but I'll worry about that at a later date.

I do need to implement a 'sprite-sheet' sort of thing along side the animation. There are plenty of things that have more than one frame, but don't animate and/or don't have multiple sequences (a mouse cursor, a button, etc.)
[edit] Actually, the current system works fine for the above:

Animation CursorAnim;
CursorAnim.AddSequence("Cursor", 0.0f, false, 0);

AnimationController CursorCntrlr;
CursorCntrlr.SetAnimation(&CursorAnim);

// And then, for a quick example in my OnMouseMove()function, with pseudo-code:
if(SomeObject.ContainsPoint(X, Y))
{
switch(SomeObject.Type)
{
case ObjectTypes::Person:
{
CursorCntrlr.SetFrame(CURSOR_TALK_FRAME);
break;
}

case ObjectTypes::Item:
{
if(SomeObject.IsOwned())
CursorCntrlr.SetFrame(CURSOR_STEAL_FRAME);
else
CursorCntrlr.SetFrame(CURSOR_PICKUP_FRAME);
break;
}

// etc
}
}


You wouldn't be using the controller directly, you'd be using AnimatedEntity, so it'd be less code for initialization, but that's the idea. Better yet, this way would also allow for animated cursors if it was wanted (change sequences instead of frames.)

I shouldn't even be worrying about it; Invasion won't have multiple cursors and the GUI will be programmed with a simple skin system, so it's not even a problem, but it is nice to know that the support is there and I don't have to change anything before I rip it out and stick it in a lib (obviously I'll go over it and whatnot, but the overall design shouldn't need to be changed at all.)
[/edit]

I got my todo list all done today, so I'm putting that up top. If I have time tomorrow at work I'm going to write out my goals and milestones, but it might be busy being the day after the 4th and I'm working with a 70-something year old lady; if so, I'll write it Sunday night or Monday afternoon and then pop it up there as well.

I didn't get to the entity system yet, but it's all designed out and is rather simple. Pretty much, I have a base entity class containing the simple data (position, rotation, scaling, tint, blend mode, and visibility) and then two derived classes: Entity and AnimatedEntity, each storing a Sprite and AnimationController respectively. Then there's an EntityManager class that sorts and renders everything. The base entity class itself is a valid class to use, as it contains all the required info for rendering planes.

Off to bed for me. Hopefully I can get more work done over Monday, Tuesday, and Wednesday (my days off next week.)
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now