20
\$\begingroup\$

I've never really done much game programming before, pretty straightforward question.

Imagine I'm building a Tetris game, with the main loop looking something like this.

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            remove all complete rows
            move rows down so there are no gaps
            if we can spawn a new block
                spawn a new current block
            else
                game over

Everything in the game so far happens instantly - things are spawned instantly, rows are removed instantly etc. But what if I don't want things to happen instantly (i.e animate things)?

for every frame
    handle input
    if it's time to make the current block move down a row
        if we can move the block
            move the block
        else
            ?? animate complete rows disappearing (somehow, wait over multiple frames until the animation is done)
            ?? animate rows moving downwards (and again, wait over multiple frames)
            if we can spawn a new block
                spawn a new current block
            else
                game over

In my Pong clone this wasn't an issue, as every frame I was just moving the ball and checking for collisions.

How can I wrap my head around this issue? Surely most games involves some action that takes more than a frame, and other things halt until the action is done.

\$\endgroup\$

5 Answers 5

11
\$\begingroup\$

The traditional solution to this is a finite state machine, which is being suggested in several comments.

I hate finite state machines.

Sure, they're simple, they're supported in every language, but they're such an amazing pain to work with. Every manipulation takes a ton of bugprone copy-and-paste code, and tweaking the effect in small ways can be a huge change to code.

If you can use a language that supports them, I recommend coroutines. They let you write code that looks like:

function TetrisPieceExplosion()
  for brightness = 0, 1, 0.2 do
    SetExplosionBrightness(brightness)
    coroutine.yield()
  end

  AllowNewBlockToFall()

  SpawnABunchOfParticles()

  RemoveBlockPhysics()

  for transparency = 0, 1, 0.5 do
    SetBlockTransparency(transparency)
    coroutine.yield()
  end

  RemoveBlockGraphics()
end

Obviously rather pseudocodey, but it should be clear that not only is this a simple linear description of the special effect, but it easily lets us drop a new block while the animation is still finishing. Accomplishing this with a state machine will generally be ghastly.

To the best of my knowledge, this functionality isn't easily available in C, C++, C#, Objective C, or Java. This is one of the main reasons I use Lua for all my game logic :)

\$\endgroup\$
7
  • \$\begingroup\$ You could also implement something along these lines in other OOP languages. Imagine some sort of an Action class and a queue of actions to perform. When an action is complete, remove it from the queue and perform the next action etc. Way more flexible than a state-machine. \$\endgroup\$
    – bummzack
    Feb 14, 2011 at 15:12
  • 3
    \$\begingroup\$ That works, but then you're looking at deriving from Action for every single unique action. It also assumes that your process fits into a queue nicely - if you want branching or loops with undefined end conditions, the queue solution breaks down quickly. It's certainly cleaner than the state machine approach, but I think coroutines still trump it on readability :) \$\endgroup\$
    – ZorbaTHut
    Feb 14, 2011 at 15:29
  • \$\begingroup\$ True, but for the Tetris example it should be sufficient :) \$\endgroup\$
    – bummzack
    Feb 14, 2011 at 15:40
  • \$\begingroup\$ Co-routines rock- but Lua as a language sucks in so many other ways, I just can't recommend it. \$\endgroup\$
    – DeadMG
    Feb 14, 2011 at 16:55
  • \$\begingroup\$ As long as you only need to yield at the top level (and not from a nested function call), you can accomplish the same thing in C#, but yes, Lua coroutines rock. \$\endgroup\$
    – munificent
    Feb 15, 2011 at 1:29
8
\$\begingroup\$

I'm taking this from Game Coding Complete by Mike McShaffry.

He talks about a 'Process Manager', which boils down to a list of tasks that need to be done. For example, a process would control the animation for drawing a sword (AnimProcess), or opening a door, or in your case, make the row disappear.

The process would be added to the process manager's list, which would be iterated every frame and Update() called on each. So verymuchlike entities, but for actions. There would be a kill flag to remove from the list when it has finished.

The other neat thing about them is how they can link, by having a pointer to the next process. In this way, your animate row process may actually consist of:

  • An AnimationProcess for the row disappearing
  • A MovementProcess to remove the pieces
  • A ScoreProcess to add points to the score

(Since processes can be one-use things, conditionally there, or there for X amount of time)

If you want any more details, ask away.

\$\endgroup\$
3
\$\begingroup\$

You can use a priority queue of actions. You push in an action, and a time. Each frame, you get the time, and you pop off all actions that have a time specified as before that time and execute them. Bonus: Approach parallelises nicely, and you can actually implement almost all game logic this way.

\$\endgroup\$
1
\$\begingroup\$

You always need to know the time difference between the previous and the current frame, then you've got to do two things.

-Decide when to update your model: eg. in tetris when a row removal begins, you don't want stuff collide with the row anymore, so you remove the row from the 'model' of your application.

-You then have to handle the object that is in a transitioning state to a separate class that resolves the animation/event over a period of time. In the tetris example you would have the row fade out slowly (change the opaqueness each frame a little bit). After the opaqueness is 0 you transfer all blocks on top of the row one down.

This might seem a bit complicated at first, but you will get the hang of this, just make sure to abstract a lot in different classes, this will make it easier. Also make sure that events that take time, like the removing of a row in tetris, are of the kind "Fire and Forget", just create a new object that handles everything that needs to be done automatically and that, when everything is done, removes itself from your scenegraph.

\$\endgroup\$
1
  • \$\begingroup\$ Also, in some cases heavy calculations might exceed the allowed time for one physics time step(eg. collision detection and path planning). In these cases you can jump out of the calculations when the allotted time has been used and continue the calculation next frame. \$\endgroup\$
    – Nailer
    Feb 14, 2011 at 11:46
0
\$\begingroup\$

You need to think of the game as a "finite state machine". The game can be in one of several states: in your case, "expecting input", "piece moving down", "row exploding".

You do different things depending on the state. For example, during "piece moving down" you ignore player input, and instead animate the piece from its current row to the next row. Something like this:

if state == ACCEPTING_INPUT:
    if player presses any key:
        handle input
    row_timer = row_timer - time_since_last_frame
    if row_timer < 0:
        state = MOVING_PIECE_DOWN
elif state == MOVING_PIECE_DOWN:
    piece.y = piece.y + piece.speed*time_since_last_frame
    if piece.y >= target_piece_y:
        piece.y = target_piece_y
        state = ACCEPTING_INPUT
\$\endgroup\$

You must log in to answer this question.