【问题标题】:Which data structure(s) to back a Final Fantasy ATB-style queue? (a delay queue)支持最终幻想 ATB 样式队列的数据结构是什么? (延迟队列)
【发布时间】:2011-01-27 02:10:30
【问题描述】:

情况:模拟环境中有几个实体,它们有一个人工的时间概念,称为“滴答声”,与实时没有联系。每个实体轮流移动,但有些比其他实体更快。这以延迟表示,以滴答为单位。所以实体 A 可能有 10 的延迟,而 B 可能有 25 的延迟。在这种情况下,轮流顺序为:

A A B A A

我想知道使用什么数据结构。起初我自动想到“优先队列”,但延迟与“当前时间”相关,这使事情变得复杂。此外,还会有延迟较大的实体,并且程序将运行数百万个滴答并不是不可预见的。当延迟本身保持相对较小并且不增加时,内部计数器越来越高似乎很愚蠢。

那么你将如何解决这个问题?

【问题讨论】:

    标签: data-structures simulation priority-queue


    【解决方案1】:

    您将实体存储在堆中,并按剩余等待时间对它们进行分组。接下来要移动的实体组将位于堆的顶部。您只需更新这些实体。当它们的剩余等待时间降至 0 时,您将它们从堆中删除。将下一组实体排在堆的顶部,同时将它们的等待时间减少上一次移动之前刚刚经过的时间。

    例如:

    您的堆有 3 个节点(A、B 和 C),顶部是节点 A,其中两个实体都剩余 5 个刻度。 childern 分别剩下 10 和 12 个刻度。

    • 在时间 t=5 时,您移动节点 A 中分桶的所有实体
    • 从堆中删除 A
    • B 移动到堆的顶部,然后剩余 10-5 = 5 个滴答声
    • 重复。

    【讨论】:

    • 如果不是按“等待时间”对堆进行排序,而是按“该实体下一次采取行动的时间”对堆进行排序,那么您不必减少“等待时间”每个实体。
    • 计数时,一旦超过 Int 或 Int64 的限制(如果您的战斗持续很长时间),您可能必须考虑翻转。
    • 我的意思是距离下一个动作的时间,但等待的时间减少了打字。
    • 如果我理解这一点,BeWarned,它不必在每次时间变化时更新每个实体上剩余的刻度吗?如果是这样,我喜欢它!
    • 没错,您只需要在每次滴答时更新堆上的顶部节点,您还必须考虑添加节点的时间。对于重叠,只需重新计算堆。
    【解决方案2】:

    根据您的描述,在我看来,“下一步是什么?”的概念。比“距离下一个行动还有多长时间?”更重要。在这种情况下,按“下一个”或剩余的最低滴答数到最高对您的队列进行排序。当然,插入是按适当的顺序输入的,而更改的条目(“加速”咒语)会从队列中删除、更改,然后以适当的方式重新输入。

    然后,您只需将下一个作业从队列中弹出即可。无论剩余多少滴答声都必须是“经过的时间”。通过队列,将每个条目的剩余滴答数字段减去您刚刚发现的滴答数。

    这样做的好处是可以跟踪剩余时间的概念,而且在没有采取任何行动时不必为经过的滴答触发事件或执行任何其他代码。您可以负担得起,因为与实时无关。只有“下一步是什么?”和“到达那里需要多长时间?”。

    【讨论】:

    • 你是对的,“下一步是什么?”是重要的概念。只要事件以正确的顺序处理,完全不需要现在是什么时间或距离下一个动作多长时间。我不确定是否要减少队列中的所有内容……可能有很多,我希望它快一点。不过,如果我找不到更好的方法,这可能就是我会采用的方法。
    【解决方案3】:

    如果我们假设您的实体正在观察或观察模拟时间,那么它们每个都可以实现一个接口,使它们跟踪ticks left 并提供一种方法来获取特定实体还剩下多少滴答声。在每个滴答声中,实体将其ticks left 减 1。

    然后您可以保留这些实体的排序集合队列(设置是因为每个实体只会在队列中出现一次),根据 get ticks left 排序,因此第 0 个实体是下一个移动的实体,第 N 个实体实体是“最慢的”。

    当实体的get ticks left方法为0时,从排序集中移除,ticks left定时器复位,重新插入排序集中。

    【讨论】:

      【解决方案4】:

      选项 #1:轮询

      我可能会构建一个控制器,它可以发现所有不同实体的延迟并为每个实体维护一个剩余滴答声。控制器将循环通过滴答声,并在每个滴答声中减少游戏中所有实体的剩余滴答声。

      一旦实体的剩余刻度值达到零,您就知道该轮到它们了,由处理刻度的心跳方法或您调用的方法控制。

      选项 #2 事件

      像 UI 范例一样思考,界面不会不断地轮询按钮以查看它何时被点击。而是让按钮在通过事件单击时通知 UI。让您的实体(或 EntityBattleContext)在准备好时触发一个事件。您将不得不以某种方式处理您的游戏时间,因为它根本不是基于现实世界的时间,您可能需要让所有实体侦听 GameTick 事件,并且当它们接收到该事件时更新它们的内部 TicksRemaining 变量。

      在遵循事件驱动路由之前,请确保轮询路由不起作用。请记住基本规则以后总是优化,因为更多时候你不需要优化。

      【讨论】:

      • 看起来可行,但如果有很多实体可能会有点慢?
      • 好吧,我想如果您有很多实体,您不想使用轮询系统。而是设置每个实体在准备好时触发的事件。这样,您的控制器只需接收事件以这种方式处理实体。
      • 这两种方法在您逐步完成每个刻度时都非常低效。通常在这种性质的模拟中,您会跳过没有发生任何事情的巨大时间间隔,而不是通过它们递增。
      【解决方案5】:

      看看Java的DelayQueue是如何实现的。

      【讨论】:

      • 全局时间概念,每个元素都知道什么时候到期,并在 getDelay 和 compareTo 上公开一个相对时间。这仍然存在溢出问题。
      猜你喜欢
      • 2016-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-19
      相关资源
      最近更新 更多