当前位置:首页>>攻略文章>>正文
魔兽科普:模拟器组成原理(一)
2013-12-27 23:50:41 作者:fhsvengetta 来源: 浏览次数:0
摘要:我会把我所知道的重点尽可能地写出来。由于这个系统非常庞杂,不可能事无巨细全都写出来,但在你读懂这篇内容之后,加上少许编程功底,应该就能够达到自制DPS模拟器的水平。
导航:

 蒙特卡洛法重识SimC反向工程时间驱动和事件驱动表达式解析面向对象的模拟器设计机器不掷骰子|

时间驱动和事件驱动
模拟器依据其驱动条件的不同,大体可以分为两种:时间驱动型,和事件驱动型。
时间驱动,由时间推动事件的发生。模拟器通过模拟时间的流逝来推动模拟过程。
事件驱动,由事件推动时间的流逝。模拟器模拟事件之间的相互关联、先后顺序和触发链,让时间被动地在其中跳转。
SimC属于时间驱动的模拟。那么我们先来以SimC为例看看时间是如何驱动模拟的。
时间驱动
SimC有一个“时间轮”的概念,每个时间轮默认长为1024秒。
SimC默认将每一秒切为32个“时间碎片”。那么每一片的长度大约为31毫秒。
“时间碎片”是SimC中最小的、不可再分的时间单位。
想象一下,一个有1024个大刻度,每个大刻度里面有32个小刻度的幸运大转轮。
\

                                 图:幸运大转轮……真幸运!
当你需要注册一个事件,让其在时刻T发生的时候,SimC会将这个事件存放在T所处的时间碎片之中。
随着模拟进行,SimC会缓慢转动时间轮,指针依次扫过各个时间碎片……当指针指向一个时间碎片的时候,其中所包含的事件就将被执行。
另外,每次到达一个新的时间碎片,SimC将检查一次动作优先级列表,看看有没有玩家现在能够执行的动作。
大转轮的周长1024秒足够长,它超过了17分钟,所以在大多数模拟中,大转轮不会转动超过一周。
然而如果超过了一周,SimC显然也有办法应对。时间碎片是循环连续使用的,第一片碎片在被扫描过之后,立即就变成了最后一片。
比如上图中,高博的大转轮刚刚扫过了“时间碎片:5”,现在高博不需要碰它,碎片5就自动成为了离指针最远的碎片。

只要计划任务的延迟时间不超过大转轮的周长,SimC就可以一直维持运转下去。模拟中最长的延迟注册事件是“筋疲力尽/心满意足”的结束事件,需要延迟10分钟,也就是600秒。
那些长达半小时或一小时的Buff,在SimC中,都被当成了不会发生变化的静态效果,像游戏中持续时间为“N/A”的那些光环一样。
使用关键字wheel_granularity指定每一秒包含的时间碎片数,使用关键字wheel_seconds指定时间轮的长度。
例如:
Code (c):
#将时间碎片的粒度缩小一倍。
wheel_granularity=64
#时间轮长度1024秒。
wheel_seconds=1024

“时间轮”的长度必须超过600秒,否则SimC会拒绝使用你提供的时间轮长度,而使用默认的1024秒。
另外,这两个值都必须是2的整数次幂。如果不是,SimC会将它取整到2的整数次幂。
例如
Code (c):
#这样写的实际效果和上面的语句是一样的。
wheel_granularity=65
wheel_seconds=601
#65会被近似到64。而601会被近似到512,然后因为小于600而被拒绝。


依据这个到2的整数次幂的取整,有经验的程序员立刻就能明白SimC时间片的数据结构。
32个碎片需要5比特的编号,1024秒需要10比特的编号。所以在默认设置下,时间轮上的所有碎片可以被编号为:
0111 1111 1111 1111
蓝色部分代表了秒号,红色部分代表了碎片号。黑色的零是未被使用的比特。
这个编号是连续无缝隙的,可以用来内存寻址。这也是SimC要求必须向2的幂取整的原因。
时间驱动的局限性
时间驱动有它的优势:它符合人类的思维习惯,人们日常生活中更容易感受到时间的流逝,而人们对事件的触发往往感受不深。
所以这种模式的模拟器易于编写,易于维护。
但它也有非常明显的局限性。
引入系统误差。
由于这种架构的模拟器,引入了原子时间单位“时间碎片”,使得所有事件的发生时间都发生了细微的改变。
以SimC的默认设置为例。假如实际游戏中,我们在第0毫秒、第12毫秒、第30毫秒和第31毫秒这四个时刻上,分别发生了事件A、B、C、D。
在模拟中,它们将分别落入第1、1、1、2片时间碎片中。

实际事件发生时间与模拟时间的对比如下:

  A B C D
实际 0 12 30 31
模拟 30 30 30 61

 大多数事件的模拟发生时刻都被略微延后了,而且ABC三事件本来发生于不同时刻,在模拟中却同时发生。
这将导致模拟器对输入“急速”属性的细微变化,产生不可预期的行为,对急速价值的评价将在一个较大范围内剧烈震荡。

\

                                                                        图:急速收益的不稳定现象。
SimC对此作了许多应对措施,例如加入了延迟系统。
加入随机浮动的延迟值,可以缓解急速震荡效应。

由于急速震荡效应,在进行急速权值计算时,世界延迟应该略微取大一些,差商区间应该略微取大一些,时间碎片粒度也应该尽可能地取细一些。
前两者会影响权值的准确度,后者会降低模拟速度。
放慢模拟速度。
时间碎片粒度越细,准确度越高,但是对大量空白时间碎片的扫描浪费了很多CPU资源,大幅拖慢模拟速度。
时间碎片粒度越粗,准确度就越差。
这是传统时间轴架构一对不可调和的矛盾。

事件驱动
在DPS模拟器上,由于SimC采用的是时间驱动架构,所以事件驱动这一块资料并不充足,还处于待开发阶段。
灰谷本人正在尝试评估事件驱动DPS模拟器的可行性及其性能。

举一例说明事件驱动与时间驱动的区别,以及事件驱动架构可能的优势。

灵魂收割,6秒后爆炸。典型的计划任务。
在起始时间点,执行了施加灵魂收割的动作,然后将灵魂收割Debuff结束和爆炸注册在了第193号时间碎片中。
这里假设时间碎片长度是SimC默认的31毫秒,则模拟过程会是这样:
起始时刻:施加
0.030:不变
0.061:不变
0.092:不变
0.123:不变
0.154:不变
……
……
……
5.992:不变
6.030:结束,爆炸
一共扫描了193个时间碎片,其中有192个时间碎片都是空的,效率非常的低。

如果采用事件驱动的模拟,我将有一个事件队列,为即将发生的事件按发生时刻先后排队。
例如我开始模拟时,事件队列中只有一个事件:
于0.000时刻,施加灵魂收割。
每次我都从事件队列中取发生时刻最早的事件出来执行。
由于队列中只有一个事件,所以模拟一开始执行的就是它。
执行“施加灵魂收割”后,这个事件会向事件队列中注册一个新事件“灵魂收割结束”。
现在事件队列是这样的:
于6.000时刻,灵魂收割结束。
每次我都从事件队列中取发生时刻最早的事件出来执行。所以我这次执行的是“灵魂收割结束”,而且当前时间立刻跳转到了6.000。
“灵魂收割结束”这个事件又会向事件队列中注册一个新事件“灵魂收割爆炸”。
现在事件队列是这样的:
于6.000时刻,灵魂收割爆炸。
每次我都从事件队列中取发生时刻最早的事件出来执行。所以我这次执行的是“灵魂收割爆炸”,而且当前时间仍然处于6.000不变。
这样我就完成了整个模拟。我只执行了三个事件,时间点跳转了一次。

由于任何事件都无法影响过去,只能影响现在和未来,所以这种基于事件链式触发的架构无需对时间进行步进操作,也能保证时间单调流逝,不会回溯。



相关报道:

[关闭] [返回顶部]


  返回首页 | 最新资讯 | 资源下载 | 魔兽图片 | 单机文档 | 技术攻略 | 玩家视频
备案号:蜀ICP备2024062380号-1
免责声明:本网站为热爱怀旧WOW的玩家们建立的魔兽世界资料网站,仅供交流和学习使用,非盈利和商用.如有侵权之处,请联系我们,我们会在24小时内确认删除侵权内容,谢谢合作。
Copyright © 2024 - 2024 WOWAII.COM Corporation, All Rights Reserved

机器人国度