开发游戏 - 如何进行多个游戏循环?

时间:2009-10-18 08:01:45

标签: c# language-agnostic events loops

我正在使用C#,但这适用于开发任何语言的游戏。

大多数游戏使用“游戏循环”,看起来像这样:

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

我正在努力理解如何将不止一个游戏循环的东西融入等式中。例如,使能量球从一个瓷砖浮动到另一个瓷砖。或者让玩家移动一个图块(不是从图块跳到图块而是动画到它)。

我提出的最好的事情是花费自上一次循环以来经过的时间,并将其传递给对象/方法,以便它可以做到这一点。但这使得很难做到这样的事情:

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.

在哪里可以找到有关“执行需要多个游戏循环才能完成的事件”的更多信息?

5 个答案:

答案 0 :(得分:1)

一种方法是存储完整的待处理操作,然后让游戏循环只执行一点点操作。从动作引用的游戏对象知道它们所处的状态,因此下一个要执行的位是已知的。虽然还有一些事情需要做,但操作会被添加回要在下一个循环中执行的待处理操作的队列,并且当操作完成后,它将不再被添加回来。

因此,在您的MoveTo示例中,要存储的操作是移动到10, 20,并且每次在整个游戏循环中,AI都会向它移动一点点。你的Shoot示例可能更好地描述为朝着某个方向行进的子弹,然后无论它击中什么都决定了行动是否继续。

我还没有完成游戏开发,所以我不知道这是否是在该领域中完成的,但这是我在基于事件的系统中做这样的事情。

答案 1 :(得分:1)

实际上,你所说的关于使用过去的内容是相当准确的。如果您熟悉C#,并且还没有这样做,我强烈建议您研究一下XNA,或者如果您愿意花一点钱,可以通过“TheGameCreators”来购买Dark GDK .NET。

无论如何,我们的想法是,对于每个游戏循环,您使用自上次更新以来经过的时间更新任何“实时”对象。 “实时”物体是任何仍然被认为是活跃的并且需要更新的物体(例如,在飞行途中的敌人,玩家,子弹,仍在爆炸的爆炸等)。您可以根据经过的时间,碰撞,火灾造成的损坏等确定每个对象应具有的下一个状态,位置,运行状况等,然后实现下一个状态。最后,然后为每个对象调用绘制过程,并以新状态呈现它们。

对于类似玩家射击的事情,这是你可以做的事情。注意,这是比任何更多的伪代码。希望它能给你一个想法。

//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;
    }
}

答案 2 :(得分:1)

你可能不会使用事件;相反,MoveTo或Shoot应被视为状态的变化。您的AI对象需要一个由以下变量组成的状态:

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

在MoveTo方法中,您需要设置对象的状态 - 例如:

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

然后在其Update方法中,您将更新位置。

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

该方法将基于某个计时器(例如每秒60个刻度)从游戏循环调用,因此实际上,该对象每秒移动30个像素。如果它有动画帧,只需用滴答计数倒计时并根据需要更改帧。

对于寻路,要从平铺移动到平铺,您可以更新每个平铺的速度,以便对象沿所需方向移动。

答案 3 :(得分:1)

除了WesleyJohnson和Gannon的解决方案之外,您还可以使用基于任务的方法。 WesleyJohnson和Gannon的解决方案具有较低的复杂性,这是一件好事,尤其是当您的游戏角色的行为被静态定义时。喜欢简单的射击游戏。但是当您希望通过脚本动态定义行为时,或者当您的actor具有复杂行为时,您可能希望将行为管理外部化。 因为你的演员的更新功能必须具有复杂的状态管理。

一种常见的方法是拥有一个名为Task(或Process或Job)的基类 并且特定的更长时间运行的任务子类Job。例如,您可以拥有MoveActorTask,PlayAnimationTask等。通过结果代码和标记它们是否已完成,您还可以以一种方式链接任务,一次一个地执行,等待使用{{3}完成前者}

以下是我们使用的内容,稍加编辑以便更好地阅读,并从一些可能会引起混淆的高级选项中删除:

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;
};

任务由TaskScheduler管理。 TaskScheduler为每个任务(composite tasks)调用任务的execute函数的每一帧,或者您可以有不同的调度策略。

答案 4 :(得分:-2)

考虑操作系统如何允许多个程序在单个处理器上运行:

  • 程序1正在运行
  • 程序1中断
  • 程序1的状态(CPU寄存器的内容等)由内核保存
  • 程序2的状态由内核加载
  • 计划2恢复

这种“中断/保存/恢复/恢复”方法对于真正难以分解成部分的任务来说是一个“最坏情况”选项。在某一时刻(可能基于任务运行的时间),您可以保存所有任务所需的变量,并停止运行代码。稍后,您可以恢复状态并恢复代码。

然而,通常可以设计您的系统,以减少诉诸此类事件的需要。例如,设计动画以便可以一次处理一帧。