« 并发问题 bug 小记 | 返回首页 | 让 LuaJIT 2.0 支持 Lua 5.2 中的 _ENV 特性 »

星际争霸2编辑器的初接触

最近在接手改进我们的怪物 AI 的底层模块。这部分策划是希望可以由他们来直接配置而不是提交需求文档让程序实现。

我们的前一个版本有较大的性能问题,光是空跑一些场景,没有玩家的情况下 CPU 符合都相当之高了。我觉得是允许策划的配置项过于细节,以及底层模块实现的方式不对,导致了大量无用的 lua 脚本空转导致的。

目前的 AI 脚本是每个挂在独立的 NPC 上,利用心跳(大约是 0.5s 一次),定期让 NPC 去思考现在应该去干些什么。这些干些什么的具体逻辑在很细节的层面都是要去运行一个策划配置的脚本在一个沙盒中运行的。在实际监测中,一个心跳的一段 AI 脚本居然会跑上万行 lua 代码,想不慢都难啊。

游戏开发和很多其他软件开发的一个巨大区别就是,你无法把程序得到正确结果当成任务的完成。运行时间往往成为重要的约束条件。如果一件事情在规定的时间片执行不完,代码实现的再正确都没有意义了。而局部的优化热点往往也意义不大。因为如果只是需要小幅度的提高性能,那么采购好一些的硬件就够了。一个模块的性能,要从数量级上的提高,必须重新思考需求,改变需求,重定义我们要做什么。

我决定看看星际争霸2 的地图编辑器是如何工作的。

我没有玩过魔兽争霸3 的编辑器,也没有玩过星际 2 的。但似乎,它们可以让用户自定义出各种形式的游戏来,并不局限在 RTS 一种类型中。我相信这个发展了超过十年的自定义地图的编辑模式,一定有很多成熟的业务抽象。

有限的时间内,我没有从网上找到太多的相关资料。在暴雪的官方网站也没能看到完整的文档。星际2 的编辑器内建了一个叫做银河(Galaxy)的脚本语言,似乎所有 GUI 界面上的编辑器操作,都可以完整的对应成一段 galaxy 脚本。很可惜的是,暴雪似乎鼓励玩家用 GUI 编辑器创造地图,而脚本只是背后之物,galaxy 的手册我并没有找到。

我只好自己把弄编辑器,在自己的使用中,推想暴雪解决问题的思路。短短两天的研究肯定会有许多错误和遗漏,也希望借公开的 blog ,有行家看到能够赐教。


简单而言,自定义星际2 的地图,编写控制脚本的人是以上帝视角来看待世界的。脚本并不单独挂在单个 NPC 单位上。这和我们之前的设计很不一样。

自定义游戏,是由数据加代码构成的。数据包括了地形数据,放置在地图上的单位,还有路径、点、区域等等类型的对象构成。这些可以很方便的用 GUI 编辑器生成。后面在代码中引用。

而代码,是由一个个 Trigger 构成的。官方翻译为触发器。每张地图有若干 Trigger ,每个都有全局的名字,平坦的存在于地图的数据结构中。从编辑器角度看,这些 Trigger 必须一开始全部实现好,在地图加载时加载到内存中。后续代码能做的事情只是开启或关闭一些 Trigger。

从脚本角度看,Trigger 似乎是一个动态对象,可以动态生成,而不是一段静态的代码。但是编辑器里似乎做不到动态创建 Trigger ,看起来暴雪也不鼓励这种动态创建过程。

Trigger 由事件(Event) 条件(Condition) 动作(Action) 局部变量四部分构成。

Event 全部是可以枚举出来的东西,不存在自定义事件这种东西。所以在编辑器里可以通过菜单选取。暴雪在编辑器设计上下了一番功夫。所有的事件都有一个英文短词用于脚本方法的定义,同时有一个长句子用来显示在编辑器选单中。根据使用者的语言,可以配置为比如中文的。另还有一段长说明,方便使用者了解用途。

Event 可以说全部是全局事件,比如单位死亡,单位受到攻击,单位进入区域等等,可以指定一个对象,也可以监控一类或全部对象。UI 事件也被纳入同一体系,比如有 UI 按钮被按下这种事件。

Event 可以说是 Trigger 的过滤器,系统知道什么时候该考虑哪些 Trigger 可能需要执行。

Trigger 是否被触发还需要看条件是否被满足,这就要看 Condition 的配置了。如果你需要刺蛇被攻击时做一些什么,也可以定义 Event 为任意单位被攻击,再在条件里写上单位类型为刺蛇。

Action 就是需要完成的一系列事情了。这些事情都是瞬发的,比如把单位移到你定义好的点,删除单位等等。

不同的 Trigger 可以拥有相同的 Event 以及相同的条件,那么事件触发,且条件满足时,多个 Trigger 会同时运行。当然这个同时是逻辑上的,内部还是有一个先后,我推测是一个类似 coroutine 的机制来驱动的。因为 Action 里的事情都可以立刻完成(这里有个例外,下面会提到),所以不会有冲突。

那些,如果要执行的事情是一个需要很长时间才能做完的动作怎么办?比如你需要把一个单位按定义好的移动速度,沿正常的寻路得到的路径移动到一个目的地。这就得引入指令序列这个概念。

每个单位身上都有一个指令队列,保存的是一系列的指令 (order) 。指令和 Action 是两类完全不同的东西。比如让一个单位从当前位置移动到一个点就是一个 order ,而把单位(不通过游戏内在机制)瞬移到那个点就是一个 action 。order 是在队列中循序执行的。在 Trigger 里能做的是添加一个 action,这个 action 是把 order 加入队列中。当然加的方法有三类:清空现有队列,加入一条指令;把指令加到队列最后;把指令插入到当前位置(立刻执行,但不清掉原有指令)。

Trigger 里可以有多个 Action ,它们只有执行次序的。但 Trigger 之间可以并发。大部分 Action 都会立刻执行结束,只有一类特殊的 Action 会把当前的 Trigger 挂起,只到条件满足才会执行后续的 action 。

这类特殊的 action 叫做等待。目前我发现有三种等待指令。

最常用的是等待一段(游戏)时间,比如我们可以利用它在游戏开始后 10 秒显示一行字。只需要把游戏开始事件加到一个 Trigger 中,在 action 里依次加入等待 10 秒,显示文字。

其它两个是等待一个 Trigger 被触发,以及等待一个条件表达式成立。我怀疑它们可以用于 Trigger 间的协作,但没有深入研究到底怎么做。不过可以确定的是,Trigger 是图灵完备的,它可以有分支和循环(它们是特殊的 Action)。Trigger 也可以看成是某种意义上的传统函数,可以不给 Trigger 设置事件和条件,而用一个 Action 直接运行一个 Trigger 。


指令和动作的概念分离对我的启发很大。

也就是说,基于 Trigger 的流程控制是面对 action 的,而 Trigger 属于整个世界而不是单个对象。order 则是附属在对象上,对象的流程控制用 order 组成。order 只可以循序的排列在队列中,Action 则可以有复杂的控制结构。

假设我们需要让一个单位移动到 A 点,然后驻留 5 秒,再移动到 B 点。目前在星际的编辑器中很难简单做到。关键在于让单位停留一段时间,我没有在 order 列表中找到。如果要实现,大约是这样的。添加一个 order 让单位移动到 A 点。另外写一个 trigger 在单位到达 A 点时触发,在这个 trigger 内等待 10 秒,再发送下一个指令让其移动到 B 点。

这里,假设支持让一个让单位等待一段时间的指令,它和现有的等待一段时间的 Action 一定是两样东西。而我们之前的设计没有区分这两者,所以实现的很不干净。


关于 Event ,如果我们仔细研究编辑器还能发现,Event 其实是定义了一系列事件,而事件发生在谁身上并不是一级考虑的要素。而大部分事件的确有发生的对象,可以是单体,也可以是群体等等。当 Condition 和 Action 在运作的时候,是需要取得这个发生对象这个参数的。

那么 Event 其实可以为发生对象做一次前置过滤。比如说,当发生单位死亡事件,那么有时候我们需要了解是哪个具体单位死亡。这个放在 Condition 里写固然可以,但失去了性能优化的余地。

Event 本身是可以内置一个过滤器的。从编辑器上看,可以设置五类过滤:预设,值,变量,函数,自定义脚本。

比如单位死亡事件,就可以使用预设的任意单位。也可以设置指定值。这个值就是在地图编辑时放在场景中的对象,通过交互界面可以很轻易的绑定过去。

也可以只一个变量,我猜想是一个声明好的全局变量。在别的 Trigger 中,可以对变量赋上值,引用具体的对象。

还可以是一个函数,比如离点最近对象(绑定了“点”的一个 closure),就是一个函数。

这么看来,从逻辑上讲,只是允许在特定事件发生的时候,若事件会携带一个参数(这个例子里是单位对象),可以在 Condition 判断前,先用一个过滤器过滤这个参数看看是不是有效触发 Trigger 的事件,这个过滤器为每个参数对象返回真或假即可。但从实现上,完全可以做一系列优化,让 Event 在实现层面不是针对世界,而是绑定在特定对象上的。

Comments

看完您的分析,突然觉得,很多游戏如果从暴雪编辑器的角度出发,会有很大的性能提升…

不过,从实际星际2地图的体验来说,过多的触发器(还能控制地形渲染)集中会造成很大的卡顿,而且实际产出的地图对网络同步的处理也不是很好(一个玩家掉线,集体等待的事情是常常有的)

个人以为暴雪编辑器的这个设计是为了更好的激发地图作者对游戏模式上的创意,而不是为了有多么高效的运行效率。弱化Galaxy脚本对制作者的影响也是(不过高级功能还是得通过脚本)

稍微纠正一下,星际 2 的地图编辑器确实是有能力动态注册触发器的(脚本语言 Galaxy 里面提供了 TriggerCreate 这样一个函数来实现),不过暴雪并没有提供在触发编辑器中动态注册触发器的手段

order实现应该就是在每个对象里做个队列,按队列执行order。
我有点纠结的是,order这种不是立即执行完的命令,是不是还应该给每个order一个绑一个回调,order执行完了,回调一下。

你好,我是一个正在运营的游戏项目程序经理,我也设计了一套策划使用的脚本系统,目前我们游戏中的所有任务,ai,副本,技能,物品逻辑,活动,都是基于这套脚本系统,我这套脚本系统思路是可以让策划自定义一个状态机,程序也是提供了事件,判断,执行这3类原子功能,我们这里叫做event, juge, need_do,比较土的名字,呵呵,策划可以通过脚本自定义一个状态机,具体方式是给这个状态机添加很多个状态,然后分别定义每个状态需要执行的need_do, 可以接收的event, 收到event后需要做的juge, 和需要执行的need_do, 或者跳转到下一个状态,来实现他们要的逻辑,我把这个东西定义成了一个叫logic_center的类,这个类提供一个逻辑绑定功能,只有用这个类绑定一个对象,指定event列表,juge函数列表,和need_do函数列表那么这个对象就支持脚本配置的功能了,这省了我们程序这边开发的很多工作量,我们只用提供这些need_do函数, juge函数, 以及游戏中某件事情发生时发出event事件,策划就可以配置出想要的逻辑,而且策划可以自己快速的看到自己想要的效果,而不用走很长的程序提交,测试部测试,策划再重新提需求的过程,目前效率基本上可以接收,目前我们随然可以搞定大部分经常变化的逻辑,但我们这个编辑器是针对一个具体对象的,所以象军团联赛这种还没法支持,希望能有机会能和你讨论一下关于脚本编辑器的思路,qq:6975356

Trigger讲的很清楚了,不过我非常想知道order是怎么个实现方式和原理,云风似乎只是简单提及?

如果网络游戏能在游戏运营阶段让玩家做地图,选最好的放进游戏里,我觉得是一个非常好的游戏开发模式。

这个就像祖母结和平结,都挺好的。

当然,我说的是逻辑都在服务器上做。别挑刺

这个问题和地图是不是无缝的没有一毛钱关系,和黑不黑屏也没一毛钱关系,和客户端也没一毛钱关系。

@fan 说得好~ #大拇指

@盖猪猪
"3D的第一人称视角的游戏屏幕一黑切个场景会被人骂死",原来这个人指的不是用户,是程序员.受教,另外你做出的东西如果不是给用户用的那么你就自己玩去吧.

@fan 我在说技术问题非程序员就别跟着掺和了

看起来确实跟war3的非常类似。war3的大型RPG地图大多会遇到一些GUI编辑器搞不定的需求,所以复杂的RPG地图通常都会直接写一些脚本代码,例如动态的处理场景创建或者动态的生成触发器等等。所以我觉得核心还是脚本的设计理念,而GUI的设计仅仅是为了方便非专业制图人员。

@盖猪猪
最近在玩无主之地2,符合第一人称FPS的定义.但也有场景的概念,跳出一个区域就"屏幕一黑"开始Loading...,似乎没人吐槽这个,这个如何说?

说来说去问题又回到了原点,场景这个东西只适合2D游戏,屏幕一黑切个场景,3D的第一人称视角的游戏屏幕一黑切个场景会被人骂死。既然引入AOI动态范围检测那么场景这个东西就该抛弃。抛弃场景之后按所谓场景,天神,地图处理AI的方式就不能使用。就需要一个新的框架来解决如何处理AI逻辑的问题,这个问题解决不了转来转去都逃不出mud。

从使用的角度来看,给单一的角色添加ai不仅麻烦,而且有很大的局限性.因为像这种即时战略,你有很多情况要操作多个单位,但rpg里,似乎又有不同.个人认为,不做过于复杂的逻辑时,这两者差别不大.

人家连dota那种规模的游戏都可以制作,本质的差别是,这类游戏一张地图就是全部世界,而mmo只是局部.我多年的经验看,还是应该尽量将问题限定在单一地图中,就是以"场景"为核心,只关注有限的全局的东西,比如任务,背包等.

如果每个npc和怪物都有自己的ai导致性能降低计算量提高,那么要么缩小整个区域的ai数量要么采用分布式运算来分摊计算量.

说实话,魔兽争霸和星际争霸的一个场景AI数量都是可预知,而MMORPG的玩家数量是不可预知的,这是两种类型的游戏,AI可以参考,但是照抄估计好难,把魔兽和星际那样的AI挪到MMO里面估计效率反而会低

另外,可以参考行为树,相信云风大神能写出NB,高效的行为树实现代码的.实现个jit的最好.

AI是AI,AI属于个体
Trigger用于场景比较方便.
如果想把Trigger用于AI,代码上很快会陷入泥潭.

星际争霸是单机游戏,所谓从天神视角安排npc不过相当于增加了一单机的玩家,这种单线程逻辑的脚本用什么方式其实无所谓了,mud的那种方式加个编辑器的壳子,都可做的很好。如果把这套概念搬到网络游戏上来,整个世界用一个单线程逻辑来处理。哪你前期做的工作就又都没有意义了,还不如当初直接拿mud来改。

我觉得这里面的关键可能不是触发器/EVENT放在哪个层面执行,而是如何抽象核心事件。

核心事件包括:游戏内对象能感知到什么(到达某个区域、被攻击、视野范围看到什么)、能做什么(移动、攻击、巡逻、对话)。其中具体的如何移动(线路选择)、如何攻击(优先打远程/脆弱兵种/魔法兵种;远程/脆弱/魔法兵种优先闪避然后才是攻击)之类则是固化的AI,已经包括在移动/攻击逻辑里面了。

然后就不再应该有AI出现了。剧情只是一个伴随player的剧本而已。player的动作会触发这个剧本的不同阶段。

核心事件抽象的目标应该是:除非极其特殊的情况,否则不应在核心事件之外出现复杂的逻辑判断。

写的太好了很喜欢看这样的文章

我觉得order应该与Trigger对调,这样更符合ai的定义

逻辑这块还是用脚本的集成环境比较好加些定制和限制检查,魔兽的编辑器有些搞的太复杂,还不如键盘精灵的编程方式实用。

@www
XML结构清晰、直观、易于扩展。我们目前是策划直接编辑XML,本来是要做编辑器的,但人手紧张一直没搞,而且策划用的很熟,效率很高。XML这层已经完全结构化了,所以测试不是问题。

网页开了,多点了几次提交,不好意思~~

@k
XML 确实是一种方案,可以递归,
但是策划直接编辑XML吗?
另外,编辑后的trigger测试起来方便么。

@k
XML 确实是一种方案,可以递归,
但是策划直接编辑XML吗?
另外,编辑后的trigger测试起来方便么。

@k
XML 确实是一种方案,可以递归,
但是策划直接编辑XML吗?
另外,编辑后的trigger测试起来方便么。

全局控制在特定的情景下,可以使问题简单,但多数情况下似乎不适用。

我就是用这种trigger方式做的副本,只不过是用XML驱动的。策划可以很直观的进行编辑,象魔兽一样做出各种玩法,并且可以做GUI编辑器。效率测一下5W个AI是极限。

这个触发器是在我们用bigword做3D产品的时候广泛用到的概念。基本所有的基于脚本的处理,都是通过触发器的机制完成的。
基本概念跟云风设想的是一致的,即当一个事件发生以后(主要指一个主动的行为),去根据事先约定好的条件,判断行为带来的结果(动作)。

但在实际的策划使用中,可能会出现某个事件的动作也会再次成为事件,触发更多的动作的情况。

这就需要我们把一个需要的事情,拆分为若干个独立进行的简单行为,然后使用脚本去链接多个触发器,达到实现一个复杂判断的效果。

实际上,在整个游戏系统中,所有的事件、条件、动作都是通用的,我们就利用这个机制,完成了技能、道具、任务、AI系统的策划配置。

让玩家广播来激活NPC?

玩家参与建立游戏世界么=0=无冬之夜OL的亮点是玩家自己建立世界并合并到服务器内容里去,不过需要审核=0=其他的好像也就我的世界

一直觉得war3, sc2 编辑器强大,未有机会接触,希望此贴能有熟悉的朋友详加讨论,非常感兴趣之话题。。。

这样做的考虑可能有效率上,NPC简单地处理order seq, 在事件时才由全局AI,触发一次逻辑,

但再想一下,NPC个体AI脚本同样目的也可达到,每个NPC有一个事件的queue, AI拿到处理就行了,设置行为序列,无有,AI就一个空处理,似乎无损效率,

而似乎传统给策划的脚本接口里 会有一个 on_ai(), 策划于是就可能在里面加各种条件判断机制,遍历什么的,写出有损效率的脚本,

这样看起来,似乎就是 事件驱动与轮询的效率差别,

从这里的解释,个体NPC上处理的是 order queue, 而把何种order queue 注入 个体NPC,是全局的逻辑,感觉这种方式适合 以兵种为区别的战略逻辑,而 如果电脑玩家 对手级别的 AI,似乎更好是把AI中心放到个体处理上,感觉这里面有游戏类型与场景的区别

http://aigamedev.com/
这个网站里有介绍一种叫做behaviour tree的模式来定制上下文无关的行为或状态,每个行为或状态可以被处理成一个节点,有一些特殊的节点专门处理状态转换。这样一个NPC的行为就可以用不同的节点组织成一棵树。一个节点可以被重复挂载到不同的AI树上。可以将NPC的某种行为做成一个结点,结点可以被当成控件一样在编辑器中使用。这样,有了一些基础的行为结点之后,就可以灵活地拼接成完整的AI树,使用不同的AI行为。

看起来星际2的编辑器和 war3 的思路是类似的。

编辑器的难点不在触发器,不在逻辑,而是在他们的GUI集成(所见即所得)和方便调试。

IMHO, 星际中的个体根本没有思考的能力, 所有的动作都是上帝思考的结果(嗯...真实世界也有可能是这样...), 但是这适用于所有单位都用同一套AI的情况(单位AI和电脑玩家的AI在这里是另外一回事). 网游中的单位的AI可能区别很大, 甚至各种BOSS都要独立设计AI. 所以我的想法是让同AI的单位共享一个上帝, 每个上帝负责一种特定AI...上帝们甚至可以组织成树状方便通信O.O. 这样最大的好处是npc们只要不停执行命令就是了.

虽然我也没玩过星际的编辑器,但从云风的这个文章看,和UDK里的KISMET有点相像。不知道云风同学有没有试过。

首先, 我在 blog 里写了, 那些持续动作被称为 order 是可以通过 action 放到对象的 orderqueue 里去的。

其次,星际里还有一个抽象概念叫做 AI 性格,不过是附加在电脑玩家上的。 我没有写。

@Cloud

另外,我有点好奇,星际的npc 应该也是有挂一个定时器,否则挂上去的动作如何得到执行呢?特别是那种持续的动作,比如走路。难道也是全局trigger 在控制它的走动?

@Cloud

但是那种RPG 和MMO 应该是有区别的。更多是事件触发型,npc 都是缺少性格的。比如想做:近身了,用冲锋击晕对方,然后后腿,用远程AOE 秒杀。这样的行为,用这种全局的,我感觉上是不好抽象的。可以,但可能会有点别扭。

@Jaconey

星际支持把 event 对应到具体对象上。

实际上,也有人用它做 RPG

对星际这种,攻击形式简单,主要关注全局策略的游戏来说,这样的AI 组织形式是比较有效的,因为它更多的需要考虑全局的事件来做出不同的反应。

但是,对于RPG 的话,也许不是那么切合实际。因为npc 多数情况下是独立的个体,有自身的性格,而不是十分需要全局控制。因此挂NPC 上的AI 设计应该是更适合RPG的 情况

大爱星际,支持云风!

Post a comment

非这个主题相关的留言请到:留言本