6
\$\begingroup\$

I'm trying to build a simple game with a number of screens - 'TitleScreen', 'LoadingScreen', 'PlayScreen', 'HighScoreScreen' - each of which has it's own draw & update logic methods, sprites, other useful fields, etc. This works well for me as a game dev beginner, and it runs.

However, on my 'PlayScreen' I want to run some animations before the player gets control - dropping in some artwork, playing some sound effects, generally prettifying things a little. However, I'm not sure what the best way to chain animations / sound effects / other timed general events is.

I could make an intermediary screen, 'PrePlayScreen', which simply has all of this hardcoded like so:

Update(){
     Animation anim1 = new Animation(.....);
     Animation anim2 = new Animation(.....);
     anim1.Run();
     if(anim1.State == AnimationState.Complete)
          anim2.Run();
     if(anim2.State == AnimationState.Complete)
          // Load 'PlayScreen' screen
}

But this doesn't seem so great - surely their must be a better way? I then thought, 'Hey - an AnimationManager! That'd be awesome!'. But then that creeping OOP panic set in as I thought about it some more.

If I create the Animation in my Screen, then add it to the AnimationManager (which may or may not be a GameComponent hooked up to Update/Draw), how can I get 'back' to it? To signal commands like start / end / repeat? I'd still need to keep a reference to the object in my Screen so that I could still communicate with it once it's buried in the bosom of a List in my AnimationManager. This seems bad.

I've also tried using events - call 'Update' on all the animations in the PlayScreen update loop, but crucially all of the animations have a bool flag ('Active') which determines whether they should begin. The first animation has this set to 'true', all others 'false'. On completion the first animation raises an event, which sets animation 2's bool flag to true (and so it then runs). Once animation 2 is complete another 'anim complete' event is raised, and the screen state changes.

Considering the game I'm making is basically as simple as it gets I know I'm overthinking this... it's just the paradigm shift from web -> game development is making me break out in a serious case of the stupids.

\$\endgroup\$

1 Answer 1

7
\$\begingroup\$

The Basic Framework

Here's my solution to this common problem. First let's choose a name for an action that spans several frames, such as an animation. I'll call it a Process which is the terminlogy I found in this book, but don't mistake it with the processes on your OS, it's a different concept. In this context, a process is anything that needs to be updated, possibly several times, before it completes.

I'll start by defining a base class for our process. Something as simple as this is enough (I kept the name as Process for consistency with the discussion above, but since .NET already has a class named like that, you might want to give it another name):

public abstract class Process
{
    public abstract void Update(float elapsed);
    public bool Finished { get; set; }
}

Now let's create a class that serves as a process queue. You add processes to this queue, and they get executed in turn. This is where my implementation differs from the one in the book above. In the book every process is updated every frame, but in this case I'll make them sequential instead, so that the second process only starts when the first one ends. Once again it's a simple class:

public class ProcessQueue
{
    public void Add(Process process)
    {
        _processQueue.Add(process);
    }

    public void Update(float elapsed)
    {
        if (_processQueue.Count > 0)
        {
            _processQueue[0].Update(elapsed);
            if(_processQueue[0].Finished)
            {
               _processQueue.RemoveAt(0); 
            }
        }
    }

    private readonly List<Process> _processQueue = new List<Process>();
}

And that's it, although you're free to add more methods, for example to clear the queue or to know how many processes are currently queued. Now just make your Animation class inherit from Process and chain them like:

// On startup
var processQueue = new ProcessQueue();

// Chain a few animations
processQueue.Add(new Animation(...));
processQueue.Add(new Animation(...));
processQueue.Add(new Animation(...));

// On Update
processQueue.Update(elapsed);

Note that you can create and chain all the processes you want before the game starts, because they only really start being executed when you call update for the first time on the queue.

When implementing the Animation class inheriting from Process make sure to set the Finished property to true when the animation ends, so that it will get removed from the queue.

I think that probably already answers your question, but if you're interested in the subject, keep reading.


Bonus - Some Useful Processes!

I might as well drop some useful types of processes for pretty much any type of game! Hopefully these will inspire you to make your own processes too.

First we have the SleepProcess which as the name implies, does nothing for a certain duration of time:

public class SleepProcess : Process
{
    public SleepProcess(float duration)
    {
        _duration = duration;
    }

    public override void Update(float elapsed)
    {
        _timer += elapsed;
        if(_timer >= _duration)
            Finished = true;
    }

    private float _timer;
    private readonly float _duration;
}

And sometimes you don't need a process that spans several frames - a single frame is enough. The following GenericProcess makes that easy:

public class GenericProcess : Process
{
    public GenericProcess(Action action)
    {
        _action = action;
    }

    public override void Update(float elapsed)
    {
        _action();
        Finished = true;
    }

    private readonly Action _action;
}

Mixing Everything Together - An Example

Here's how you can use these processes. Let's say for instance that you would like to play animation A, wait 2 seconds, play animation B, and finally write a message to the debugger. By using the processes above you can implement this entire sequence as four lines of code:

processQueue.Add(new AnimationProcess(...));
processQueue.Add(new SleepProcess(2));
processQueue.Add(new AnimationProcess(...));
processQueue.Add(new GenericProcess(() => Diagnostics.Debug("Message")));

And the only thing you need to do is remember to call Update on the process queue. Can you see how powerful this concept can be when properly used? Pretty much everything in your game can be considered as a process!

What About Looping?

What if I wanted to take the example above and make it loop infinitely? Well, this too, is extremely simple to implement with this framework, using a sort of "delayed recursive" call. Just put the processes you want to loop inside a function, and then add a GenericProcess at the end that adds that calls that function once more:

void LoopProcesses()
{
    processQueue.Add(new AnimationProcess(...));
    processQueue.Add(new SleepProcess(2));
    processQueue.Add(new AnimationProcess(...));
    processQueue.Add(new GenericProcess(() => Diagnostics.Debug("Message")));

    // Call LoopProcesses again after all of the above are finished
    processQueue.Add(new GenericProcess(LoopProcesses));
}

// Call once to start looping
LoopProcesses();
\$\endgroup\$
15
  • \$\begingroup\$ Nice, I had this framework in a game I did once, but with the name of Event, instead of Process. To tell the truth, processes sounds way better for me, I just didn't mind the name in the time :D \$\endgroup\$ Apr 6, 2012 at 3:07
  • 1
    \$\begingroup\$ @codinghands That's the most simple way, yes! But once again that's a design decision with a lot of room for variation. There's even a very interesting thing you can do which is make your ProcessQueue class itself a Process, and give it two different modes of operation - sequence and parallel (although the name queue is no longer appropriate). Then you could just create a ProcessQueue (in parallel mode) and add your other ProcessQueues (in sequence mode) to it, just like as any other process. This is basically the composite design pattern and allows many other tricks too :) \$\endgroup\$ Apr 6, 2012 at 13:58
  • 1
    \$\begingroup\$ Proception! Baaarm! That's a great idea. I really need to read some game design architecture stuff, that wasn't immediately obvious at all. Going to give implementing this a shot right now. Thanks again! \$\endgroup\$ Apr 6, 2012 at 15:59
  • 1
    \$\begingroup\$ @codinghands Don't feel ashamed, I only realized the benefits of treating the queue itself as a process yesterday, when speaking about the subject with a fellow game developer :) You're always learning new tricks in this business. \$\endgroup\$ Apr 6, 2012 at 16:02
  • 1
    \$\begingroup\$ @codinghands This will vary from situation to situation, but I don't think that dependency should be a responsibility of the queue. So I tend to add dependencies to the processes themselves. E.g. pass a reference to A to B's constructor. I think I know what you're trying to do though, but it's a bit hard to explain so compare what I will say with this example. Basically, pass a reference to the character to the animation process, and use the reference to get the character's position but on the first update, not in the constructor (see example in link). \$\endgroup\$ Apr 6, 2012 at 17:56

You must log in to answer this question.

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