Separating animation and game code

Started by
8 comments, last by Boris Karloff 19 years, 3 months ago
I'm wondering how to go about splitting animation and game code up. Say I'm working on a board game like, for instance, chess. Suppose I have a Chessboard object representing the game. I can call methods to move certain pieces, and to query the state of the game board. Using this, you can make a fairly simple chess game. You could, for instance, type in the position of the piece you want to move, and the position you move it to, and the Chessboard object would perform the necessary logic steps within the MovePiece method. Then yuo could draw the game by querying the state, and you're done. However, this involves no animation. The MovePiece function is called between two calls of the drawFrame function, so the pieces simply jump to their new position. Now, suppose I want to create a more interesting-looking game. Battle Chess, for instance. This would require some sort of animation as a piece moves across the board (could be a complicated walking-animation or simply sliding the piece over to the new position frame-by-fame). But, if I use a traditional game loop like this simplified one:

while (!quit)
{
	// Handle Input
	read starting and destination position
	
	// Update
	Chessboard.movePiece(startPos, destPos)
	
	// draw
	DrawFrame()
}
...I see now way of animating, since the piece will be at its new position before drawframe is called. What ways are there of fixing this? The Chessboard object now blissfully unaware of any D3D meshes and velocity.... Would creating a second Chessboard object, which is only responsible for drawing it all, be a solution? What decent articles are there to discuss how to go about these sort of things?
Nein heer du smign. ah open up the nine im heer du shmine
Advertisement
Since in battle-chess you really only move one piece every turn/frame, the answer could simply be to store the change somewhere and send it to the DrawFrame() function. I must admit though that it feels like an ad-hoc solution and would likely not work as well for more active games. For example, it would not work if you wanted to allow players to skip the animation by pressing some key at any time (unless you check for input while animating, but this could make your code a bit messy).
Exactly, I can think of a lot of ad-hoc solutions, but no all-purpose means of going about this sort of thing. I'm just using Battle Chess as an example, by the way.
Nein heer du smign. ah open up the nine im heer du shmine
Well, it would seem that you would need to decrease the time-step between renders, i.e. a move would not occur in a single time step but would be drawn out over several frames.

The answer though lies in the type of game you want to make. In a TBS game you could make moves animate them at the end of the loop. Animation would then proceed as outlined above (send the change). For more real-time games, you would need to have position variable for all entities in the game and update them each frame (keeping the update small so you don't get the same problem as with chess).
Yes, but then you're merging the animation and the game code. If the step has to be broken up in smaller increments for the sole reason of rendering, game code and graphics code are no longer separated.
Nein heer du smign. ah open up the nine im heer du shmine
There's no simple answer for this. I'd suggest using a parametric line equation

position = starting_point + t * (termination_point - starting_point)

the "t" paramiter is what percentage of the line segment you have traversed. If t = .5 then position is the midpoint of the line. If t = .75, then position is much closer to the endpoint than the starting point. If t = 1 then position = termination_point. If t = 0 then position = starting_point.

By incrementing t, you can create animations. Once t reaches 1, the animation is over.
I think that for what you are looking for, you need to make gameplay independent of your display. Each frame needs to call your DrawFrame function. Somewhere in your program you will need to maintain a list of animations that are being displayed. Each animation should itself know where it is being displayed on the screen, and which image to display. So your game loop will go something like this:

int updateGame(){    // game logic happens here    --gameTicks;}int updateAnimations(){    int ii;    for(ii = 0; ii < countAnimations; ++ii) {        animations[ii]->update(elapsedTimeSinceLastUpdate);    }}int gameLoop(){    while(!quit) {        while(gameTicks > 0) {            updateGame();        }        updateAnimations();        render();    }}


The important thing here is that your animations will also need to know how long it should take to complete their animation, not in frames but in real time. The animation code should be able to interpolate the movement and frame-flipping of the image on the screen. This will create a frame-rate independent display.
Quote:Original post by Boris Karloff
while (!quit){	// Handle Input	read starting and destination position		// Update	Chessboard.movePiece(startPos, destPos)		// draw	DrawFrame()}
Let's start here. You have too much cohesion between your main loop and your program logic. You want a higher level of abstraction that passes user input to the chessboard system, which then resolves piece movements, delegating actual animation of the pieces to the pieces themselves.
struct Piece{  position p;  bool captured;  void moveTo(position P, timestep t);  void update();  ...};struct Chessboard{  vector<Piece> pieces;  bool ready();  void play(UserInput i);  void update();  BoardRep state();  ...};...int main(){  ...  while(1)  {    ...    if(board.ready())    {      // get user input      board.play(i);    }    board.update();    Draw(board.state());    ...  }  ...}

The Piece is responsible for animating its transition from its initial position u to its final position v over an interval t. Piece::moveTo() sets this transition in motion, but Piece::update() is responsible for the step-by-step movement. Board::update() calls Piece::update() for each of the Pieces it stores. Board::state() returns a representation of the state of the Board (the positions and animation indices of all Pieces) that is ready to be rendered. Board::ready() indicates whether user input can be processed by the Board (obviously, it returns false while any of the current user's Pieces is moving).
Here's how I'd do it:

Suppose you have an array of all of your pieces. Each element in the array is a structure that holds information about each piece (rank, position, animation_state, etc).

So what you do is you trigger the animation state when you want a pice to move. You would also probably have some other variables in the structure, like destination_square or animation specific settings so that your animation function will know how to animate the piece.

Then each time through your main game loop, you check every piece on the board and see which ones need to be moved. The ones that need moved, get moved.

In chess, only one piece moves at a time, so I would probably just halt all other processes in the game until the piece has finished animating. Basically set game_state = ANIMATING_PIECES; Then once the animation has finished, set game_state = WHATEVER_THE_USUAL_STATE_IS; That way the usual stuff will not execute until the Animation has finished.

since this is battle chess, I'd probably have a game_state called ANIMATING_BATTLE. Then the battle animation can execute while everything else is halted.

basically like this:


switch(game_state)
{
case WHATEVER_THE_USUAL_STATE_IS:
{
/*The usual junk
would go here*/
break;
}

case ANIMATING_PIECES:
{
/*Code to animate a move*/
if (no_more_anims)
game_state = WHATEVER_THE_USUAL_STATE_IS;
break;
}

case ANIMATING_BATTLE:
{
/*Code to animate a battle*/
if (no_more_anims)
game_state = WHATEVER_THE_USUAL_STATE_IS;
break;
}
}


This is most likely how I would do it, since chess is a turn based game. Everything happens in order. In a real time game, you would be constantly doing the animations so this wouldn't work.
Quote:Original post by Oluseyi
The Piece is responsible for animating its transition from its initial position u to its final position v over an interval t. Piece::moveTo() sets this transition in motion, but Piece::update() is responsible for the step-by-step movement. Board::update() calls Piece::update() for each of the Pieces it stores. Board::state() returns a representation of the state of the Board (the positions and animation indices of all Pieces) that is ready to be rendered. Board::ready() indicates whether user input can be processed by the Board (obviously, it returns false while any of the current user's Pieces is moving).


That's one way I figured, but wouldn't that cause a lot of cohesion between the game logic (the piece knowing where it needs to go, etcetera) and the graphics (piece position, animation?)
Nein heer du smign. ah open up the nine im heer du shmine

This topic is closed to new replies.

Advertisement