MichaelAquilina / Some-2D-RPG

Tinkering with Game Development
MIT License
28 stars 5 forks source link

Feature request: callback when an animation ends #4

Closed behindcurtain3 closed 11 years ago

behindcurtain3 commented 11 years ago

I am trying to implement a rudimentary "Combat Dummy" using the graphic from the LPC. I have all the animations working, however, I can't seem to find a way to switch the CurrentDrawableState back to an Idle animation once the "Spin" animation ends (Loop=false). Is there a way to test for an animation that has finished that I'm missing?

If not, it would be awesome to just have an event raised that we could subscribe to.

MichaelAquilina commented 11 years ago

Hi behindcurtain3, its actually something im still figuring out how to perform in a well engineered manner. The reason this is not as simple as it seems is because Animations are not 'Updated' at each loop per say. Rather, when a draw call is made, the gameTime is passed as a parameter to the GetSourceTexture() and GetSourceRectangle() method of the Animation instance. With this gameTime value, the current frame to display is returned as follows:

int index = (int) Math.Abs((elapsedMS) / FrameDelay);

return (Loop)? index % Frames.Length : index;               //If looping, start from the beginning

The Rectangle instance from Animation.Frames[index] is then returned.

As you can see, an animation 'ends' only if it is requested to be called at each loop. You could technically fire an event when index % Frames.Length == 0, but this is not always guaranteed to fire if the application get stuck during the few milliseconds that its meant the display the last frame.

There is some information on how this is done on the repsitory's Wiki (I try to keep this as up to date as possible - but due to being under heavy development, some things on the page may be out of date).

This is mainly the reason why i haven't implemented that yet (because otherwise its relatively simple). I was hoping that over time id come up with a better solution for it, but i have been focusing on other areas.

I will try give this feature some attention this week - you could also try have a go at it yourself if you wish.

EDIT: I Just remembered that the Animation class exposes an 'IsFinished' method which should give you an indication whether or not an animation has completed its cycle. While events would also be useful, this should also give you a way of detecting the state during Update loops.

behindcurtain3 commented 11 years ago

Thanks for the great reply. Unfortunately I'm having issues with the IsFinished method also. Here is the code in my update loop:

if (CurrentDrawableState.Contains("Spin"))
{
    List<GameDrawableInstance> animations = Drawables.GetByState(CurrentDrawableState);

    foreach (GameDrawableInstance instance in animations)
    {
        if (instance.Drawable is Animation)
        {
            Animation animation = (Animation)instance.Drawable;

            if (animation.IsFinished(gameTime.TotalGameTime.Milliseconds))
            {
                CurrentDrawableState = "Idle_" + Direction;
            }
        }
    }
}

I tested using breakpoints and IsFinished never returns true. Is this the correct usage? (I agree events would be more useful, I'll look into the engine code and see if I can come up with something)

(Edit: Nice update today, the auto-loading of entities is awesome!)

behindcurtain3 commented 11 years ago

I changed the IsFinished call to this:

if (animation.IsFinished(gameTime.TotalGameTime.Milliseconds - instance.StartTime))

And now it always returns true. Has to be something easy I'm overlooking.

MichaelAquilina commented 11 years ago

The last post you actually made should be the right way of doing it.

To be clear, its a bug in the way i coded the Animation class and ill write it down in my todo list. Whenever you call Reset(gameTime) on a GameDrawableInstance, the StartTime value is changed:

 public void Reset(GameTime gameTime)
 {
     StartTimeMS = gameTime.TotalGameTime.TotalMilliseconds;
 }

Items are then drawn using this equation:

Drawable.GetSourceRectangle(gameTime.TotalGameTime.TotalMilliseconds - StartTimeMS);

Unfortunately, the bug arises because Animation is currently not aware of this value when checking if it IsFinished. I will give it priority this afternoon and fix it as soon as possible for you. In the meanwhile you could use the fix you pointed yourself (it should work as i think it should be, but i dont have the solution where i am at the moment). Ill probably make all IGameDrawable classes expose some form of GetCurrentState() property that will fix this issue.

EDIT: Glad you liked the update, thought it would simplify game code quite nicely!

behindcurtain3 commented 11 years ago

I just pushed a commit to my fork for a possible event handler. It's not perfect because it relies on whoever is using the animation to either reset its instance or change the CurrentDrawableState to avoid repeatedly calling the event. However, I did test it with the combat dummy and it works.

Here is the commit 9412c33

Let me know what you think.

MichaelAquilina commented 11 years ago

The idea behind it is good, but i have a feeling ill need to perform some re factoring of the Animation class to make it work perfectly (The issues you mentioned should obviously be avoided if possible).

MichaelAquilina commented 11 years ago

I just committed a few changes that should help you with this particular post. See the CombatDummy Example to get an overall idea of how it can be done now.

behindcurtain3 commented 11 years ago

Very cool! Thanks :D