9
\$\begingroup\$

How do you handle the separation of animation from the world state within a turn based game? I'm working on a 2D grid based game currently. The below code is simplified to better explain.

When an actor moves, I want to pause the flow of turns while the creature animates and moves to the new position. Otherwise, the screen could lag significantly behind the world state, which would cause weird visual appearance. I also want to have animations that don't block the flow of the game - a particle effect could unfold over multiple turns without affecting gameplay.

What I've done is introduce two types of animations, which I call blocking animations and non-blocking animations. When the player wants to move, the code that gets executed is

class Actor {
    void move(PositionInfo oldPosition, PositionInfo newPosition) {
        if(isValidMove(oldPosition, newPosition) {
             getGame().pushBlockingAnimation(new MoveAnimation(this, oldPosition, newPosition, ....));
             player.setPosition(newPosition);
        }
    }
}

Then the main update loop does:

class Game {
    void update(float dt) {
        updateNonBlockingAnimations(dt); //Effects like particle explosions, damage numbers, etc. Things that don't block the flow of turns.
        if(!mBlockingAnimations.empty()) {
            mBlockingAnimations.get(0).update(dt);
        } else {
             //.. handle the next turn. This is where new animations will be enqueued..//
        }
        cleanUpAnimations(); // remove finished animations
    }
}

...where the animation updates the screen position of the Actor.

One other idea I am implementing is to have a concurrent blocking animation, where multiple blocking animations would be updated simultaneously, but the next turn wouldn't happen until all of them were done.

Does this seem like a sane way to do things? Does anyone have any suggestions, or even references to how other similar games do such a thing.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

If your system works for you, I don't see any reason not to do so.

Well, one reason: It might get messy when you don't easily can judge the consequences of "player.setPosition(newPosition);" anymore.

Example (just making things up): Imagine you move the player with your setPosition on top of some trap. The call to "player.setPosition" itself will trigger some other call to .. say a trap listener code which in turn will push a blocking animation by itself that displays a "hurting splash" on top of itself.

You see the problem: the splash gets displayed simultanously with the move of the player towards the trap. (Lets assume here that you don't want this, but want the trap animation to be played right after the move finished ;)).

So as long as you are able to keep all the consequences of your actions in mind that you do after those "pushBlockingAnimation" calls, everything is fine.

You may need to start to wrap game logic into "blocking something" - classes so they can be queued to execute after the animation finishes. Or you pass a callback "callThisAfterAnimationFinishes" to the pushBlockingAnimation and move the setPosition into the callback function.

Another approach would be scripting languages. For example Lua has "coroutine.yield" which returns the function with a special state so it can be called later to continue at the next statement. This way you can easily wait for the end of the animation to execute game logic without cluttering the "normal" program flow look of your code. (means your code would still look a bit like "playAnimation(); setPosition();" instead of passing callbacks around and cluttering game logic over multiple functions).

Unity3D (and at least one other game system I know) features the C# "yield return" statement to simulate this directly in C# code.

// instead of simple call to move(), you have to "iterate" it in a foreach-like loop
IEnumerable<Updatable> move(...) {
    // playAnimation returns an object that contain an update() and finished() method.
    // the caller will not iterate over move() again until the object is "finished"
    yield return getGame().playAnimation(...)
    // the next statement will be executed *after* the animation finished
    player.setPosition(newPosition);
}

Of course, in this scheme the call to move() gets all the dirty work now. You would probably apply this technique only for specific functions where you know exactly who call them from where. So take this only as a basic idea how other engines are organized - its probably way too complicated for your current specific problem.

I recommend you stick to your pushBlockingAnimation idea until you having real-world trouble. Then modify it. ;)

\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much for taking the time to write this response, I really appreciate it. \$\endgroup\$
    – mdkess
    Feb 18, 2012 at 18:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .