【问题标题】:Conceptually, how does replay work in a game?从概念上讲,重放如何在游戏中发挥作用?
【发布时间】:2010-06-17 18:07:54
【问题描述】:

我有点好奇如何在游戏中实现回放。

最初,我认为游戏中的每个玩家/AI 操作都会有一个命令列表,然后它会“重新玩”游戏并让引擎照常渲染。但是,我查看了 FPS/RTS 游戏中的回放,经过仔细检查,甚至粒子和图形/听觉故障之类的东西都是一致的(并且这些故障通常in一致)。

那么这是怎么发生的。在固定摄像机角度的游戏中,我虽然可能只是将整个场景的每一帧都写入一个流,然后存储然后重放该流,但这对于允许您暂停和移动摄像机的游戏来说似乎还不够大约。您必须在所有时间点存储场景中所有内容的位置(不是吗?)。因此,对于粒子之类的东西,需要推送大量数据,这似乎对游戏在玩时的性能产生了重大影响。

【问题讨论】:

  • 最初的星际争霸回放实际上并不一致。您可以将同一场比赛看两次,并看到一些截然不同的结果。
  • @Andres:有趣,我没注意到。特别是,对于 RTS 类型,我想到的是《英雄连》。
  • 澄清我认为 SnOrfus 的问题:有些游戏(神秘海域 2、光环 3,甚至战地 2)允许您完整地录制游戏。游戏结束后,您可以以指定的速度播放它,并在动作发生时飞过关卡,从地图上的任何位置查看它。所以我认为这是关于记录所有玩家/对象的移动,而不是与视频缓冲区有关。
  • @Sean O'Hollaren:是的,没错。
  • 然后我还会为几乎默认重放的赛车游戏添加。我很确定模型的位置被记录下来,然后一切都通过引擎运行。

标签: design-patterns


【解决方案1】:

我认为你最初的想法是正确的。要创建重播,您将从用户接收到的所有输入(以及接收到的帧号)与任何随机数生成器的初始种子一起存储。要重玩游戏,您可以使用保存的种子重置 PRNG,并向游戏引擎提供相同的输入序列(与帧号同步)。由于许多游戏会根据帧之间经过的时间量来更新游戏状态,因此您可能还需要存储每帧的长度。

【讨论】:

  • 帧数可能不是一个很好的参考,因为回放可能以与现场游戏不同的帧率运行。
  • @Ben:帧率没有任何区别,因为帧数仍然相同。这是正确的答案。
  • 图形框架和引擎“框架”(或迭代)不一定相同。在许多旧游戏中,引擎在一个主循环中以与图形相同的速度更新。使用现代引擎时,通常允许图形更新速度与 GPU 允许的一样快,并且引擎会一直保持良好、一致的游戏动态分辨率所需的水平(通常是物理引擎)。
  • @iamgopal:如果你知道伪随机数生成器的状态,那么这个问题就已经解决了。另一种方法可能是将随机数视为另一种输入形式,并将其保存在按键等旁边。
  • 我想强调的是,这种方法需要您的游戏引擎是确定性的,并且它以固定的时间步长运行。我相信所有暴雪的RTS游戏都是这样构建的。非确定性游戏将包括额外的同步数据,以确保长期的一致性。
【解决方案2】:

星际争霸和星际争霸:母巢之战具有重播功能。比赛结束后,您可以选择保存回放以供稍后查看。回放时,您可以在地图上滚动并点击单位和建筑物,但不能改变它们的行为。

我记得曾经看过原版游戏中的一场比赛的回放,但回放是在《母巢之战》中观看的。对于那些不熟悉的人,母巢之战包含所有原始单位和建筑物,以及各种新的。在最初的游戏中,玩家通过创建计算机无法轻易对抗的单位来击败计算机。当我在母巢之战中播放回放时,计算机可以访问不同的单位,它创建并用来击败玩家。因此,根据播放文件的星际争霸版本,完全相同的回放文件会产生不同的获胜者。

我一直觉得这个概念很吸引人。回放功能似乎通过记录玩家的所有输入来发挥作用,并假设计算机每次都会以完全相同的方式响应这些刺激。当玩家的输入被输入到最初的星际争霸回放器中时,游戏的播放方式与原始比赛中的完全一样。当向 Brood War 回放器输入完全相同的输入时,计算机会做出不同的反应,创造出更强大的单位,并赢得了比赛。

如果您正在编写重放引擎,请记住一些事情。

【讨论】:

  • +1:非常有趣。我从来没有听说过。提供了一些关于他们如何开发它的很好的见解。
【解决方案3】:

主要有两种方法:

  1. 存储事件(例如播放器/ai 操作)——正如您所说的那样。
  2. 存储状态(完整的游戏状态,例如对象的位置,在连续的时刻)。

这取决于你想做什么。有时存储事件会更好,因为这通常占用更少的内存。另一方面,如果您想提供可以从不同起点以不同速度播放的回放,最好存储状态。存储状态时,您还可以决定是否在每个事件或 f.e. 之后存储它们。每秒仅 12 或 25 次 - 这可能会减少回放的大小,并使回放/快进变得更容易。

请注意,“状态”并不意味着图形状态。更多类似单位职位、资源状况等。图形、粒子系统等通常是确定性的,可以存储为“动画 X,时间 Y:Z”。

有时重播被用作防止作弊的方案。那么在这里存储事件可能是最好的。

【讨论】:

    【解决方案4】:

    从技术上讲,您应该将引擎编写为确定性的,即没有随机性。假设游戏中的角色瞄准对手的手臂并开火,那么在所有情况下都应该对对手施加相同的伤害。

    假设炸弹在 X 位置引爆,则爆炸产生的粒子应始终产生相同的视觉结果。如果您需要随机性,请创建一组随机数,在玩游戏时选择一个种子值,并将该种子值保存在回放中。

    一般来说,在游戏中具有随机性是个坏主意。即使是多人游戏,你也不能让一半的玩家看到爆炸周围,而其他人则不能,因为他们没有得到正确的随机值。

    让一切都是确定性的,你应该没问题。

    【讨论】:

    • 人工智能呢? AI不是随机的吗?
    • 这真的没有必要。对所有随机事件使用种子伪随机数并将种子保存在回放文件中。这样一来,在回放期间将生成相同的“随机”数字。
    • -1 明确误解了计算机中“随机性”的工作原理
    • 嗯....不....我非常清楚没有“真正的”随机性。然而,大多数人试图通过将他们的随机种子设置为系统时间之类的东西来解决这个问题。但是我要说的是,这种事情不应该做。我不在乎他是否使用系统 API 或预定义的随机数表。我原来说的是对的。他的引擎中的每个函数都应该根据其输入产生相同的结果。时间不应该是一个因素。
    • 如果粒子与游戏机制没有任何有意义的交互,那么 RNG 对它们来说是否不同并不重要。这在网络同步模拟的情况下会有所帮助(就像大多数 RTS 游戏和许多其他类型的游戏一样),因为模拟必须同步每一帧(粒子效果刚刚得到单独更新)。
    【解决方案5】:

    鉴于初始状态一系列带有时间戳的动作,只需遍历序列,因为记录的动作应该会发生重放。

    为了让随机事件以完全相同的方式再次发生,使用种子伪随机数并将种子保存在回放文件中。

    只要您使用相同的算法从种子中生成随机数,您就可以重新创建所有事件,就像它们在现场游戏中发生一样,而无需游戏状态的完整快照。

    这将要求回放按顺序观看,但这对于游戏回放来说是很正常的(参见星际争霸 2)。如果您想允许对时间线进行随机访问,您可以以设定的时间间隔拍摄完整状态快照(比如每分钟),以设定的粒度在时间线上跳跃。

    【讨论】:

    • 如果您每隔一定的秒数(比如 5 或 10 秒)重新播种,那么在回放流中录制并允许向前或向后跳转(到 PRNG“关键帧" 本质上)。
    【解决方案6】:

    NVidia PhysX(游戏中经常使用的物理模拟引擎)能够随时间记录物理场景的完整状态。这包含来自游戏引擎的任何驱动输入,这意味着您不需要像其他人建议的那样跟踪随机数种子。如果您使用此场景转储,您可以在外部工具(由 NVidia 提供)中重放它,这对于追踪物理模型的问题非常方便。但是,您也可以使用相同的物理流来驱动图形引擎,这样您就可以进行正常的相机控制,因为只记录了驱动图形的物理。在许多游戏中,这包括粒子效果(PhysX 包括一些非常复杂的粒子系统。)至于声音,我猜是逐字记录的(作为声音流),但我不确定。

    【讨论】:

      【解决方案7】:

      您最初的想法是正确的,对于真正复杂的效果,它们并没有被完全记住。例如,魔兽争霸3回放系统不存储动画的状态,或者随机效果情况下的粒子效果等。此外,大多数东西可以以确定性的方式从起点开始计算,所以对于大多数系统使用随机变量(例如,给出随机偏移的粒子爆炸),您只需要效果的时间和随机种子。然后,您可以在不知道它最终会是什么样子的情况下重新生成效果。知道它正在通过确定性代码路径。

      纯粹从概念上考虑,要回放事件的时间线,您所需要的只是用户操作。程序将以完全相同的方式做出反应,但随机变量除外。在这种情况下,您可以忽略随机性(效果看起来完全一样是否真的很重要,或者它们可以随机重新生成),或者存储种子值并伪造随机性。

      【讨论】:

        【解决方案8】:

        把我的两便士扔进去。

        取决于你想要什么,回放可以通过

        1. 录制视频缓冲区并稍后重播,
        2. 每帧捕获对象状态并稍后重放,

        大多数时候,人们想要一个交互式回放,所以 2. 是要走的路。然后根据您的限制,有多种方法可以优化此过程

        • 确保系统是确定性模拟*,这样每个输入都会产生一致且预期的输出
        • 如果需要随机性,请确保以后可以准确地复制随机数[查看使用 Pseudo Random Number Generators PRNG 播种,或使用固定随机集]
        • 将游戏元素分为“机械”和“审美”元素。机械元素会影响结果[例如柱子倒下和阻塞路径],美学元素是为了展示,不影响系统中的任何决策过程[例如火花等视觉粒子效果]。

        这真是一个有趣的话题。我记得原来 Xbox Wreckless 的一个发布标题有一个很好的播放功能。不幸的是,重播不止一次会搞砸;)

        哦,是的,怎么会有人忘记Blinx Time Sweeper很棒的互动重播被纳入实际游戏机制!


        *= 似乎有一些关于时间步进的 cmets。我在这里使用“模拟”来捕获此功能。在核心,您的引擎需要能够产生离散的时间帧。即使重播帧的处理时间比原始帧长或短,系统也必须感知相同的时间增量已经过去。这意味着使用每个记录的输入记录帧时间步长,并将此增量提供给您的引擎时钟。

        【讨论】:

          【解决方案9】:

          也许您可以简单地保存每个玩家发送的命令堆栈。因此,您无需保存炸弹在某个时间点引爆或某辆汽车被摧毁,您只需保存每个玩家发送的按键即可。然后,在回放中,您只需模拟游戏,就像在这些压力机上会发生的那样。我觉得这有可能占用更少的空间,但我从来没有开发过这样的回放系统。

          不过,这个问题很有趣。我会对它在职业比赛中的表现感兴趣。

          【讨论】:

            【解决方案10】:

            丹·布莱恩特

            此外,记录随机种子不足以进行倒带 支持,因为随机进程不是可逆过程 在依赖随机性的所有逻辑中没有特殊支持。 将随机操作的结果记录为更灵活 事件流的一部分。

            这正是我最初的想法,当时我试图弄清楚他们是如何做到的,这样游戏的重播每次都一样。有了《毁灭战士》,我想到了拍摄的随机性:D。存储任何使用过的随机数,我发现它可能是一个解决方案。 那是在我看到一篇关于孤岛危机技术的 pdf 论文之前。那里的一些纹理噪声和草或树的配置,似乎是使用带有固定可逆种子的伪随机化来制作它,因此您在任何时候都看不到噪声、树木和草的配置改变!

            避免同时存放数百万棵树和草的竖井位置。 显然伪随机序列可以随时重放,因为逻辑是固定的,只是制造一个假的统计随机数字序列。

            【讨论】:

            • 如果您希望引起 Dan 的注意,请在他的贡献下方添加评论 - 否则他可能不会看到它。
            • 可能是因为我只是一个客人,但我在父帖子中看不到任何“添加评论”功能,Dan回复了,更不用说Dan的回复了。我看到有一个编辑改进功能,即使是不是我的帖子,但这是如何工作的?
            • 啊,好问题! seems here 你需要 50 个代表点来评论你自己以外的问题或答案——我很抱歉。 50 很容易获得 - 通常只需几个有用的贡献就可以实现。是的,您可以编辑其他人的问题和答案,但在您到 2000 年之前,您的编辑将被其他人审核。请查看您的 privileges chart here
            【解决方案11】:

            拥有一致重播的问题与拥有一致的多人游戏相同(嗯,更容易)。

            正如其他人之前提到的,RTS 游戏中的回放是通过记录所有输入来存储的(这有效果。滚动没有效果。) 多人游戏也传输所有输入

            记录所有输入而不仅仅是猜测 - 有一个用于阅读魔兽争霸3回放的库揭示了这一点。

            输入包括此答案的时间戳。

            【讨论】:

            • 不,它与一致的 MP 游戏不同(或更简单)。当您玩 MP 时,游戏通常要求每个人都拥有相同版本的游戏,而存储会话不一定是这种情况(因为它可能与旧版本的游戏一起存储)。如果其中一名玩家是 AI 对手,这一点尤其重要。试想一下,您重玩一场游戏,其中一个单位在新版本中的攻击点仅比记录版本中的多一个。这可能会导致完全不同的结果。
            【解决方案12】:

            我相信游戏会以一定的增量对所有事物(所有事物)的状态进行快照。然后,当回放发生时,可以使用简单的线性插值来填充“洞”。至少我认为会这样。

            您是正确的,记录输入是不可靠的/不能保证相同的输出。游戏肯定要跟踪所有对象(或至少是重要对象)的状态

            【讨论】:

            • 不,输入相同的输入将产生与第一次完全相同的结果。你只需要确保你得到正确的时间,在最初接收到的相同帧之间提供输入。定期保存整个游戏状态可能需要大量内存并产生不一致的结果。
            • @Peter,“提供相同的输入将产生完全相同的结果”:不。许多游戏中存在随机元素,每次重播时可能会有所不同。您需要跟踪的不仅仅是输入。
            • 确实如此。您还需要存储 PRNG 的种子(请参阅我对这个问题的回答)。
            • 我知道它会消耗性能和内存,但是如果您错过了输入或随机生成器方面的一件小事……或其他任何事情,重播将会非常糟糕!
            • @BlueRaja,Bob 的内存快照想法不一定那么牵强,尽管一个好的引擎可以记录状态“增量”,而不是为每次迭代编码所有内存。这可能更容易在引擎级别支持。此外,记录随机种子不足以支持倒带,因为在依赖随机性的所有逻辑中,如果没有特殊支持,随机进程不是可逆过程。将随机操作的结果记录为事件流的一部分更加灵活。
            猜你喜欢
            • 2023-03-22
            • 1970-01-01
            • 1970-01-01
            • 2011-02-10
            • 2013-11-26
            • 1970-01-01
            • 2017-06-14
            • 2012-12-24
            • 2010-09-28
            相关资源
            最近更新 更多