1
\$\begingroup\$

I'm trying to find a way to deal with throttling/managing actions that need to happen on a regular (regulated) basis.

Right now, if I want to throttle an "action" in MonoGame I need to setup a "timer".

Let's say I want to spawn a bunch of explosions during the update loop, but I don't want to spam them. I would setup a variable to track the countdown to the next time I can spawn an explosion, and another variable for the time between:

public override void Update(GameTime gameTime)
{
    // ExplosionTimer is a class variable, defaulted to 0
    // ExplosionTimerMax is a class variable set to something like 1000 milliseconds
    ExplosionTimer += elapsedGameTime;

    if (ExplosionTimer >= ExplosionTimerMax)
    {
        // Perform actions
        ExplosionSound.Play();
        SpawnExplosion();

        // Reset the timer
        ExplosionTimer = 0;
    }

    // Other update code
    ...
}

This is pretty easy to setup and straightforward, but is there a better way? If I need to setup 5 different timers, that means I need to define 10 variables, increment them all, run checks for their state, and reset them if needed.

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

I usually create a class called a DelayedAction like this:

public class DelayedAction : IUpdatable
{
    public DelayedAction(Action action, float delay)
    {
        TimeRemaining = delay;
        Action = action;
    }

    public Action Action { get; private set; }
    public float Delay { get; private set; }
    public float TimeRemaining { get; private set; }

    public bool Update(float deltaTime)
    {
        TimeRemaining -= deltaTime;

        if (TimeRemaining <= 0)
        {
            Action();
            return false;
        }

        return true;
    }
}

Then I add everything that implements the IUpdatable interface to a list:

_updatables.Add(new DelayedAction(PlaySound, 1.0f));

That gets executed in a loop in the update method, and removed if it returns false:

foreach(var updatable in _updatables.ToArray())
{
    if(!updatable.Update(deltaTime))
         _updatables.Remove(updatable);
}

It's a little more complicated than that for optimization reasons, but the above implementation should work.

Obviously, the action that gets fired can either just play the sound, or you can use it to spawn a new one like the code in your question. You can also queue up a bunch of things with different delays for various effects.

_updatables.Add(new DelayedAction(PlaySound, 1.0f));
_updatables.Add(new DelayedAction(PlaySound, 2.0f));
\$\endgroup\$
4
  • \$\begingroup\$ I really hope that anything that is a "delayed action" doesn't change any of your game state. Otherwise you're going to get some really really hard to track down bugs where things change when you're not expecting them to. Especially if you have multiple threads. \$\endgroup\$
    – Matt D
    Jun 21, 2013 at 5:09
  • \$\begingroup\$ Yeah, I agree this approach doesn't lend itself very well to multiple threads. I've never needed to use it that way. Maybe it's not clear out of context, but I can't recall running into any particularly difficult game state bugs (yet :) \$\endgroup\$ Jun 21, 2013 at 5:35
  • \$\begingroup\$ Doing something which has no deterministic impact, at any time, will be fine (eg: playing audio). Doing something which does have a deterministic impact out of sequence will break your determinism (moving objects, game events, etc), so things like replays, and networking may drift out of sync depending on what you're doing. Those subtle sync bugs are really hard to find. \$\endgroup\$
    – Matt D
    Jun 21, 2013 at 5:37
  • \$\begingroup\$ Ah, right. That explains it. I've only really used this for animations, sounds and non-deterministic things as you say. I'll keep your advice in mind if I ever catch myself doing otherwise ;) \$\endgroup\$ Jun 21, 2013 at 5:47
1
\$\begingroup\$

What you want to be able to do, is script game events. And I'm using script in the loosest possible way. The way I prefer to do this is using an "Event" or some other asynchronous construct to communicate things between systems. I like to do this because it limits the coupling between systems.

Lets use the "Set of a lot of explosions" concept.

Something happens in your game, and you now want to fire off 5 explosions (using the FireExplosion event) over a 3 second period. and you want these explosions to be placed randomly within a specified area (2D Rect). The game logic builds up a "Event" which contains this information and flicks it in the general direction of your "Scripted Sequence" system.

The Scripted Sequence system grabs the event when it updates next, and queues up 5 "FireExplosion" events, evenly spaced in time across the 3 second period, with the desired "2D Rect" payload data.

Each frame, your Scripted Sequence System will check to see if any events need to be raised to the Game. If there are, it then generates the required events (In our case the FireExplosion event), assigns the payload and flicks it into the Game's Event queue for processing on the next update. Next frame, It'll check to see if anything's ready to go, and so on. Eventually, this system will raise 5 events over 3 seconds as requested!.

The Game, on its next update, duly processes the queue of incoming events, and sets up an Explosion, as requested by the incoming FireExplosion Event..

This is a neat paradigm to use for a few reasons.

  1. If you're using a networked game you can sync up things to happen at specific points in time in the future. Useful for "End Of Game" events and what not.
  2. It also means you can record what goes through this system and play it back at any point. giving you a neat way to setup repeatable tests.
  3. You can easily expand on it to do simple cutscenes.
  4. Or even implement a proper scripting language to respond to in game events!
\$\endgroup\$
5
  • \$\begingroup\$ I agree that this answer is a good system design, but it's not clear how it solves the "delay" problem? \$\endgroup\$ Jun 21, 2013 at 5:37
  • \$\begingroup\$ Ah, I read that as "throttling explosion sounds". Not "throttling explosions, sounds". Important things commas. I'll come back and edit this in a bit :) This kind of system is really useful for throttling how many sounds you're playing at once though. which is slightly but not really useful. ill get back to you later :D \$\endgroup\$
    – Matt D
    Jun 21, 2013 at 5:40
  • \$\begingroup\$ Actually, maybe your interpretation of the question is correct. We'll have to see what the asker (John) says when he comes back. \$\endgroup\$ Jun 21, 2013 at 5:51
  • \$\begingroup\$ This is a really great answer, but my question was not about scripting actions, but just limiting their spawn rate. So if I have a vehicle that is "exploding", I want it to spawn explosions randomly around it for a few seconds before it disappears from the game world. But I want to spawn the explosions every 0.25 seconds, bot every update loop, otherwise there would be too many, not to mention the problem with spawning the sounds that many times. \$\endgroup\$
    – John B
    Jun 25, 2013 at 17:00
  • \$\begingroup\$ The whole point is that you can dynamically "script" explosions to happen over time. rather than do it procedural. its the same thing just handled outside your game loop \$\endgroup\$
    – Matt D
    Jun 25, 2013 at 23:31

You must log in to answer this question.

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