Best way for queueing game events

I’m writing skeeball controller software for my SkeeBall Model H (game thread here: http://forum.arcadecontrols.com/index.php/topic,156300.0.html).

I’m trying to figure out the best way to develop something like a queue of game actions that i want to happen one after the other, but I’m having some issues figuring out the best way to signal “completeness”.

A simple example is something like this:

public class SomeSkeeballGame { 

    private int _ballsLeft = 1
    private BonusWheel _wheel;

    ...

    private void Initialize() 
    { 
        _wheel = new BonusWheel(...);
        _wheel.SpinCompleted += SpinCompleted;
       Components.Add(_wheel);
    } 
    private void SpinCompleted(object sender, SpinCompletedEventArgs e)
    {
           if(e.IsBomb)
           { 
                  PlayBombSoundEffect();
                  GameState.Score = 0;
           } 
            else 
           { 
                 GameState.Score += e.BonusPoints;
           } 
    } 
    protected override void Update(GameTime gameTime)
    { 
          switch(GameState.Status)
          {
                   if(_ballsLeft == 0 || GameStatus.TimeLeft == 0)
                   { 
                         CheckHighScore();
                         EndGame();
                   } 
                    case GameStatus.InProgress:
                    {
                           if(CheckKey(Keys.Hole40))
                           { 
                                     SpinBonusWheel();
                                     _ballsLeft--;
                           }  
                    }

          } 

    } 

If the user throws a skeeball and hits the 40 pocket, spin a bonus wheel and apply the score. Problem is, they way it’s written, the ball count will be deducted when the pocket is scored, and the bonus wheel will spin, but on the very next update cycle the high score screen will be displayed before the bonus wheel has spun and was scored.

Sure, I can think of hacks like set a flag and check it in the bonus wheel “spin completed” event, but that’s very non-generic.

There are other games where I want things to happen first. LIke:

If the user hits a pocket
Play an animation… WHEN THIS IS COMPLETELY DONE
Play a sound … and light up an indicator … WHEN THE SOUND IS COMPLETELY DONE
Either end the game or subtract a ball depending on the status.

So I want some kind of queued game action system when I can queue up events and have them play through until no more are left. I’ve designed soemthing like this, but I’m having trouble figuring out how to signal that an event has finished, when an event might be made up of multiple asynchronous calls that all need to finish before the next action in the queue is processing.

Any suggestions along these lines?

I’d say its about it making sense there is nothing inherently non generic about flags.
Define non generic in your own specific applicable context…
The user fires a ball you set a flag.
The ball hits a target you set a different flag.
The user is in a game mode of a type A you set a flag.
You enter a condition check phase and check for flag a b c you trigger a action.
Or
You check for everything that fullfils a specific set of conditions and do something all at once.
You enter a different game mode now flag c is never true.
Its basically the same with the exception that one is far easier to deal with.
Be careful with thinking events equal generic fixes.

I think I get your problem. Had the same ugly code when setting up multi-player game. Lots of states and transitions. All in the update loop. Wrote this here https://github.com/UnterrainerInformatik/FiniteStateMachine maybe that helps. Maybe you can build on it. Lacks split and join though (no parallel states).

And like @willmotil stated: flags are not evil either.

wilmotl:
I hear what you’re saying, but my software is really a bunch of mini games. It’s got multiple skeeball game modes. And I’ve got a SkeeballGame base class that handles a lot of the generic functionality and state tracking. THis is because there aren’t too many options here… I’m tracking when balls go in holes. The game modes are built on top of that engine.

I’ve also designed it to work as an SDK as well to let others write games for it, so coming up with any kind of generic service or “event queue” or something makes a lot of sense.

It’s a lot of, ball goes in hole, something happens (that may be instantaneous or not). If it’s not instantaneous, I want to finish whatever it was before the next “action” or event happens . There’s got to be a decent way to do this generically.

@throbax
That’s a great generic state transition… but not exactly what I’m looking for. Flags may be the way to go in the end, but I’m going to see if I can think of something a little more generic. If I come up with a solution I’ll post it here.

so I came up with a solution. It’s not perfect, but it works.

Here’s one scenario why I needed this:

For example, on the last ball of the skeeball game, the user hits the 40 pocket which causes the following to happen.

  1. The bonus wheel spin is initiated
  2. The remaining ball count is decremented and is now 0
  3. The update loop sees the ball count is 0 and calls EndGame
  4. Game Ends
  5. If the user has a high score, the high score screen is displayed.
  6. The bonus wheel spin completes a few seconds later.
  7. if the bonus wheel landed on “Add x Balls”, the remaining balls count is incremented.
  8. If the bonus wheel landed on one of the X Points wheels, the score in incremented by X.

Notice how 6-8 are out of order due to the way the game loop ends. If the bonus wheel landed on a “bomb” (which clears score to 0), the user is registering an invalid high score. IF they had 0 points and the wheel landed on 1000 points, they wouldnt have the opportunity to register the high score, is it recorded 0 at game end.

There are also plenty of things that could happen that could supercede game ending. Maybe a certain game has a special “WINNER!!!” animation if your score is above X, or there’s some kind of sound effect that should play and pause for effect before the “game over” sign comes up.

So I created a GameEventPipeline component that allows submission of game events to a queue. This is really useful for queued events like "WHen the user hits a bomb, play the bomb sound effect. When the sound effect ends, decrement the score. When that’s done, do the next thing .)

I have 2 game event priorities, high and low. Highs are always processed first. Lows will wait until there are no highs.

For my example above, I add the “Spin wheel” to the pipeline with HIgh priority.
The game then detects game end and EndGame to the pipeilne with low priority.
When the wheel spin completes, it adds whatever the action for the spin outcome is (add balls, add score, play a sound effec,t whatever) as high priority to the pipeline.

Lets say the out come of the wheel spin was "1000 Points). We added "AddPoints(1000) to the pipeline as high priority)
Immediately after pipeline.ProcessNext() is called.

The queue processes “AddPoints(1000)” as it is high priority and while there is “EndGame” in the low priority queue, there is nothing else in the high prilority queue. It plays the sound effect and adds the score. Then it calls Pipeline.ProcessNext().

There are no more high priority items, so it finds the EndGame event and processes game end.

code for pipeline and game event below.

public class GameEvent
{
    public GameEventHandler Handler { get; private set; }
    public bool IsInstant { get; private set; }
    public GameEvent(GameEventHandler handler, bool isInstant)
    {
        Handler = handler;
        IsInstant = isInstant;
    }
}

public class GameEventPipeline
{
    private Queue<GameEvent> _highPriority;
    private Queue<GameEvent> _lowPriority;
    private GameEvent _currentEvent;
    public bool Processing { get { return _currentEvent != null; } }
    public GameEventPipeline()
    {
        _highPriority = new Queue<GameEvent>();
        _lowPriority = new Queue<GameEvent>();
    }
    
    public void Add(GameEventHandler gameEventHandler, GameEventPriority priority, bool isInstant = false)
    {
        if(priority == GameEventPriority.Low)
        {
            _lowPriority.Enqueue(new GameEvent(gameEventHandler, isInstant));
        }
        else
        {
            _highPriority.Enqueue(new GameEvent(gameEventHandler, isInstant));
        }
        
        if(this.Count ==1)
        {
            ProcessNext();
        }
       
    }
    public void ProcessNext()
    {
        _currentEvent = null;
        if(_highPriority.Count > 0)
        {
            _currentEvent = _highPriority.Dequeue();
        }
        else if (_lowPriority.Count > 0)
        {
            _currentEvent = _lowPriority.Dequeue();
        }
        if (_currentEvent != null)
        {
            _currentEvent.Handler.Invoke();
            if (_currentEvent.IsInstant) { ProcessNext(); }
        }
    }
    public int Count { get { return _lowPriority.Count + _highPriority.Count + (_currentEvent == null ? 0 : 1); } }
    public void Clear()
    {
        _currentEvent = null;
        _lowPriority.Clear();
        _highPriority.Clear();
    }
}

I also created an extension method for sound effect to notify when playing is competed and fire an action. This comes in useful for the pipeline:

public static class MediaExtensions
{
    public static void Play(this SoundEffectInstance instance, Action completedAction)
    {
        var t = new System.Timers.Timer(100);
        t.Elapsed += (o, e) =>
        {
            if (instance.State == SoundState.Stopped)
            {
                t.Stop();
                completedAction.Invoke();
                t.Dispose();

            }
        };
        instance.Play();
        t.Start();
    }
}

Usage of both of these:

Pipeline.Add(()=>
        { helper.Get<SoundEffect>("Bomb").CreateInstance().Play(()=>
            {
                 Pipeline.ProcessNext();
            });
          }, GameEventPriority.High);

This queues a “Bomb” sound effect as a high priority game event. When it’s done playing, it advances the pipeline.