views:

382

answers:

5

I'm using C#, but this is applicable to developing a game in any language.

Most games use a 'game loop', which would look something like this:

while (game is running)
{
  UpdateEnvironment();
  DrawEnvironment();
}

I'm struggling to understand how things which would take more than one game loop fit into the equation. For example, making a ball of energy float from one tile to another. Or having a player move one tile (not jump from tile to tile but animate to it).

The best thing I have come up with is taking the time elapsed since the last loop, and passing it to the object / method so it can do its thing. But that makes it hard to do things such as:

AI.MoveTo(10, 20); // Pathfind, then walk the path to this tile.
Player.Shoot(); // Shoot a bullet, and detect collisions and update along the way.

Where can I find more information on 'performing events which take more than one game loop to accomplish'?

+1  A: 

One way to do it is to store the complete pending action and then have the game loop perform only a little bit of it. The game objects referenced from the action know the state they are in, so the next bit to perform is known. While there is still something left to do, the action is added back to the queue of pending actions to be performed in the next loop, and when the action is complete, it is no longer added back.

So in your MoveTo example, the action to store is the move to 10, 20 and every time across the game loop, the AI is moved a little bit towards it. Your Shoot example is probably better to describe as the bullet traveling in a certain direction, and then whatever it hits determines whether the action is continued or not.

I haven't done game development, so I don't know whether this is how it's done in that field, but this is the way I would do something like this in an event-based system.

jk
+1  A: 

Actually, what you said about using the elapsed is fairly accurate. If you're familiar with C#, and aren't already doing so, I would highly recommend you look into either XNA or if you're willing to spend a little money, Dark GDK .NET by "TheGameCreators".

Anyway, the idea is that for each game loop, you update any "live" objects using the elapsed time since the last update. "Live" objects being anything that is still considered active and needs updating, (e.g., enemies, players, bullets in mid flight, explosions that are still going off, etc). You determine what the next state, position, health, etc each object should have based on the elapsed time, collisions, damage from fire, etc and then implement that next state. Finally, you then call the draw process for each of these objects, and render them in their new states.

For something like a player shooting, here is something you could do. Note, this is more pseudo code than anything. Hope it gives you an idea.

//Generic Game Class
public class MySweetGame : Game
{
    Player _player = new Player(this);
    List<Bullet> _bullets = new List<Bullet>();
    public List<Bullet> AllBullets()
    {
     get { return _bullets; }
    }


    public void Update()
    {
     //You would obviously need some keyboard/mouse class to determine if a click took place
     if(leftMouseButtonClicked)
     {
      _player.Shoot();
     }

     //This would be assuming you have an object collection or array to hold all the bullets currently 'live' like the above '_bullets' collection
     //This is also assuming elapseGameTime is some built in game time management, like in XNA
     foreach(Bullet blt in _bullets)
     {
      blt.Update(elapsedGameTime);
     }
    }
}

//Generic Player Class
public class Player()
{
    Vector2 _position = new Vector2(0,0);
    int _ammunition = 50;
    MySweetGame _game;

    public Player(MySweetGame game)
    {
     _game = game;
    }

    public void Shoot()
    {
     if(_ammunition > 0){
      _game.AllBullets.Add(new Bullet(50, _position));
      _ammunition--;
     }
    }
}

//Generic Bullet Class
public class Bullet()
{
    float _metersPerSecond = 0;
    Vector2 _position = new Vector2(0, 0);

    public Bullet(float metersPerSecond, Vector3 position)
    {
     _metersPerSecond = metersPerSecond;
     _position = position;
    }

    //Here is the meat you wanted
    //We know the speed of the bullet, based on metersPerSecond - which we set when we instantiated this object
    //We also know the elapsedGameTime since we last called update
    //So if only .25 seconds have passed - we only moved (50 * .25) - and then update our position vector
    public void Update(float elapsedGameTime)
    {
     distanceTraveled = metersPerSecond * elapsedGameTime;
     _position.x += distanceTraveled;
    }
}
WesleyJohnson
A: 

Consider how operating systems allow multiple programs to run on a single processor:

  • Program 1 is running
  • Program 1 is interrupted
  • Program 1's state (contents of CPU registers and such) is saved by the kernel
  • Program 2's state is loaded by the kernel
  • Program 2 resumes

This "interrupt/save/restore/resume" approach is a "worst-case" option for tasks that are really hard to break up into parts. At one point (perhaps based on how long a task has been running), you save all of the variables the task needs, and stop running the code. Later on, you can restore the state and resume the code.

However, often it is possible to design your system in a way that reduces the need to resort to something like this. For example, designing animations so that they can be processed one frame at a time.

Artelius
This sounds like a __horrible__ idea. Preemptive multi tasking is far, far too complex for such a simple thing as incrementing animation each frame (for example)
Mike Cooper
I was using it by way of explanation only. For things like path finding you may want to interrupt the number crunching if it's been going on too long, a kind of co-operative yielding I guess.
Artelius
+1  A: 

You probably wouldn't use events; rather, MoveTo or Shoot should be thought of as a change in state. Your AI object would need a state consisting of variables like this:

class AI
{
   StateEnum State; //Idle, Moving, Attacking, Dying, etc.
   PointF Location;
   PointF Velocity;
   PointF Destination;

In your MoveTo method, you would need to set the object's state -- something like:

   void MoveTo(x, y)
   {
      Destination = new PointF(x, y);
      Velocity = new PointF(0.5, 0);
      State = StateEnum.Moving;
   }

Then in its Update method, you would update the location.

   void Update()
   {
      switch (State)
      {
         case StateEnum.Moving:
            Location.Offset(Velocity); //advance 0.5 pixels to the right
            break;
         default:
            break;
      }
   }
}

That method would be called from the game loop based on some timer (e.g. 60 ticks per second), so in effect, the object is moving 30 pixels per second. And if it has animation frames, simply count down with the ticks and change the frame as needed.

As for pathfinding, to move from tile to tile, you could update the velocity at each tile so that the object is moving in the desired direction.

David Gannon
+1  A: 

Apart from the solutions by WesleyJohnson and Gannon, you can also use a task based approach. WesleyJohnson's and Gannon's solution has less complexity which is a good thing especially when the behaviour of your game's actors are statically defined. Like with a simple shooter game. But when you want the behaviour to be defined dynamically through scripting or when your actors have complex behaviours you may want to externalise behavioural management. Because else your actors' update functions have to have a complex state management.

A common approach would be to have a base class called Task (or Process or Job) And specific longer running tasks subclass Job. For instance you can have a MoveActorTask, PlayAnimationTask etc. With result codes and marks whether they are finished, you can also chain Tasks in a way that they are executed one at a time waiting for the former to be finished by using composite tasks

Here is what we use, slightly edited for better reading and stripped from some advanced options that may confuse else:

class Task
{
public:

    /**
     * Constructor.
     *
     *  @param isDiscardable Set this true, if the Task's goal can be reached in a single step.
     *         For instance if a Task is supposed to slowly close a window by fading
     *         its alpha to 0, then it is discardable, and Task#discard will just finish
     *         the process by closing the window.
     *
     *  @param destroyWhenDone Set this to true, when the TaskScheduler shall delete the
     *         Task, after execution is finished. This should usually be the case, but
     *         sometimes it is sensible to pool a number of Jobs for reuse.
     */
    Task(bool isDiscardable, 
        bool destroyWhenDone);

    virtual ~Task();

    /**
     * This is the function in which the Task is supposed to do whatever it is supposed to do.
     * This function is called by the TaskScheduler at most once per frame. The frequency depends
     * on the Job's priority given with TaskScheduler#addTask.
     * @param time the time source time, since the last call of this function.
     * @return true, when the Task is done, false else. If false is returned, the Task will be
     * rescheduled for another execution.
     */
    virtual bool execute(Time time) = 0;

    virtual TimeSource::TimeSourceType getTimeSource() const = 0;

    /// Returns whether the Task can be removed from the queue by the scheduler,
    bool isDiscardable() const;

    /// Returns true, if the Task shall be deleted, if the Job is finished. Returns false else.
    bool destroyWhenDone() const;

    /// Finish whatever the Task is doing. It won't get a chance to continue.
    /// Overloaded functions must *not* call this implementation.
    virtual void discard();

protected:
    bool mIsDiscardable;
    bool mDestroyWhenDone;
};

The Tasks are managed by a TaskScheduler. Each frame the TaskScheduler calls the task's execute function for all tasks (Round robin) or you can have a different scheduling strategy.

haffax