【问题标题】:Simulating virtual worlds: continuous or discrete steps?模拟虚拟世界:连续步骤还是离散步骤?
【发布时间】:2012-08-18 08:23:42
【问题描述】:

我正在制作类似于 Polyworld 的东西,这意味着我将模拟虚拟世界,小爬行者在其中跑来跑去、吃东西和进化。我正在用 Node.js 做这个,我计划使用物理和神经网络,但我不确定更新世界的最佳方式是什么,更具体地说,更新函数是否应该接收增量时间作为参数,还是每次都做同样的事情,与他们最后一次打电话的时间无关?两种方式都有什么好处?

编辑: 我反对持续更新的一点是,我想实现某种间隔,例如,每 20 模拟秒产生一个食物块。如果 dt 与 1 不同(或 1 的一小部分),这将永远无法精确工作。

再一次,如果我使用离散更新,更新不关心已经过去了多少时间,我将无法“减慢时间”。当我让它在强大的服务器上工作并在浏览器中渲染时,我认为更新会经常发生,我需要一种在不影响模拟的情况下减慢时间的方法,这样我就可以看到发生了什么。

【问题讨论】:

    标签: javascript performance node.js simulation


    【解决方案1】:

    您的更新函数应该在每个时间步执行相同的操作。

    这两种方法都有缺点,但是在模拟群体中的多对多交互时,传递一个表示自上次更新以来经过的时间的增量变得难以管理。这是因为预测将发生交互的时间点(增量)非常耗时。如果错过了这些时间点,则模拟不准确。

    在每个时间步更新所有元素的方法的一个缺点是它会做不必要的工作。但是,这种不需要的工作的成本可能会低于准确预测需要评估哪些时间点所需的工作量,尤其是在复杂的交互环境中。

    【讨论】:

      【解决方案2】:

      我会以恒定的时间步长来执行此操作。编码要容易得多。每个更新功能根据环境执行一个步骤。您不必每一步都更新浏览器。您可以计算 10 或 100 步,然后将结果发送到浏览器。

      这样会更准确。许多简单的小步骤比依赖于 delta 时间的函数更容易编码(一场噩梦)。

      如果您使用可变时间步长。当时间步长很大时,您可能会遇到蚂蚁在 t0 时位于 A 点而在 t+delta 时位于 B 点的场景。您首先更新蚂蚁,它在 B 点结束。您更新了 A 和 B 之间的食物重生点,该重生点应该在 t0+ 1/3 delta 处重生。蚂蚁没有看到食物就经过了。模拟错误。

      您可能需要的其他东西:

      • 要真正准确,您必须检查段之间的碰撞 [先前位置 - 新位置] 而不是点。否则蚂蚁可能会在不发生碰撞的情况下交叉自己。检查物理引擎。
      • 避免将所有对象发送到浏览器。使用八叉树或四叉树快速确定您的数据子集对应于浏览器显示的区域。

      在 Node.js 中执行此操作的选择很奇怪,我会使用 Java 或真正的 oop 语言。

      您会在游戏开发者论坛上找到很多有用的帮助。

      【讨论】:

      • 你认为 Javascript 试图成为 OOP 的术语“真正的 oop 语言”暗示了这一点。真的不是。
      • 据我对 Node.js 的了解,它使用的不是 OOP 的 Javascript(即使可以编写“OOP 风格”的代码)。我认为进化游戏正是那种会从严格的 OOP 设计中受益的程序。 Nodejs 会让它更容易上手,但我担心它很快就会变成意大利面条式的代码。具有类型化变量、访问修饰符、严格的类模型、继承、接口的严格语言将需要更多的设计,但会产生更易读和可维护的代码。这不是针对 NodeJs 的指控,只是个人意见。
      • Javascript 是面向对象的。而已。它是动态的,但它是 IT。是。哎呀。 Node.js 足以胜任这一点,而 Javascript 将提高生产力并且不会引起头痛。
      • @bokan 逻辑编程的经典用例(例如 Prolog、F#)就是这种游戏风格。
      【解决方案3】:

      如果您没有多个必须协作的代理(每个都有自己的线程)并且您不必处理流程问题的同步/事件,我建议您使用连续模拟。使用固定时间步长并在每一步中更改您的世界状态。 每个世界片段使用如下函数改变其状态:

      newState = f(oldState, deltaSteps)

      关于您提到的速度问题,不要直接将您的迭代映射到时间。定义一个中间时间单位(步长),然后他们将此单位时间映射到毫秒、迭代或您喜欢的时间。因此,如果您要提高或降低模拟速度,只需更改用于从步骤转换为时间/迭代的因素。如果你需要改变速度,只需改变你的常数。

      查看page,了解有关模拟技术的一些见解。

      【讨论】:

        【解决方案4】:

        我认为你会希望你的动画是连续的,并且基于一些经过的时间或时钟(你可能能够加速或减速)。因此,您可能希望基于 delta 的一些更新函数。

        但这并不意味着你不能使用 setInterval 来生成食物块。这也不意味着其他一切都需要或应该基于该增量。

        例如,您可以在位置更新后检查彼此靠近的生物或繁殖所需的任何条件,然后以不依赖于当前时钟的离散步骤生成后代。不过,您可能想记录发生这种情况时的时钟。

        【讨论】:

        • 如果我想一直更改增量怎么办?然后我根本不能依赖 setInterval,或者一般依赖它,因为它不精确。此外,没有必要比其他人更频繁地检查某些内容,但感谢您的想法,我可能会使用它。
        【解决方案5】:

        每次调用更新函数时,您都可以计算自动画开始以来经过了多少时间。然后将这段时间传递给更新函数,即使帧可能不会在第 20 秒精确更新,您也可以根据实际时间进行所有计算。

        示例:汽车在第 20 秒开始移动,速度为 3units/s。假设更新功能在以下时间触发:..., 19.35s, 20.67s, ... 当在 19.35 秒更新时,您知道它不应该移动什么,所以什么也不会发生,但是当更新功能被触发时,时间值为 20.67 秒,那么您知道汽车已经移动了 0.67 秒,因此您计算它的位置(时间* speed) 0.67*3 = 2.01 并进行所有其他计算/绘制,就好像它已经移动了 2.01 个单位。这样您就不必担心时间测量不精确、滞后、更新功能比平时花费更多时间等。

        【讨论】:

          【解决方案6】:

          我认为您不会超过某个频率(例如 50 Hz)。这会在不必要的精度上浪费 CPU 时间。

          如果用户的设备无法提供该更新率,您可以选择

          1. 保持相同的物理频率并降低挂钟速度
          2. 使用更高的 delta T 降低物理频率

          如果频率仍然高于 20Hz,我会选择 2。如果它变得更低,您可能应该切换到策略 1 以保持精度。

          因此您可能需要基于 deltaT 的解决方案,以便调整更新频率。

          【讨论】:

          • 考虑您的编辑:当在模拟周期的中间发生食物掉落事件时,您可以将周期分成两部分并以额外的物理周期为代价完美精确地生成食物。
          • 我不确定我是否理解...假设事件设置为在模拟开始后的 20 秒内发生。但是,如果模拟以每次更新 0.7889835 秒的速度进行,则事件将永远不会在它应该发生的时候发生。
          • 您运行 25 个正常模拟步骤 (19.7245875)。然后你用 deltaT = (20 - 19.72) 运行一个步骤(现在正好是 20 秒),然后你放下食物,然后你继续用正常的 deltaT 运行。
          • 哦,有趣。我其实也有类似的想法,虽然有点不一样……不过明天我还是要考虑一下,但看起来还不错。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-05-12
          • 2018-09-04
          • 1970-01-01
          • 2023-03-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多