通过线程实现游戏引擎确定性

时间:2013-03-15 06:45:22

标签: c++ multithreading game-engine deterministic

我想在我的游戏引擎中实现确定性,以便能够保存和重放输入序列并使网络更容易。

我的引擎目前使用一个可变的时间步长:每帧我计算更新/绘制最后一个并将其传递给我的实体的更新方法所花费的时间。这使得1000FPS游戏看起来像30FPS的快速游戏,但引入了不确定的行为。

解决方案可能是将游戏修复为60FPS,但这会使输入更加延迟,并且无法获得更高帧率的好处。

所以我尝试使用一个线程(不断调用update(1)然后休眠16ms)并在游戏循环中尽可能快地绘制。它有点有效,但它经常崩溃,我的游戏变得无法播放。

有没有办法在我的游戏循环中实现线程来实现确定性,而不必重写所有依赖于引擎的游戏?

4 个答案:

答案 0 :(得分:5)

您应该将游戏框架与图形框架分开。图形框架应该只显示图形,没有别的。对于重放,无论您的计算机能够执行多少图形帧,无论是每秒30帧还是每秒1000帧,重放计算机都可能以不同的图形帧速率重放它。

但你确实应该修复游戏框架。例如。每秒100个游戏帧。在游戏框架中,游戏逻辑被执行:与游戏相关的东西(以及重放)。

只要没有必要的游戏框架,你的游戏循环应该执行图形框架,所以如果你将游戏修改为每秒100个游戏帧,那么每个游戏帧只需0.01秒。如果您的计算机在游戏框架中只需要0.001来执行该逻辑,那么剩下的0.01秒将用于重复图形框架。

这是一个很小但不完整但不是100%准确的例子:

uint16_t const GAME_FRAMERATE = 100;
uint16_t const SKIP_TICKS = 1000 / GAME_FRAMERATE;

uint16_t next_game_tick;

Timer sinceLoopStarted = Timer(); // Millisecond timer starting at 0
unsigned long next_game_tick = sinceLoopStarted.getMilliseconds();

while (gameIsRunning)
{
    //! Game Frames
    while (sinceLoopStarted.getMilliseconds() > next_game_tick)
    {
        executeGamelogic();

        next_game_tick += SKIP_TICKS;
    }

    //! Graphical Frames
    render();
}

以下链接包含有关创建精确游戏循环的非常完整的信息:

http://www.koonsolo.com/news/dewitters-gameloop/

答案 1 :(得分:4)

要成为网络中的确定性,您需要一个真实点,通常称为“服务器”。游戏社区中有一种说法是“客户掌握在敌人手中”。确实如此。你不能相信在客户端上为公平游戏计算的任何东西。

例如,如果您的游戏变得更容易,如果由于某些原因您的线程每秒更新59次而不是60次,那么人们会发现。也许在一开始他们甚至不会是恶意的。他们当时只是让他们的机器处于满负荷状态,而且你的过程没有达到每秒60次。

一旦你拥有一台服务器(甚至可能在进程中作为单个播放器中的线程),它不关心图形或更新周期并按照自己的速度运行,它的确定性足以至少得到相同的结果适用于所有玩家。基于计算机不是实时的事实,它可能仍然不是100%确定性的。即使你告诉它更新每个$频率,它也可能不会,因为计算机上的其他进程占用了太多负载。

服务器和客户端需要进行通信,因此服务器需要将其状态的副本(性能可能是最后一个副本的增量)发送到每个客户端。客户可以以最佳速度绘制此副本。

如果您的游戏与线程崩溃,也许可以选择将“服务器”实际放入进程并通过网络进行通信,这样您就会发现速度非常快,哪些变量需要锁定,因为如果您只是将它们移动到另一个项目,您的客户端将不再编译。

答案 2 :(得分:2)

将游戏逻辑和图形分成不同的线程。游戏逻辑线程应以恒定速度运行(例如,它每秒更新60次,如果你的逻辑不太复杂,甚至更高,以实现更流畅的游戏)。然后,您的图形线程应始终尽可能快地绘制逻辑线程提供的最新信息,以实现高帧率。

为了防止绘制部分数据,您应该使用某种双缓冲,其中逻辑线程写入一个缓冲区,而图形线程从另一个缓冲区读取。然后每次逻辑线程完成一次更新时切换缓冲区。

这应该确保您始终充分利用计算机的图形硬件。当然,这确实意味着你对最低cpu速度施加了限制。

答案 3 :(得分:0)

我不知道这是否会有所帮助,但如果我没记错的话,Doom会存储您的输入序列并用它们来生成AI行为和其他一些东西。 Doom中的演示块将是一系列数字,表示不是游戏的状态,而是你的输入。从那个输入,游戏将能够重建发生的事情,从而实现某种决定论......虽然我记得它有时会失去同步。