编写游戏循环的困惑

Posted

技术标签:

【中文标题】编写游戏循环的困惑【英文标题】:Confusion with writing a game loop 【发布时间】:2017-06-07 17:58:57 【问题描述】:

我正在开发一个 2D 视频游戏框架,但我以前从未编写过游戏循环。我看过的大多数框架似乎都实现了drawupdate 方法。

对于my project,我实现了一个调用这两个方法的循环。我注意到在其他框架中,这些方法并不总是被称为交替。一些框架的update 运行方式比draw 运行方式要多。此外,大多数这些类型的框架将以 60FPS 运行。我想我需要在这里睡一会儿。

我的问题是,实现这种循环的最佳方法是什么?我是先拨打draw,然后拨打update,还是相反?就我而言,我正在编写一个围绕SDL2 的包装器,所以也许该库需要以某种方式设置一些东西?

这是我正在考虑的一些“伪”代码。

loop do
  clear_screen
  draw
  update
  sleep(16.milliseconds)
  break if window_is_closed
end

虽然我的项目是用Crystal-Lang 编写的,但我更希望找到一个可以应用于任何语言的通用概念。

【问题讨论】:

没有直接关系,但您可能需要重新考虑拥有完整的clear_screen 步骤。这可能很昂贵,而且并不总是必要的。调用者可能只想清除更改的小部分以节省 CPU 时间。 至少对我来说,如果更新更有意义,那就画吧。如果反过来,您将始终绘制前一帧而不是当前帧。 好的,有道理!这些是我正在寻找的东西。谢谢:D 【参考方案1】:

这取决于您想要实现的目标。有些游戏更喜欢游戏逻辑比帧率运行更频繁(我相信Source游戏会这样做),对于某些游戏,您可能希望游戏逻辑运行频率更低(我能想到的唯一例子是服务器一些多人游戏,非常有名的守望先锋)。

同样重要的是要考虑这是一个分辨率的问题,而不是速度问题。逻辑速率为 120 和帧速率为 60 的游戏不一定以 x2 速度运行,游戏逻辑中的任何时间关键操作都应相对于时钟*而不是 tic 速率来完成,否则如果出现以下情况,您的游戏将真正进入慢动作帧渲染时间过长。

我建议编写这样的循环:

loop do
    time_until_update = (update_interval + time_of_last_update) - current_time
    time_until_draw = (draw_interval + time_of_last_draw) - current_time
    work_done = false

    # Update the game if it's been enough time
    if time_until_update <= 0
        update
        time_of_last_update = current_time
        work_done = true
    end

    # Draw the screen if it's been enough time
    if time_until_draw <= 0
        clear_screen
        draw
        time_of_last_draw = current_time
        work_done = true
    end

    # Nothing to do, sleep for the smallest period
    if work_done == false
        smaller = time_until_update

        if time_until_draw < smaller
            smaller = time_until_draw
        end

        sleep_for(smaller)
    end

    # Leave, maybe
    break if window_is_closed
end

您不想每帧等待 16 毫秒,否则如果该帧需要很长时间才能完成,您最终可能会过度等待。 work_done 变量是为了让我们知道我们在循环开始时计算出的间隔是否仍然有效,我们可能已经完成了 5ms 的工作,这将完全关闭我们的睡眠,所以在那种情况下我们会返回并计算新值。

* 你可能想把时钟抽象化,直接使用时钟会有一些奇怪的效果,比如你保存游戏,把上次使用神通的时间保存为时钟时间,它会立即消失加载保存时的冷却时间,因为现在是过去的几分钟,几小时甚至几天。被操作系统挂起的进程也存在类似问题。

【讨论】:

哇!感谢您的详细解释。这完全有道理。我认为我的示例缺少很多内容,但这就是混乱的地方。就我而言,这些游戏都是基本的 2D 游戏,所以没有什么像守望先锋这样的大型游戏。此外,帧率不需要太疯狂,因为使用此框架制作的游戏不会真的太大。我会尝试这样实现,看看效果如何。我应该从 60 的 update_interval 和 120 的 draw_interval 开始吗? 我假设您的意思是每秒 120 次迭代,而不是 120 毫秒(每秒只有 8.3 次迭代)。我会以 60/s 的速率开始,如果硬件允许,我会提高更新速率,它可以提高游戏的精度(例如,在许多物理系统中,它可以防止物体穿过墙壁)。提高绘制率没有什么意义,因为世界上很少有显示器能够超过 60fps。 (虽然可能为拥有这些监视器的人提供一个选项?) 我实际上只是指原始值。在您的示例中,您为 update_intervaldraw_interval 变量定义了一个变量。我很好奇您会建议这些变量的起始值。另外,在处理像current_time 这样的时间时,你建议使用秒吗?还是应该是毫秒? 我总是使用毫秒,对于大多数游戏来说,秒是巨大的,它可能会混淆诸如动画再生健康条之类的事情。对于原始值,我将使用间隔的实际大小,因此每秒 60 次迭代将是 1000/60 = 16/17 毫秒。

以上是关于编写游戏循环的困惑的主要内容,如果未能解决你的问题,请参考以下文章

为 ncurses 游戏编写游戏循环?

用 Python 编写游戏循环的正确方法是啥?

如何在 Haskell 中编写游戏循环?

如何在纯 Haskell 中编写简单的实时游戏循环?

实验任务5-编写猜数游戏

在循环中对promises感到困惑