浅谈技能系统

标签: 技能 系统 | 发表时间:2019-04-10 12:00 | 作者:青狐秀水
出处:http://www.zhihu.com

前言

抛开需求

技能框架

技能框架的设计


前言


在比较复杂的游戏中,最为关键的,也就是技能系统了。

技能系统,很容易遭到程序员、策划、测试,甚至于玩家的挑战。一个技能框架的可读性、扩展性、安全性以及健壮性都是极其重要的,我们作为一个设计者,必须要把这些问题都考虑在内。作为一个综合性系统,程序、美术、音效,甚至于策划精妙绝伦或着是与无理取闹的需求,我们都必须要考虑进去。

在技能系统这一块,也许算是我做的比较多的,也是思考的最为深入的一个Gameplay的系统。

所以忍不住想写那么一些东西,去分享一下我的想法、以及做法。


抛开需求


我们先把策划人员的需求放下,我们当作没有这一类人。

我们仔细想想我们曾经玩过的游戏,当然,或多或少你是玩过游戏的,不然我很怀疑你是怎么做到游戏程序员的。


技能究竟是什么?

如下图,在一个典型的技能里,它通常都包含有动作、特效、声效,以及本次影响某些作用,以及持续影响某些作用或表现。至于持续影响的部分,则是可能有其他系统接管的,例如:BUFF系统(见 链接)。




为什么我们将一个技能分成两种部分,一是表现上的,一个是数值上的。从一个设计者的角度而言,如果这次的技能发生了,并且确确实实的命中的目标,我们就应该进行这次的结算,不管表现是否由于什么原因进行了中断,或者跳过。并且,如果有伺服器进行参与的时候,我们总是应该以服务器为准。


所以,为了更好的复用我们的代码(客户端以及伺服器同一套代码时,或者是跨项目间的重用),我们在设计技能系统的时候,一般都会注意,将表现的控制,以及数值处理上的控制分开(也就是所谓的逻辑与表现分离)。对于一些非常成熟的项目框架,甚至于我们可能还会在我们的技能系统上继承于某些Interface,例如,ISubSystem(ISubSystem可能是我们的框架提供出来,用于系统级扩展的一些接口),这种设计的框架,可能会给人一种,稍微优雅的直观感受。这时,我们可能会忍不住的Show一下自己的设计,于是搞了一个对于我们系统稍作抽象的Interface,叫IAbilitySystem(派生于ISubSystem)。然后再写一个叫做AbilitySystem去实现我们的技能系统,然后美曰其名的笑谈:以后别人看不顺眼我们的AbilitySystem,就可以写一个叫做SkillSystem的玩意去替代,反正我们外部持有的对象是ISubSystem/IAbilitySystem。




这种情况下,你的代码固然丑(很多程序员都认为别人的代码很丑,没有问题吧),但也会因为设计良好而被人点赞。


你付出的,仅仅是对你愿意暴露出去的API,再在interface声明一次。麻烦?也许吧。所以这就是设计者该去权衡的地方了,一旦不当,就成了所谓的设计过度。


在现在这种情况下,我们可以简单的分出几个类型了,这里不探讨设计过度与否。





把这个ISubSystem扩展开,其实就很接近我们整个Gameplay框架的一个原型了。当然这已经不是我们聊的事情了。


技能框架的思考


如果我们的野心足够大,我们可以想要的会是一个让我们一劳永逸的技能框架,这个想法有些膨胀了,但其实我是觉得可以做到的。尽管不是一个可以应用于所有类型的游戏的技能体系,但框架本身是可以被复用的,代码不行,思路也一样。对于不同的技能体系而言,这应该有些困难,因为我们思考的方向、要点、难点,其实都不一样。


比如一个回合制的战斗系统(梦幻模拟战),跟一个即时战斗的ACT战斗系统(战神),不管是设计难度还是实现难度,都相差十分巨大,巨大到跟你知道的你跟马云的差别一样。


所以,不管是为了效率、实现难度,我们可能会针对不同的技能应用场合,设计不同的技能系统,并选择一个最优解。尽管他们的大体框架很接近,但是细节上的实现却是完全颠覆的。


我们以 回合制(Turn-based)的游戏来开始我们的话题。


思考下面的技能。


火球:角色朝前方发射一个火球,火球命中目标(角色)的时候,对目标造成伤害,火球消失。

大爆炸火球:角色朝前方发射一个火球,火球命中目标(障碍、角色)的时候,造成爆炸,对周围的目标造成伤害,并且对爆炸波及的目标,造成持续性灼烧伤害。


如果是有实现过技能系统的同学,自然很容易就知道,技能 火球的实现。

我们先来看看一个“十分切题”的代码, 这里的十分切题并不意味着是最优解

   public class Fireball : IAbility
{
    /// <summary>
    /// Config {
    ///  float velocity; // 速度
    ///  float radius; // 火球半径
    ///  string fxName; // 特效名称
    ///  //其他
    /// }
    /// </summary>
    public FireBallConfig Config { get; private set; }

    /// <summary>
    /// 技能持有者
    /// </summary>
    public IAbilityOwner Owner { get; set; }
 
    public void Launch(Action<FireBall, List<IAbilityTarget>> onHit)
    {
        // 执行动作表现
        // 延迟 0-n秒 执行特效表现
        // 延迟 0-n秒 播放飞行声效
        // 命中后回调
    }
}

乍看之下,这种代码好像没有什么问题。但是一旦选择这种代码,我们再去思考一下,需求人员如何跟我们进行数据以及功能的交换,美术人员如何参与技能表现的调优?


可能造成如下结果:

一:

策划人员维护一张超级庞大的表,然后所有的技能配置项都填在里面,每一行就是一个技能,但是由于字段含义,势必会造成一行中很多空缺字段,维护难度,增删难度,剧增。

二:

策划人员维护一大堆技能表,不同的技能表分别配置不同类型的技能。很大情况下,策划人员可能后期都在找这个技能配置在哪个表中。

三:

一旦美术同学发现哪里需要调优,我们可能要先把美术同学的资源放进来,然后经过一大堆的游戏逻辑,终于到了可以展示我们技能的场景,好不容易等到MP满了,我们可以释放我们的技能看看了( 别怀疑,这种做法在以前很盛行,其实更多的主要是项目一直处于所谓的紧张期,当然还有很重要的一个原因,所使用的引擎太土鳖)。

四:

它无法量产。

五:

它的命名太过“策划化”了,如果我们要实现一模一样的功能,但是名字叫做冰刺(Icicle)的技能,然后用了火球(Fireball)这个名字的技能,总觉得有一种莫名的喜感,就是不知道策划同学会不会无所谓就是了。


换句话说, 如果这是一个大型项目,我觉得上面的代码完全是技能系统的外行,很有可能一开始设计这种技能的人到了后期每天会被鞭一百次。当然, 对于不注重技能扩展性,或者是技能所在的比重并不大的项目而言,这完全是没有问题的,因为这都不是重点,也不是需要量产的功能,自然也就不是问题了。将就一下,随便就过去了。


我觉得,作为一个合格的技能体系,它应该至少符合下面的几点要求。

1. 代码尽可能优雅的。

2. 框架是可以轻易扩展的。

3. 可以量产的。

4. 可以游离于游戏之外,单独进行重放的。


那么,有没有那么一个共同的抽象,可以让我们做到这种技能体系呢?


我们尝试把数值逻辑与表现逻辑分开。

在表现层上,我们需要一个工具/插件,它至少符合以下几点:

1. 支持保序的表现

2. 可以扩展的

3. 可以同时编辑我们不同类型的Track的

4. 可以离线验证我们技能

5. 可以与数值逻辑部分交换上下文的事件系统(EventSystem、EventManager)


如果你使用Unity,可以考察一下uSequencer/Timeline系统。

如果你使用UE4,毫无疑问自然要考虑的就是Blueprint了


技能框架的设计


我们这是一个框架,应该从全面的角度去思考问题,如果单纯的从某个技能出发,是很难设计出一个可以扩展的框架的。

假设我们在与策划人员在参与讨论的时候,已经对即将要去实现的技能体系有了大概的认知。


我们可以尝试从以下方面去考虑一下,我们的技能能不能这样。


1. 对技能的特点进行抽象,要知道,策划人员往往并不具备技能特征抽象的能力,或者说习惯

2. 对技能需要配置的数据项进行归纳,往往这些数据项都是需要进行随时配置的,我们要对策划描述中出现的数值/词语变得敏感。比如,速度、方向、重力、加速度、大小等物理特征,都可能需要程序人员转化成数值配置项。

3. 尝试着将技能套入我们的技能框架

4. 与策划人员进行反复沟通,看看能不能尽可能的切入我们已有的技能体系,而不要做一只特立独行的鸡。功能需求,是需要沟通的,不是需求方让你做什么,你就做什么,做出来了,就觉得很厉害,做不出来就不行。

5. 给你编辑好的技能,一个合适的命名。合适的技能命名会具有比较高的辨识度,可以让你看见它的时候,一下子反应出这是什么,而不是一个黑人问号脸。

6. 你的技能应该如何设计成可以独立运行。有时候,我们虽然知道数值逻辑与表现逻辑需要分离,但是实现起来可能并不是那么轻松的,你需要绞尽脑汁的将数据项从表现系统抽离。一个最漂亮的做法无异于:我们可以在一个空场景中,加载1到N个角色,这些角色,至少包含一个是技能发起方,其他的自然是可被搜寻的对象。这个技能施展的时候,这个技能发起方可以有一些动作,动作伴随特效,然后他往前一砍,一个特效往前飞行,碰到某个可以被搜寻的对象的时候,这个对象会被捕获到你的技能流程里面,交由技能流程做处理,这个技能流程可能需要进行例如这个对象是否HP为0等等一系列是否符合捕获条件的判断。然后,这个对象头上可能会冒出一些伤害数值,以及受击特效,受击动作,如果技能可能把一个角色打飞,可能这时候还会对应不同的后续处理。

7. 往往一个技能表现会有两部分。一部分是发起者(Sponsor)的,一部分则是回应者(Responders)的。对于类似于三国无双这种游戏而言,可能一刀下去就是一大片的。这种情况自然也要纳入考虑。

8. 更复杂的一些技能系统,可能会存在着连携技能。就是你跟另一个合作伙伴同时在使用可以共鸣的一些技能的时候,会产生出双人组合技,这种系统就要看具体的需求了。


我们看看能不能把技能Sponsor部分按照下面的方式划分,还有声效部分就不写了。




对于Responders方,我们看看能不能分成这样一种方式。




接下来,就是对我们抽象出来的节点做一个代码实现的事情了。


这两部分串行起来的方式,其实就是OnHit,每次OnHit就会触发一类受击表现。

然而,总会出现有非常规的问题,比如,霸体的受击表现。

这里可能就会出现一种情况,一种Attack表现,对应N中OnHit表现了。至于飘血、数值,走的又是另一套方式,一般可能会走EventMessage事件分发机制。这样既可以解耦,也可以在技能编辑器下免去没有数值处理对象的尴尬。


此外,我们还应该对技能描述尝试抽象出一个个独立的函数(或方法),这些方法我们可以重用在其他技能的Timeline(或BP)之中,例如,发射火球,这种功能,我们可以视为“Load and fire a VFX”;受击动作、攻击动作,可以视为“Play animation clip”等等。而且,这个才是所有技能设计之中,最应该优先分割、考虑的。也是我们未来可以真正复用到其他项目之中的技能功能子集。



来源:知乎 www.zhihu.com
作者: 青狐秀水

【知乎日报】千万用户的选择,做朋友圈里的新鲜事分享大牛。 点击下载

相关 [技能 系统] 推荐:

浅谈技能系统

- - 知乎每日精选
在比较复杂的游戏中,最为关键的,也就是技能系统了. 技能系统,很容易遭到程序员、策划、测试,甚至于玩家的挑战. 一个技能框架的可读性、扩展性、安全性以及健壮性都是极其重要的,我们作为一个设计者,必须要把这些问题都考虑在内. 作为一个综合性系统,程序、美术、音效,甚至于策划精妙绝伦或着是与无理取闹的需求,我们都必须要考虑进去.

新时代的 Linux 系统管理员都需要哪些技能?

- - 外刊IT评论
你上次编译内核是多久以前的事儿了. 新生代 Linux 管理员的答案很简单:我压根儿没编译过内核. 我也从没编译过内核,当然自己弄着玩儿的不算. 我实在想不到我为啥需要自定义内核,所以我就用我的“开箱即用”内核了. Linux 老鸟们可能会笑话我们,但你不能否认:随着越来越多的企业采用 Linux,新生代的 Linux 管理员虽然生就一身相当不错的技术实力,却因缺乏编译内核这类简单却基础的技能而和一个优秀的 Linux 管理员之间存在差距.

壳系统

- Vernsu - It Talks-魏武挥的blog
经常有人被我问到“你用什么浏览器”时的答案是:傲游啦360啦,但事实上,这些都不是真正的浏览器,从技术角度讲,充其量只是在IE浏览器上加一个壳罢了. 在国外,壳浏览器是以“皮肤”的形式存在,纯属为了美化浏览器而用. 但在中国,壳浏览器成了一门生意. 奇虎的主要收入来源并非来自那个由于一场商战而赫赫有名的安全卫士,而是来自于360浏览器(它有两个版本,分别以IE和Chrome为内核).

秒杀系统

- - 开源软件 - ITeye博客
秒杀系统架构分析与实战. (反馈非常好的文章,推荐). (1)查询商品;(2)创建订单;(3)扣减库存;(4)更新订单;(5)付款;(6)卖家发货. (1)低廉价格;(2)大幅推广;(3)瞬时售空;(4)一般是定时上架;(5)时间短、瞬时并发量高;. 假设某网站秒杀活动只推出一件商品,预计会吸引1万人参加活动,也就说最大并发请求数是10000,秒杀系统需要面对的技术挑战有:.

Ext文件系统

- Haides - 博客园-首页原创精华区
  虽然从Ext2到Ext4,找数据的方式发生了变化,但是,磁盘的布局还是非常相似的. 其实这个东西也不需要变化,因为现在也没什么特别巧妙的方式,而且磁盘的吞吐量、效率的瓶颈也不在这里. 当然,这里排除那些根据自身文件特点设计的数据库,毕竟还是为了支持通用文件.   Boot在第一个块,放的应该是引导程序,超级块就放在了第二个块上,如果不是可以在mount的时候通过参数sb来设置.

HBase 系统架构

- - 博客园_首页
HBase是Apache Hadoop的数据库,能够对大型数据提供随机、实时的读写访问. HBase的目标是存储并处理大型的数据. HBase是一个开源的,分布式的,多版本的,面向列的存储模型. 5 可在廉价PC Server搭建大规模结构化存储集群. HBase是Google BigTable的开源实现,其相互对应如下:.

Linux系统监控

- - CSDN博客系统运维推荐文章
查看所有的进程和端口使用情况:. 查看nginx并发(连接数)进程数:. 查看当网络连接状态中,已建立连接的数量:. 查看系统tcp连接中各个状态的连接数. 输出每个ip的连接数,以及总的各个状态的连接数. df -hl 查看磁盘使用情况 . df -hl 查看磁盘剩余空间. df -h 查看每个根路径的分区大小.

mysql 权限系统

- - 数据库 - ITeye博客
mysql 权限系统控制一个用户是否能进行连接,以及连接后能够针对那些对象进行什么操作. mysql权限控制包含两个阶段. 2:检查用户是否具有所执行动作的权限. 本文实例,运行于 MySQL 5.0 及以上版本. MySQL 赋予用户权限命令的简单格式可概括为:. 一、grant 普通数据用户,查询、插入、更新、删除 数据库中所有表数据的权利.

银行核心系统-贷款系统【信贷系统】

- - ITeye博客
一、         贷款业务. 贷款按期限分为短期、中期与长期贷款,短期贷款是指期限在1年以内的贷款,中期贷款是指期限在1年(含1年)至3年(含3年)之间的贷款,长期贷款是指期限超过3年的贷款. 贷款的种类目前有个人助学贷款和个人住房贷款:. l     个人助学贷款:须提供两位担保人,无须质押物,贷款额度不超过人民币10万元.

理解Linux系统负荷

- Adam - 阮一峰的网络日志
如果你的电脑很慢,你或许想查看一下,它的工作量是否太大了. 在Linux系统中,我们一般使用uptime命令查看(w命令和top命令也行). (另外,它们在苹果公司的Mac电脑上也适用. 你在终端窗口键入uptime,系统会返回一行信息. 这行信息的后半部分,显示"load average",它的意思是"系统的平均负荷",里面有三个数字,我们可以从中判断系统负荷是大还是小.