游戏开发论坛

 找回密码
 立即注册
搜索
查看: 6037|回复: 1

关于类DOTA游戏多样化技能系统的设计思考

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2016-2-23 13:42:12 | 显示全部楼层 |阅读模式
19300001203883130331048041100.jpg

  GameRes游资网授权发布 文 / 洛城

  在游戏里,每一个人物都有很多的技能。像DOTA,英雄联盟一样,技能也不都是单一的直线判断,而是有很多的花样。这类游戏的技能系统是如何设计的呢?

  这里想从自动机的角度来抽象这个问题,以期得到一个更泛化的解决方案。因为我是引擎程序员而非game play,因此可能实际解决问题未必和我的想法一样。

  首先,学习过自动机理论或者是编译原理,又或者对游戏人工智能方面有所了解的朋友一定听说过自动机的概念。这里我不怎么严谨的描述一下什么是自动机:

  (1)它拥有若干个状态(State),其中有一个状态是初始状态,并对应若干结束状态;

  (2)它拥有一个符号集,并接受符号集内部的符号作为输入;

  (3)它拥有若干称之为“状态转移函数”的边,这些边连接状态,把当前状态和输入的符号作为参数,并实现从一个状态到另一个状态的转移。

  这样的自动机一般是叫做有穷自动机,仔细看看,是不是和我们在LOL这类技能施放类的游戏的行为很类似?

  试着把自动机里的一些概念映射到游戏里:

  (1)在LOL这类游戏里,的确存在很多显式的状态的概念,譬如说死亡,减速,晕眩,这就是玩家当前的状态。

  (2)用户的输入可以作为符号集,这里的用户输入有时候并不是按下了某个键这样简单,而是更有意义的输入:譬如某个技能是否已经释放到了一个人身上,又或者某个AOE技能是否与某个角色的包围盒发生了碰撞。

  (3)状态转移函数则是设计师定义的技能施放机制,譬如寒冰的剑射中敌人后对目标产生晕眩效果,也就是使得敌方英雄从当前状态转化到了晕眩状态。

  经过这样的抽象,整个英雄技能的施放就很有条理了:每个角色都有一个唯一的状态机,它的当前状态可以通过双方玩家的输入而发生转移,转移的规则作为一个个单独的状态转移函数由策划配表完成。

  我们以LOL的几个技能施放为例子,看看如何设计针对英雄自身的自动机。

  先看光辉女郎LUX的被动技能的触发机制:首先,敌方英雄当前状态是正常状态,接着LUX施放了一个伤害技能且该技能击中了敌方英雄(也即是一个有效输入),于是根据状态转移,这个技能使得敌方英雄掉血并且处于一个被标记的状态,紧接着,LUX平A了敌方英雄一次,于是又有一个状态转移函数,使得敌方英雄在被标记的情况下掉了额外的生命值,同时使得该英雄重新回到了正常状态。这是一个非常典型的一次技能判断机制。

  但这里就存在一个问题,有朋友就会说,这样简单的输入->反馈式的逻辑谁不会写啊,引入所谓的自动机有什么用?真实的MOBA类游戏里,很多技能的施放和产生的效果并不是仅仅由用户的一个输入行为决定,甚至不是一个状态决定,一个结果或者一个状态转移很可能是由多个角色身上的已知状态以及多个用户的同时输入来决定的,如何引入多输入或者多状态的状态转移函数呢?

  我们再引入一类更为复杂的自动机,这类自动机叫做下推自动机。下推自动机和有穷自动机几乎一样,唯一的不同是,下推自动机除了状态,输入,和状态转移函数以外,还包含一个可操作的数据栈。这个数据栈的操作可由状态转移函数来进行操作,状态转移函数可以把用户之前的输入压入栈里或者从栈里取出来使用。这样,用户先前的一些状态就能够被记录下来而不用使用组合来生成新的状态。

  比如有一个技能,当施放时,如果敌方,同时处于晕眩+减速+血量低于百分之五十,就对敌人造成一击必杀(死亡状态),如果是先前的有穷自动机,我们要表示这样一个状态,可能需要重新定义一个新的状态:X英雄一击必杀的状态(也就是晕眩+减速+血量低于百分之五十),一旦随着游戏逻辑的复杂,这样的状态将会呈几何级数增加。反过来,如果是使用下推自动机,我们则不需要定义新的状态,只要有晕眩,减速和血量低于百分之五十这三个单独的状态,然后当晕眩发生时,状态转移函数从正常状态转移到晕眩,同时把晕眩状态作为一个输入压入到数据栈里,紧接着,又发生了减速,那么从晕眩转移到减速,再把减速压入到数据栈里,此时血量低于百分之五十的话,可以再让用户进入血量低于百分之五十的状态。当处于这一状态时,再接受X英雄放了大招,这时候状态转移函数以X的大招作为输入,当前处于血量低于百分之五十的状态,同时从当前数据栈里找到了另外两个必要条件:晕眩和减速,那么以此为依据,让敌方英雄进入死亡状态。

  同时,我们的状态转移附加的事(比如施加晕眩状态,或者使其进入血量低于百分之五十),这可以抽象成一系列的原子行为被附着在状态转移函数中,而状态转移函数则负责依次触发这类行为,而有些行为我们可以认为是同步的,有些则是异步的。譬如三秒的晕眩可以理解为一个异步操作,它分两个阶段执行,第一阶段是开始时施加晕眩,第二阶段是结束时取消晕眩,这类异步操作就进一步化为了两个原子操作。状态转移函数的目的就变成了调度一系列行为的触发,同时转换(Switch)当前的状态。

  所以到目前为止,我们抽象出的结构有这么几个:状态,行为,输入,状态转移函数(一系列的行为调度+一个状态的切换),它的拓扑结构和图的类型非常接近,因此可以让策划用配表的形式把这种图的给表示出来并简单的存储在一个XML文件里。程序要做的事情则是通过策划配表得到的XML文件重建这个设计好的自动机,并把相应输入link到这个自动机的输入端,同时把行为绑定为一些固化的执行函数。

  这是一个比较基础的玩法,如果想要玩的更复杂,也有一些脑洞可以开,比如说,程序员都听说过正则表达式,以及可能不一定听说过的上下文无关文法,这类表示方法能够非常简洁有效地表示一个技能的触发规则,如果你的策划能够了解和熟悉这样优雅的规则表示法,并且能够把他想要设计的技能或者英雄用一个上下文无关文法或者正则表达式来表示出来,同时你又有足够的能力设计出这样的语法解析器,那么你完全可以把一个技能施放系统当做一个编译器的前端解析器来实现。同时引入一些逻辑操作符来丰富规则,譬如与(AND)或(OR),或者闭包操作等。

  除过这里介绍的自动机,实现类似行为的另一个常用结构是所谓行为树(Behavior Tree),但从我的理解来看,这和编译原理中讲到的语法树没有太多不同。

  所以其实现在很多游戏中需要考虑和解决的问题,在经典的算法和基础计算机理论层面已经有很多聪明的先驱提出了很多优雅的算法,往往通过合理的抽象我们都能把游戏中的问题规约为这类经典问题来解决,而很多国内的game play程序员喜欢发明些trick来临时解决,最后的结果往往就是补丁打补丁,代码可读性极差,还是因为忽略基础理论和基本功不扎实。

  相关阅读:如何设计一个易扩展的游戏技能系统?

0

主题

9

帖子

28

积分

注册会员

Rank: 2

积分
28
发表于 2016-3-1 16:31:08 | 显示全部楼层
编译原理么
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-5-20 08:51

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表