一个好扩展的技能系统能让技能、BUFF、道具之间的关系能够很好的表示和调整,对游戏开发而言无疑是减少了不少的加班时间。那么要怎么设计一个易扩展的游戏技能系统呢?游资网从游戏策划、程序和玩家这三个角度进行整理:
策划角度:
作者:猴与花果山
感觉这个问题很久不提了但是还是值得拿出来探讨下。我花了至少5年在设计和实践(我自己经历了大小7个项目,虽然大多最后因为其他原因都没上)这样一套机制并且可以说基本解决了这个问题!我并不想藏着他,我也和很多朋友分享了,同时也感谢他们提出了很多意见,包括细节和优化等方面,正因为互相之间的借鉴和交流,我到今天已经把这套机制归纳的非常好了,还是想把它分享给更多想做好游戏的人,希望大家能进一步交流,把它更完善化,以形成一种规范,这套机制适用于任何类型的游戏开发,因为他是一个很棒的思路。
首先分析一下可扩展性:我从一个设计师的角度来看,所谓的可扩展性是——你并不知道策划下一个设计的是什么,但是你需要在尽可能不改动核心代码的基础上去把它实现了,并且在调试的时候(甚至是上线之后要做调整的时候),你可以并不伤筋动骨的去修改它。这里除了你要有好的代码规范外,还需要抽象一套机制来实现它。
实际上,好的策划的脑洞是非常大的,你真不知道他会设计出什么样的技能,但你并不能说一些设计因为无法实现就理所应当被埋没了,(我对策划设计Dota类游戏的思路要求是开放的,发挥想象力的,
因此我想了这样一个机制:[设计思想] 游戏系统设计思路的牢笼 一味追求实用性
他们的核心在于:
- 明确区别了AOE\Buff和技能3块,策划应该从这个角度出发思考问题。
- 这既然是一套机制,你可以把它用在任何游戏的框架当中。比如我要做一个Dota传奇的卡牌游戏,一样可以用这套机制,但是核心在于——你的策划要有能力归纳出游戏中的回调点。
当然我们在使用这套机制的时候,逻辑上实现并没有任何问题,但是我们一样会遇到一些从逻辑变成动画的困难,尤其是当我们的战斗在服务器上一瞬间完成了,但是要把结果告诉客户端吗,并且有客户端重新验算一遍的时候,因此我在之后有总结了一套作法,来完成这个事情:手游回合制游戏战斗机制归纳式设计
这个解决的是,当你有各种有趣的buff,但是又想不大改客户端的时候,你应该这样去建立这个框架。你可以想象如果你做一个MT类型的游戏,战斗是服务器一瞬间的,但你又要客户端重演,我们举个例子:
比如我门设计了在MT类型游戏中加入地形因素:可以有火海,火海每回合开始的时候对所有场上敌我英雄造成火焰伤害。
然后有个英雄是一只凤凰,凤凰有2个被动效果:
- 受到火焰伤害的时候变成治疗自己相当于伤害值的血量。
- 战斗中第一次死亡可以复活,回复最大生命值50%,如果在火海中则回复100%。
这样一个英雄和地形,我们如何实现呢?如果你看了我上面的几套机制,并且理解了,那基本没有难度,你根本不需要硬编码。但这里有个问题,我如何让客户端重现?这就是上面这篇说的关键了。
程序角度:
作者:扼杀黑暗
技能没什么框架,只是有很多字段罢了。
比如 cd 施法距离、释放动画、飞行动画等等。。。
其实游戏技能不是一直不是什么难点,毕竟根据每个属性实现逻辑就好了。
技能真正麻烦一点是其实是 所谓的“效果”。
因为从很久以前,游戏设计的时候就把效果这个概念添加进来了。
对于 游戏战斗对象主体,我们暂时叫做BattleAgent简称BA。
影响BA的数据有很多,比如移动速度 攻击力 基础属性 等等,影响的入口也有很多:
技能
buff/被动技能
装备
强化
宝石
魂
等等,而这些实际上从影响结果没什么区别。
首先我们先谈区别,对于这些数值影响,其实区别只有入口或者说是作用的方式,
技能是BA(castor)对BA(target)释放造成的瞬间数值影响。
buff是castor对BA(target)安装后造成的持续数值影响,分为按时触发瞬发和持续修改数值。
装备是特定容器对BA持续修改数值。
所以这里游戏开发者们抽象出了 效果这个概念。
对与效果而言,只存在2个行为:
- 对BA产生数值影响
- 对BA撤销数值影响
所以效果最终定义为:
- interface Effect {
- void cast(BattleAgent target);
- default void reverse(){
- }
- }
而对于其他功能实体来说,就可以简化为效果的容器:
- interface EffectContainer extends Effect{
- List<Effect> getEffects();
- }
这样我们就只要定义不同效果容器就可以了,
比如技能:
- class abstract Skill implements EffectContainer{
- public void spellTo(BattleAgent target){
- foreach(Effect effect in getEffects()){
- effect.cast(target);
- }
- }
- }
对于buff:
- class abstract Buff implements EffectContainer{
- public void update(){
- foreach(Effect effect in getEffects()){
- effect.cast(target);
- }
- }
- }
对于被动技能(其实也是buff):
- class abstract BuffSkill extends Buff {
- public void install(){
- foreach(Effect effect in getEffects()){
- effect.cast(target);
- }
- }
- public void unstall(){
- foreach(Effect effect in getEffects()){
- effect.reverse(target);
- }
- }
- }
装备同理被动技能,
是不是很清晰?
而对于复杂的技能效果,因为我们已经抽象出了Effect。
所以怎么实现也就很容易了!
- class DamageEffect implements Effect{
- private int damage = 100;
- public void cast(BattleAgent target){
- target.hp -= damage;
- }
- }
看起来是不是很简单,我们来写个变羊。
这个技能包括 2 个效果 外形修改和属性。
1 外形变羊
- class ChangSheepEffect implements Effect{
- public void cast(BattleAgent target){
- target.gameObject = GameManager.getAnimeObject("sheep");
- }
- }
2 攻击力和防御力变0 速度变慢
- class PropChangeEffect implements Effect{
- public void cast(BattleAgent target){
- target.atk = 0;
- target.def = 0;
- target.speed = 50;
- }
- }
就是这么简单,同学你明白了吗?
如果要深入一点的话,就是变羊是持续型的,到了时间会变回来。
所以我们要一个可以触发buff的效果:
- class TriggerBuffEffect implements Effect{
- BuffSkill buff = new BuffSkill (){
- public List<>getEffects(){
- return new List().add(new ChangSheepEffect()).add(new PropChangeEffect());
- }
- }
- public void cast(BattleAgent target){
- int time = 3000;//3秒
- target.addBuff(buff,time);
- }
- }
然后把这个TriggerBuffEffect加到技能能上就ok了,就完成了一个可以变羊3秒的技能。
玩家角度:
作者:梧桐
这篇文章想表达的是我作为一个玩家,不是作为商业公司的员工,所谓的程序、美术或策划,对感兴趣的一些游戏技能体系的一些分析。
这个要看追求了,没追求一张 excel 表配置上几百个参数,碰到一个需求加一个总能写出来;有追求甚至能和 war3 一样十几年前的游戏就可以在没有源码情况下做出 dota(然而总有嘴强王者认为这没有什么大不了);又或者像我一样幻想有没有可能设计出一套集成 GTA(模拟现实)+神之浩劫(moba)+虐杀原型(超现实动作)又可供玩家扩展的技能系统?
技能是如此有意思的事情,精巧的、可扩展的技能框架简直可以说是游戏最大的乐趣,远不是一些泡菜网游里面一句一切皆 buff 就能把我打发了的,所以很久以来我对探索技能在各种游戏里面的实现乐此不疲,分享一点研究的线索。
比较少谈具体的编码细节,因为就像我本篇最后说的那样,研究多了甚至开始反思技能这样一个概念到底存不存在,或者说不同游戏类型讨论的技能真的是同一种东西吗?如果游戏真的是现实生活的映射,那我们人类作为角色拥有技能吗?
最简单的就是虚幻竞技场那种 fps,感觉这种没什么好说的,虽然虚幻竞技场的改装枪是一个玩点,但是代码层面并不复杂。
复杂点的泡菜网游 rpg 或者 moba 可以参考星际争霸的银河编辑器,风暴英雄就是用这个做出来的。
熟悉 war3 不熟悉 sc 就下载 ga 的 war3 mod(魔兽争霸III 2015),这是论坛头目在 sc 体系下完整的复刻了 war3,可以观察 war3 里面的技能一个一个是怎么实现的,常见的比如暴风雪、空中锁链、变羊、风暴之锤等等,其实简单的的网游技能无非就是这些技能的变种。
里面大体来说把技能相关的东西解耦成了如下模块,unit、action、order、ability、behavior、effect、actor,然后通过反射和连线配置来扩展游戏。
简单描述这个体系,很复杂,也不求没研究过的人能看懂:ability 就是通俗意义上的技能,包装成 order 被 unit 调用,ability 本身不负责技能逻辑,只有使用 ability 消耗的资源(比如消耗多少魔法)前摇后摇之类的处理,他指向一个 effect,在 ability 调用时候转发到 effect 身上;effect 晚点说,先说 behavior,常见的 behavior 类型就是添加一个 buff,也就是对一个属性的阶段性修改,behavior 是有状态的;回头说 effect,effect 最终组成了一颗 effect 树,effect 常见的有发射投射物、应用 behavior、周期性的调用下一个 effect、对区域进行搜索然后对搜索到的每个对象进行调用 effect、造成伤害、创建单位等,除了周期性效果之外的 effect 都是无状态的,只是简单的对逻辑进行一次函数调用。以上所有的一切描述都是纯逻辑层面的,而 actor 是表现体,所有的特效、动画都是通过 actor 监听以上所有对象的信号 来做出相应的反应,而不是常见的动画驱动逻辑,换句话说,如果 actor 不监听逻辑层的信号,表现会有问题,但是游戏的核心逻辑还是照样跑,包括 unit 类其实都不包括任何的表现信息,只是在 unit 类创建时候通知 actor 创建并做表现 。actor 很类似一个完全被动驱使的客户端,其他模块是服务器。最后,在整个一套异步流程中,需要一些全局的类似 ai 中的黑板数据,记录比如施法者、施法位置、目标攻击位置、目标攻击对象等,由于 effect behavior 的树状向下扩散,所以这个黑板数据其实也要不停地更换上下文,这个就有点复杂了。
另外,表现层的 actor 的技能动作处理,也比较有意思,采用动作名称模糊匹配的思路:大概就是这样的:对角色当前各种状态进行采样(比如心情、天气、角色上一个状态名称等),根据状态类型优先级自动选择最合适的且存在的动作,比如如果资源同时存在Attack 00 Attack 01动作,那就随机播放一个 Attack,再比如风暴英雄里面普通死亡、爆炸死亡、天空击飞死亡播放不同动作也是在这个层次处理的。当然,动作模块的设计是个大问题,各大引擎中间件也经常是一个版本一个方案的大改,他这套方案也有局限性。好处就是用起来方便,ruby 曰:约定大于配置。想象一下,当玩家新做一个路人角色,这个角色在看到美女时候就自动做特定的好玩动作,而这一切需要做的也许仅仅是,配置一个 npc,给他一个特定名称的动作,比如 love stand。星际目前有296个关键字可以相互组合。
再谈一个小细节,一般自研引擎都解决的比较好,但是也可能还有人还没注意这件事:星际美术的动作资产中,除了骨骼动画,声音、特效等实时演算的功能也集成在里面,而且编辑器可以做到直接预览,上层技能框架里面把很多单位的表现状态(比如替换模型)都抽象成“动作”(而不是单纯的骨骼动画),这种直观的设计很舒服。反观一些方案(比如 unity 很常见的做法)直接把 fbx 作为美术流水线的终点,局限性很大,加个简单特效都要放到上层技能框架里,还不能预览,最好在裸骨骼动画和“技能”之间加个支持编辑器预览的、引擎实时演算功能(主要是特效、声音)相关的中间层,这个中间层的结果作为“动作”最终暴露给业务层,这方面 unreal 做的就比较好,编辑器对 fbx 的二次加工支持预览,用起来方便多了。
研究完了会发现顺便把插件接口、游戏录像(甚至可以做到从录像中某个点加入扮演某个阵营、网络同步、表现信息自动适配机器配置等内容还一块解决了。只不过我个人山寨的时候最后还是把这套方案的一些繁琐细节给折中了,最后的结果很类似 dota2 的技能系统,这就不多说了。
一个玩家都能随意扩展的 moba 技能体系,难道专业策划还做不好?这恐怕是做游戏思路的差异吧。再补充个好玩的,之前 war3 平台的地图《军团 td》作者做了个网游梦塔防,在测试中。
具体到编码细节我从 war3 时代就开始研究,也山寨过,非三言两语可以说清楚,但核心思路其实也就上面说的那些。只要把常见技能都亲手制作一遍,心中自有定论。虽然说起来简单,但是如果非要把自己定位到程序、美术或策划的立场上,而不是玩家或者 mod 作者,这玩意可能都太复杂了。
有人说看起来和某些 ai 的设计有点像?那是当然的,无论是 ai 还是所谓的“技能”,从基础的 if else 到上层做了一层又一层的抽象,说到底都是无非就是为了控制异步逻辑的复杂度,手段都差不多。至于游戏 ai 里面的乱七八糟理论,和本主题无关,就忍住不吐槽了,反正也是一个乱象丛生的领域。
实在觉得搞不懂也懒得折腾这么复杂的东西,就去研究 war3 的 we 编辑器吧。
大体思路就是把持续性效果、瞬间伤害、生成单位等一些基础设施准备好,再设计出常见技能的技能模板,最后在合理的时间点发出信号,配合触发器或脚本来扩展技能内容。
技能模板如果懒得设计,甚至初始技能每个技能一个状态机硬写好像也没什么不可以,反正魔兽争霸那么复杂的游戏其实基础技能也没多少。
未来扩展出来的技能都是这些技能模板的实例,区别是发出的信号或触发器在脚本层处理不一样。
魔兽那么多自定义地图的技能都是这么出来的,复制一个已有的技能的配置,然后稍作修改,虽然说起来好像有点傻。
还记得当年第一次见 dota 屠夫的钩子,惊为天人。
要研究 we,ga 也是大本营,只不过现在讨论的少了,毕竟老了,都转到 sc 上面去了。
比 moba 的技能还要复杂?mugen 之类的格斗够不够?
b 站的 mugen 专区,大家玩的不亦乐乎,unity 上读过一些逆向的格斗游戏,做的不错的比如韩国的网游灵魂之心,用的是状态机。国内的比如王者之剑、影之刃啥的,其实都很好逆,unity 写的。unity 平台还有一个格斗插件写的也不错,读的是早期版本,没有用状态机有点难读,但很多格斗业务逻辑相关的细节处理的蛮好,比如技能取消、帧优势(Frame Advantage这么翻译没错吧,街霸的概念)、物理,现在的新版本应该更优秀吧。
格斗相关的代码虽然读了很多,但是没有实际做过格斗游戏,不好给更多的参考意见了。
个人的一些反思,可能有点口齿不清乱七八糟 ,对真正做游戏未必有用,放在这里暂记下吧:再复杂的游戏,所谓的技能,本质上是在一段时间线上有撤销需求、需要强扩展设计的、分支极为复杂的、支持配置的异步逻辑,这些异步逻辑之间还可能会有并行交互。更远一点来看,游戏单位在游戏世界中的存在本身就是状态(比如云风的 skynet 游戏服务器中对每个单位生成一个 luastate 的 agent,配合 coroutine 来实现异步拉成同步不阻塞),很多技能就是单位这个状态体生成的子状态,这样理解单位的行为(比如 walk、idle,而不单单是通俗意义上的技能)会变得更有意思,仿佛和人类社会一样,一切都是并行顺着时间向前走的,没有回调没有中断,一切逻辑的上下文变得自然而然(异步的复杂性很多时候是来源于上下文的丢失)。
如果这套设想是成立的,简化异步编程,各行各业有很多探索,游戏行业未必就是做得最好的,比如我曾业余实践过基于 actor 模型(coroutine模拟的) 的技能框架,虽然很多细节至今没能解决,但确实是很有意思的探索。