unity如何设计一个游戏角色技能学习页面



 //差值运算 滑动页面时有缓慢效果;
 
 
 //当拖拽结束后才执行吃函数
 
 /// 控制滑动列表按照页数滚动
 //滑动结束后获取相应的位置;从而来通过滑动条控制页数
 //换可以通过水平位置获取活动值;
 
 //如果当前值小于滑动值;
 
 //离最近的值进行页面设置
 //滑动到当前页数把toggle按钮设置true;
 //注册4个公开的方法;
 
//定义两个公开的持有引用 //通过传递参数判断是否隐藏; //通过查找方法获取相应的组件 //按下快捷键 表示可以开始计时; //判断标志位 时候可以开始计时 //控制关键组件的仳例值 //判断计时器大于等于冷却时间 归零 //触发点击事件设置标志位
  • 打开Unity3D 打开Unity3D,会进入这个界面用来选择需要打开的项目。双击已有项目会打开该项目点击NE...

  • 曾仕强教授曾在《百家讲坛》上说,当你有一个女儿从小你不好好教她,就会害了别人全家所以,你跟谁有仇你就宠坏你女...

  • 茜さす(TV版-消音)-索菲 没错索菲老师又歌性大发了哇哈哈 这一集你们的夏目和娘口33只出现在片头和片尾曲里。呵...

  • 也许是因為小时候我家住的地区不属市内五区[呲牙]所以周边没有什么市内游泳馆。那时长青、丰乐、泉园、南塔、方家栏一...

1. 如何实现一个强大的MMO技能系统—序章

技能系统可以说是游戏中广泛存在而又最重要的系统了它是整个游戏战斗体验的核心。一套强大的技能系统可以让游戏的策略性鈳玩性得到极大的提升。然而技能系统牵涉到的模块非常多又会面对策划各种各样的奇葩需求,同时技能模块中很多系统的设计不仅仅對程序员的编码能力提出了一定的要求而且还需要程序员对游戏机制有着深入的理解。可以说要实现一套强力的高可用高扩展的技能系統还是有一定复杂度的

如果作为一个游戏玩家,谈论到技能系统我们一定会对War3的技能编辑器印象深刻,通过设计一个高扩展的技能系統编辑器它打造了无数的经典游戏。各种对战地图RPG守图,TD塔防趣味闯关,甚至产生了其中最经典的风靡全球的Dota为我们展示了War3技能系统的强大。后来Valve甚至因此专门开发了Dota2而且它提供了更强大的地图编辑器,基于该编辑器开发的自走棋更是开创了一个新的游戏品类這就是技能系统所展现的魅力。

如果论网络游戏领域技能系统之集大成者相信魔兽世界,守望先锋Dota2都可占得一席之地。深入玩过这几款游戏的话都会被他们技能系统之精巧所震撼。但是前两款游戏的技术分享实在太少所流传在外的资料并不足以让人去深入研究他们嘚技能系统的架构是怎样的。而Dota2因为地图编辑器的存在有大量的资料可以去获取[1][2]。你甚至可以直接写一个技能脚本去运行一下验证自巳的猜测是否正确。本人的技能系统实现可以说极大的受益于此,正因为有着可以大量阅读的文档和技能示例才让我对技能系统的认識有了质的飞跃,在此深表感谢

互联网上关于游戏开发相关的技术文章浩若烟海,佳作繁多本人也因此所获良多,受益匪浅然而对於技能系统设计这块尚有遗珠之憾,本系列的文章将围绕如何打造一个强大的MMO技能系统这个主题展开对技能系统涉及到的各个模块一一詳细讲解,从而让读者对技能系统有深入的了解和认识

本系列将技能系统分为如下模块依次讲解:

2. 如何实现一个强大的MMO技能系统——AOI

interest),它是MMO游戏中一个极其重要的概念主要解决游戏中多人同屏的问题。本文并不打算去讲AOI相关的算法如九宫格,十字链表六边形网格の类的具体算法实现,因为此类文章网络上已经有很多了虽然大多数开发者都接触过AOI,但是我发现其中很多人并没有真正深入领会AOI的本質因此本文会从设计的角度来阐释什么是AOI,为什么要有AOI以及如何去设计一个高质量的AOI系统。

很多开发者在设计AOI系统的时候通常是这样設计的(以最常见的九宫格为例)比如把场景按照一定的Size划分出很多格子,然后由AOI系统根据玩家位置来更新玩家的所在的网格索引每個玩家获取周围9个格子的玩家信息来进行显示。

这样就在细节上出现了很多如下问题:

玩家位置变化的时候如何处理立即更新或者等到丅一帧update的时候更新?
玩家位置变化或者周边其他玩家变化导致所在网格索引变化后带来的对象的增加、删除如何处理?
由于显示列表的嫆量限制玩家A的显示列表里面添加了玩家B,但此时玩家B的显示列表满了该怎么处理呢?
这样的问题林林总总很多开发者都花了不少惢思去处理这样的问题。但是请让我们更深入的考虑一下为什么会出现这样的问题呢?这样的设计就够了吗

又比如说某天策划脑洞大開,说我们来做个暗杀玩法吧并绘声绘色地给你描绘了如下让人心潮澎湃的情境:

“你从酒馆老板处接到暗杀任务后,展开羊皮纸一看上面只记录了目标所在的场景,并没有具体位置信息你需要传送到目标场景之后,自己去发现目标的踪迹于是你使用传送卷轴到达叻目标所在的场景,然后站在高山之上开启了鹰眼术,目光如电刺穿远方层层的迷雾随着你细致地搜寻,突然间发现远方的天际一个嫼点缓缓浮现你知道他就是此次的任务目标了,随即你立刻翻身上马向目标疾驰而去……”

那这个时候作为程序,你该如何实现这个需求呢

很明显,此时目标离你的距离远超你九宫格的距离你依然要将他显示出来。那这个时候该怎么办呢

所以,我们顺其自然地有叻一个新的概念——“关注者“AOI搜寻到的列表并不能就完全等同于关注者列表。而客户端显示的就是关注者列表里面的内容AOI搜寻到的列表只是用来获得关注者的手段。

为了让读者有个更直观的理解我举个例子。大家都很熟悉微博假如我们把游戏当作微博来看的话。

登录场景:当我们注册账号进入微博(游戏场景)的时候大家都没有关注者。这个时候我的微博首页的时间线上没有任何其他人的动态(游戏中此时只看得到自己)
获得其他玩家信息:这个时候为了让大家互相交互,微博提供了附近的人的选项(假设微信上的这个功能迻动到了微博)这个时候你点击附近的人(游戏中向AOI系统拉取周边九宫格的玩家列表),然后获得了一系列的用户信息列表此时你一┅关注,瞬间你的时间线动态就丰富了起来(关注者列表新增周边的玩家并显示在本地客户端)
消息同步:假设你的用户名是A,并且你關注了BC。那么此时你的关注者列表里面是BC而BC此时并不会说就关注你了,而是收到系统通知获得了新的粉丝A。那么我们现在看A的时间線会不停的收到B和C的动态,如果是在游戏场景下那么B和C最常发送的动态就是玩家B或C移动到了某位置P1(Obj:MultiCastPktPosChanged),玩家B或C使用了技能(Obj:MultiCastPktCastAbility)等等(此處仅为让读者能够直观理解概念实际上的同步优化处理有很多方法和技巧,在此并不展开)
关注机制:虽然上例中A能收到B和C的各种动態,但是A发出来的动态只有自己看的到B和C是看不到的,也就是说AOI并不需要双向连接这并不仅仅只是为了解决前面提到的第3个问题(细細思考一下)。同时这也是符合现实感受的比如说每天上班的时候地铁里面有很多人,你会关注谁呢你关注的目标会关注你吗?但此時如果周围有人拍了一下你的肩膀喊了你的名字,那么你就会立即关注他是谁对吧。(以游戏场景为例的话假如周边有很多玩家的話,你不一定需要严格按照位置远近来排序而是需要把跟你关联度最高的玩家加到你的关注者列表中。比如不远处的某个玩家向你发起叻切磋请求那么此时的解决方案就应该是把他加入到你的关注者列表,而不会说根据距离远近再来把这个玩家增添移除掉)
此时,回箌最开始的问题那么前面的问题的答案就显而易见了。

AOI的信息需要立即更新吗不一定需要,我们未必需要时时刻刻关注离我最近的目標附近的人这个功能里面的用户列表更新频率慢一点也没关系。
2. 因为我们并不需要时时刻刻关心附近的人所以自然也不会有某个玩家茬边界进进出出导致的AOI不停的创建和删除的开销问题。(如果还需要优化就加上lazy time处理下吧)

  1. 这个问题已经显而易见了没必要一定得互粉,看缘分啦我附近如果没其他人,又太寂寞的话说不定也会关注你关注列表满了就算了。

最后策划的需求解决起来也很简单了。搜尋目标的过程就是使用技能给自己加了个BuffBuff创建的时候将目标加到关注者列表中,目标死了后buff销毁并将目标移除出关注者列表。这在微博系统中相当于你直接搜索该玩家的账号直接加关注,任务达成后再直接取关就行了并不需要与”附近的人“这个功能发生任何关系。

因此当我们设计AOI系统的时候,并不需要严格只显示九宫格范围内的对象九宫格只是一个手段,一个让我们获得关注者的手段它只昰个过程,并不是结果大家在设计的时候多考虑这一点,使用起来就方便很多了甚至我们可以让技能改变关注者列表,从而达成一些特殊效果

3. 如何实现一个强大的MMO技能系统——技能

一个完整的技能系统逻辑层主要包括三个模块:技能模块,buff模块projectile(子弹)模块。然而茬很多项目中这三个模块可能并不是同一个人负责的,所以会发现这些模块间的联系并不够紧密然而技能的需求是非常复杂的,这样僦有可能随着技能需求的越来越复杂每个模块都搞了一套自己的脚本化的逻辑,最后维护难度急剧增加导致开发成本大大提高。本章將主要讲述一个高扩展性的技能模块该如何设计使得技能支持尽可能多的效果,同时又能够保持架构的精简和高扩展性

首先,我们大致思考一下平常玩的游戏大致会发现有如下几种技能类型:

主动施法技能(最常见的通用主动施法类技能,如普通攻击等一次性触发效果类技能(GeneralAbility)引导类持续施法技能(ChannelAbility),如大法师暴风雪)
开关类技能((ToggleAbility)点击技能开启/关闭效果类似于恶魔猎手献祭)
激活类技能((ActivateAbility)点下祐键激活/停止,一般是给普通攻击附加特殊效果)
在这里我们采用标记位(可进行或操作)的形式来表明这些技能的类型这意味着一个技能可能被检测既是被动又是引导类技能(我们将在最后的示例部分讲述为什么需要这样设计)。

下面我们一步步讲解这些技能类型该如哬设计在我们的技能系统中,所有的技能里面不包含任何具体技能效果及逻辑每个技能根据其类型提供若干抽象接口,具体执行效果甴策划去配置

当一个技能标记为被动技能时,则它是被动技能被动技能一般会在技能初始化时生效,技能初始化时会有抽象行为接口Ability::OnAbilityInit()那么被动技能就可以在这个接口中执行一些行为,具体执行那些功能由策划配置一般情况下策划会配置给角色添加Buff来监听各种事件以觸发各种效果。那么技能的各种效果该如何实现呢我们将在下一章buff系统中讲到。

当一个技能标记为主动施法的技能时它可手动释放,哃时在施法前需要有目标信息这里一般有三种情况:

技能释放时不需要目标即可释放(如群疗,踩地板技能) -> 1 << 1
技能释放时需要选定目标(单体指向性技能) -> 1 << 2
虽然技能系统千变万化但是初始阶段选择目标就是这三种情况的组合。有些复杂的技能会有两种组合情况:

比如策劃有个需求是闪现技能如果当前没有选择目标就朝前方闪现五米,有目标时则闪现到目标身后则我们的目标选择配置项就是1|2,当释放技能的时候我们保存下当前的目标信息(有无目标)后续在技能效果执行的时候去获取目标是否存在,然后实现不同的效果(Dota2中Lion的穿刺和先知的招树就是2|3。)

如果标记为普通施法技能我们将技能划分为如下几个阶段:

当我们满足技能释放的各种前置条件检查后,会进叺如下几个阶段:

技能起手Ability::OnAbilityStart()这个时候通常情况下策划一般配置为播放动画,技能开始转CD执行完成后技能进入前摇阶段。
技能前摇阶段鈈允许其他技能释放除非技能可强制立即释放(bImmediately=true)(如有些游戏要求滚动还有解控技可以立即打断当前技能)。前摇阶段提供一个配置時长CastPoint(Spell时间点),一般为动画抬手到攻击帧时长比如说播放一个挥刀动画0.3秒后动画到攻击点,这时策划就配置CastPoint为0.3秒
Spell阶段一般为技能具体逻輯执行阶段。通常会根据配置选择相应目标造成相应效果当执行完操作之后进入后摇阶段。
后摇阶段一般不能被其他技能打断除非是連招或者强制立即释放类技能。我们的技能系统不需要引入公共CD这个概念通过前摇后摇阶段的划分就足够满足各种需求了。
如果标记为歭续施法类技能(Channel)我们将技能分为如下几个阶段:

3.后摇阶段同普通技能。

本章主要讲述技能的类型和阶段划分通过良好的阶段划分和逻輯抽象就可以让技能有非常高的扩展性,这样在后续的开发中仅仅只需要做一些功能的扩展而不会影响整体的架构。

我会举出一些常见洏特殊的技能例子以便让读者有个直观的理解。

问:如果被动触发后会进入CD此时被动不再生效如何实现?(Dota2白牛重击效果)答:策划茬被动触发时配置StartCooldown(duration)即可技能的CD是个可配置行为,执行StartCooldown就转CD什么技能都能走CD,而且可以在任意情况下走CD走不走CD策划看着配就行。技能赱CD后然后在被动行为里面检测技能是否在CD状态如果在CD状态则不执行任何效果。
问:技能既可以主动释放又能有被动效果如何实现(Dota2敌法師护盾)答:被动效果其实是由Buff具体负责监听各类事件触发效果,主动技能也能在OnAblityInit的时候执行行为策划在此处配置加buff就行了。那么问题繼续来了如果有一个效果是禁用被动技能的话,前面的带被动效果的主动类型技能该不该禁用呢答:这时候技能类型为标记为位(bit)的莋用就体现出来了,如果标记为被动(AbilityFlag 问:蓄力类技能也是一种常见的技能类型啊为什么没有被提到?答:蓄力类技能在我们的技能系统Φ直接简化为普通技能了它只需要策划在技能抬手的时候播一个蓄力动画,然后CastPoint配成蓄力时长在Spell的时候再播一个动画就行了。继续问:那蓄力后的攻击动画到攻击效果还有一个间隔时长怎么办继续答:配置技能为引导类技能,例如配置ChannelInterval为10000秒ChannelTime为0.3秒,这样技能引导阶段僦只调用ChannelFinish蓄力的效果在ChannelFinish里面触发即可。
问:各类轻功击退,击飞冲刺攻击等各种位移效果怎么实现,他们之间万一存在各种复杂的咑断规则又怎么处理答:我们通过Buff去改变角色运动,状态属性等。下一章将会具体讲述这些情况

4. 如何实现一个强大的MMO技能系统——BUFF

Buff模块可以说是技能中最核心又最复杂的系统了。一个优秀的Buff系统能够让策划的创意得到最大限度的发挥大幅增强游戏的战斗深度和可玩性,并且同时也能让开发者轻易的扩展维护支持更多的效果和功能。本章将为你详细讲述一个强大的Buff系统是如何实现的(长文预警)

艏先我们将Buff系统分为三个层次,具体继承关系如下:

Buff:所有Buff的基类包含各类成员函数和基本接口。

Modifier:继承于Buff代表这个Buff是一个修改器,咜可以用来修改当前目标的各种属性状态等等。抽象Modifier这个类的目的是出于性能优化的考虑因为当Buff修改角色的属性或者状态时,会导致偅新计算角色的动态属性 而在游戏中我们很多的Buff并不需要修改角色的属性状态,仅仅用来提供一段逻辑那么如果它是一个Buff不是Modifier,就不需要重新计算角色的动态属性

MotionModifier:继承于Modifier,代表此类Buff提供修改玩家运动效果的功能因为牵涉到与运动组件的交互,所以抽象出一个新的類

Buff类层次结构划分了之后,那么Buff需要包含那些成员数据呢

在这里,我将说明一下CasterAbility以及Context这三个成员,这也可能是我们Buff系统中一些独特嘚点

Caster代表Buff的施加者,它有可能为空也有可能不为空,视具体构造时是否传Caster参数而定但是Buff有一个配置项bNoCaster(是否强制设置Caster为空)。如果bNoCaster = true则Buff的Caster一定为空。

为什么要有一个bNoCaster设置呢那是因为我们的Caster不仅仅是一个成员项,它还关系到Buff合并问题如果存在两个TypeId类型相同的Buff时候,當他们的Caster相同才可以走合并流程(Buff层数增加)如果Caster不同,则不能合并当策划有一些玩法需求可以多人给BOSS叠Buff时就可以配置Buff的bNoCaster=true,这样就不需要开发者在写代码添加Buff的时候小心翼翼的设置Caster参数为空了另外还有几种情况也需要设置bNoCaster=true,比如存在一个熔岩地图或者冰雪地图,玩镓每秒掉多少血量这个时候也可以配置bNoCaster=true。再比如说一些活动buff如双倍经验buff,红名惩罚buff都可以由策划配置bNoCaster=true。类似于双倍经验还有红名Buff這种所有需要存盘的Buff,我们都需要设置bNoCaster=true也许会有人有疑问,这样能满足需求吗完全可以,我会在最后的示例部分举出一个例子来解答這个疑问

Ability代表Buff是由哪个技能创建,它有可能为空也有可能不为空,视具体构造时是否传Ability参数而定通过Ability这个成员类型,我们就将Buff与技能联系起来了我们能在Buff中取得技能的各种数据,通过获取技能的数据然后由Buff来实现各种各样的技能效果。

BuffTagBuffImmuneTag由策划配置(基于标记位),标注这个Buff属于那些种类以及免疫哪些种类策划可以定义一些Tag如下:

Fire时,则代表这个Buff可以免疫所有木系和火系Buff由于Tag的实际定义由策划控制,策划可以根据他们的需求组合出各种各样的免疫效果我将在后面的示例里面描述一些基于Tag和ImmuneTag用法的例子来让读者体会Tag和ImmuneTag者两个概念抽象的简洁之美。

Context代表Buff创建时候的一些上下文数据它是一个不确定的项,通过外部传入各种自定义的数据然后在Buff逻辑中使用这些自萣义数据。

第二节:Buff执行流程

在Buff从创建到销毁的过程中我们划分为如下几个阶段:

Buff创建前检查当前Buff是否可创建。一般主要是检测目标身仩是否存在免疫该Buff的相关Buff如果被免疫则不会创建该Buff。
Buff在实例化之后生效之前(还未加入到Buff容器中)时会抛出一个OnBuffAwake事件。如果存在某种Buff嘚效果是:受到负面效果时驱散当前所有负面效果,并给自己加一个护盾那么这个时候就需要监听BuffAwake事件了,此时会给自己加护盾并苴把所有负面Buff驱散。这意味着一个Buff可能还未生效之前即销毁了(小心Buff的生命周期)
当Buff生效时(加入到Buff容器后),我们提供给策划一个抽潒接口OnBuffStart由策划配置具体效果。
当Buff添加时存在相同类型且Caster相等的时候Buff执行刷新流程(更新Buff层数,等级持续时间等数据)。我们提供给筞划一个抽象接口OnBuffRefresh由策划配置具体效果。
当Buff销毁前(还未从Buff容器中移除)我们提供给策划一个抽象接口OnBuffRemove,由策划配置具体效果
当Buff销毀后(已从Buff容器中移除),我们提供给策划一个抽象接口OnBuffDestroy由策划配置具体效果。
Buff还可以创建定时器以触发间隔持续效果。通过策划配置时调用StartIntervalThink操作提供OnIntervalThink抽象接口供策划配置具体效果。
Buff由于其有着生命周期可控低耦合(通过监听事件修改逻辑),高内聚、易于扩展的特性因此通过使用Buff来管理逻辑的话,不仅方便处理各种复杂的行为同时还能有效的减少开发者的维护难度。

例如延迟触发伤害是游戏Φ非常常见的需求在一些开发者的设计中就是直接给角色挂个定时器触发伤害。简单的游戏里这样做没什么大问题但是如果技能逻辑稍微复杂点,这样就会带来很多问题例如某天策划提出需求,如果受到控制效果时需要取消该延迟伤害此时你怎么办,直接干掉timer结果策划过了两天又提出了个新的需求,还是受到控制效果时需要这个延迟伤害立即触发,你又怎么办再又比如说,当角色受到伤害超過1000点时这个延迟伤害立即触发,你又该怎么做

这里就体现出Buff的方便之处了,我们可以直接添加一个持续时间为N秒的BuffBuff销毁时触发伤害。如果需求变更为受到控制时取消伤害那么我们就在Buff中检查当前是否包含有Tag为Control的Buff。如果有则设置Buff.bTriggerDamage=false,同时自我销毁然后在BuffDestroy触发的时候檢查是否触发伤害,如果bTriggerDamage为false则不触发伤害同理,当需求为Buff监听伤害超过1000点伤害立即触发时我们只需要通过Buff监听OnTakeDamage事件,检查当前受到的傷害值是否大于1000点如果是则销毁Buff,此时立即触发BuffDestroy并执行伤害效果

从上面的例子我们可以看出整个控制逻辑都是在Buff内部完成的,不需要各种手动开启/取消定时器只需要Buff扩展下逻辑检查即可,具有非常好的扩展性和高内聚性

Buff可以通过修改状态去影响角色行为逻辑。以下列举一些最常见的状态:

Stun(眩晕状态——目标不再响应任何操控)
Root(缠绕又称定身——目标不响应移动请求,但是可以执行某些操作洳施放某些技能)
Silence (沉默——目标禁止施放技能)
Invincible (无敌——几乎不受到所有的伤害和效果影响)
Invisible (隐身——不可被其他人看见)

这些状态是高度凝练的精华,抽象到极致的代表非常多的游戏效果实际上都是这几种状态+运动+动画的组合。这里很多开发者都会有一个设计误区就昰把Buff的状态跟运动和动画耦合在一块比如:眩晕状态一定就是播个眩晕动画,然后击退状态就是击退位移+击退动画这样最后导致的问題就是状态膨胀,而且各种逻辑耦合Bug频出,最后维护成本大大提高

Stun为例,很多人第一眼看过去就觉得它是个Debuff是个敌人给我方加的控制Buff。实际上并非如此Stun可以用到的地方非常多。例如有个技能是野蛮冲撞释放后2秒内向前移动10米并将敌人推开。那这个Buff的实现就是技能Spell的时候给角色加个Buff这个Buff会有个Stun状态同时带位移突进效果。挂上这个Buff后技能施放后角色2秒内就不会响应角色按键移动和释放其他技能嘚请求了,同时往前突进的效果由Buff控制将来处理各种位移打断效果也很方便。再比如说有个技能叫寒冰屏障:你被一道寒冰屏障所笼罩在十秒内不会受到任何物理和法术伤害,但这期间无法移动、攻击或施法那这个技能的实现也很简单,就是一个十秒的Buff同时添加了眩暈和无敌这两个状态如果还需要每秒回血,则StartIntervalThink(interval)然后OnIntervalThink的时候Heal当前角色即可。

除了各类战斗效果之外我们的Buff甚至可以扩展到一些其他场景。比如说打BOSS前有个播过场动画的需求此时策划希望隐藏Boss和玩家的血条和姓名。那么此时我们完全可以做个Buff这个Buff扩展个状态HideHpBar,当有这個状态时即隐藏血条和名字就行了而且我们还可以让这个Buff加上无敌状态,毕竟播过场动画的时候我们不希望玩家或者BOSS真的受到什么伤害

总而言之,Buff状态除了上面提到几种高度凝练抽象的状态外我们还可以根据具体游戏的需求去扩展各种特殊状态,以满足策划的需求哃时方便开发者管理逻辑。

在游戏中Buff的添加与移除是一个频繁的过程而玩家的属性来源有很多,如等级装备,成就任务,时装等等各种各样的来源相比于Buff,这些模块修改属性的频率要远低于Buff所以我们一般将玩家的属性划分为两层,第一层时Core(核心层)第二层是External(外部层)。Core层是玩家各个其他模块的属性总和而External层则是Buff修改属性的总和。两者相加既为玩家的实时属性

现在的MMO中为了增加动作表现仂,经常会有很多位移效果如突进,翻滚千斤坠,击退击飞,拖拽吸引等等。那么这些效果该如何实现呢而且有时候会遇到各種复杂的运动打断效果,比如击飞时不能被击退击飞过程又能被冰冻效果定住,然后又有破冰技能击退冰冻物体并解除冰冻效果面对這些复杂的情况,我们该如何设计呢

motionTypeId:运动类型id,配置项包含运动位移参数及相关数据。
priority:运动优先级每个运动都有优先级,低优先级不能打断高优先级

通过这三个参数,我们就能实现各类打断需求了

比如说击退的运动优先级是100,击飞的运动优先级是200那么在击飛过程中,施加击退Buff调用ApplyMotion的时候会返回false这时可以销毁掉这个击退Buff,即击飞时无法击退如果击飞时被冰冻,且冻在半空中停止不动那麼我们就需要设计一个静止Buff:运动优先级是300,作用效果是速度设置为0不受重力影响,同时修改Stun状态并挂载冰冻特效当破冰技消除冰冻效果时,则设置破冰Buff的位移效果为击退设置运动优先级为100,forceInterrupt为true此时ApplyMotion强制打断运动,冰冻Buff会触发OnMotionInterrupt回调在此接口中冰冻Buff自我销毁即可。

Buff修改运动仅代表修改运动轨迹比如说击退仅仅只是以直线移动一段距离。而击飞是以曲线移动一段距离同理轻功的翻滚,突刺其实都與击退是相同的运动轨迹他们都是在一定的时间内以直线到达目标地点,且都设置Stun状态它们不一样的地方其实仅仅只是动画层的表现嘚不同。(可能策划还会设置不同的Tag和ImmuneTag标记下)

我们要牢牢记住玩家看起来各种花哨的轻功击退击飞等位移效果实际上是State+Motion+Animation的组合。掌握住了这一点我们就可以通过简单的组合实现各种丰富的效果了,而不会被各种花哨的效果所迷惑以为他们都是不一样的效果,导致最後设计出无比庞杂且难以维护的系统了

第六节:Buff监听事件

Buff可以通过监听各类事件,执行特定逻辑或者修改事件数据来实现各种效果

最瑺见的事件监听一般有:

|= DamageFlag_NotMiss,标注该伤害无法被闪避就行了又或者如果有一个需求是给目标造成伤害后有10%几率触发DOT伤害效果,那么我们在OnAfterGiveDamage嘚时候取出event.Target并给这个目标加个DOT类Buff即可
OnBeforeDead,OnAfterDead监听我方死亡时触发如免疫致死效果可以通过监听OnBeforeDead事件修改角色当前的Hp>0,从而让角色提前退出迉亡流程以避免死亡死亡后触发额外效果,如爆炸或者召唤其他生物都可以通过监听OnAfterDead事件来执行
OnKill事件,监听我方击杀目标时触发如當击杀目标后获得治疗效果回复即可通过监听到Kill事件时给自己加一个HOT的Buff来实现。
开发者可以通过扩展各类事件列表让Buff通过监听对应事件僦能执行任意逻辑。不需要与任何模块耦合只需要抛出事件,监听事件执行逻辑即可获得Buff功能上的扩展。

以上我们通过六个小节讲述叻Buff系统主要模块的实现方法通过这样的设计,我们让Buff的深度和扩展性都能够得到了极大的提升几乎能实现各种各样的效果。足以让策劃的创意得到最大限度的发挥

为了让读者便于直观理解,我会提出一些具体实现的例子以供参考:

问:Buff互斥效果也很常见怎么做?
答:BuffTag和BuffImmuneTag可轻松实现比如说火系Buff和水系Buff互斥。无论策划的需求是存在水系Buff的时候无法添加火系Buff还是存在水系Buff的时候添加火系Buff会驱散水系Buff都鈳以实现。第一种情况最简单水系Buff配置Tag

问:霸体效果怎么实现,而且假如说存在破霸体效果又怎么实现而且Boss的霸体效果完全不受影响叒怎么实现?万一还存在特殊效果可以让Boss受到控制怎么办
StrongControl(免疫弱控制和强控制)就满足需求了。如果存在某个特殊的效果能让Boss受到控淛效果的话那这个Buff的Tag不要标记WeakControl和StrongControl就行了,这样它就无法被免疫掉了看起来复杂的霸体破霸体效果实际实现就这么简单,就这么清晰鈈需要引入任何新的系统。

问:**Buff存盘那块如何处理跟施法者相关的属性数据**如施法者可以给目标添加一个强力的毒Buff,具体伤害数值有施法者属性决定离线后依旧生效,直到Buff时间结束才移除
答:这块我们的处理依旧很简单,Buff依然设置bNoCaster=true但是在Buff创建的Context里面我们设置Context.DamageValue为根据施法者属性计算出来的伤害数值。然后Buff持续造成伤害的时候直接取Context.DamageValue即可至于说想要玩家离线再上线,Caster离线再上线后毒的伤害数值还能實时修改的话,这样的需求是不存在的如果一定要做,当然也能做只是麻烦一点而且也没有必要。这样的需求一般仅仅存在测试的大腦中策划是不会有这样的玩法需求了。

问:**常见的基于指定地点延迟触发的AOE效果怎么实现**当技能施法成功后就延迟触发,不会被打断AOE效果(如果能被打断,我们可以用引导类技能轻松实现)
答:我们将技能标记为可指定目标地点释放当技能Spell的时候我们先给自己加一個Buff,这个Buff仅仅用于延迟效果(当然可以有更多的可能性如监听到某种事件立即结束并触发AOE效果),当Buff持续时间到了的时候在OnBuffDestroy的时候创建AOE效果Buff这个AOE

5. 如何实现一个强大的MMO技能系统——子弹

子弹又称抛射物(Projectile),是游戏中非常常见的一个概念它常见于各大远程职业的技能效果:通过施法创建出一个带有弹道的物体,经过飞行后命中目标并触发各类效果在游戏中,我们经常能看见各种各样复杂多变的子弹效果那么它们是如何实现的呢?

游戏是欺骗视觉的艺术。对渲染有了解的开发者对此一定有深刻的体会而子弹系统的实现,也正是完媄的贯彻了这种理念

看起来变幻莫测的子弹系统本质上只有两类:

  • 线性子弹(LinearProjectile)。子弹为等腰梯形检测盒子无需目标,创建出来后沿着特萣方向飞行一段距离飞行途中进行相交测试(SweepTest),对检测范围内的目标调用技能执行OnProjectileHit触发效果到达目的地后触发OnProjectileHit(此时hitTarget为空)并自我销毁。
    楿信有很多开发者看到这里可能会比较疑惑为什么没有抛物线子弹,这种像炮弹一样经过抛物线轨迹飞行落地后会爆炸的子弹难道不昰非常典型的子弹吗?然而这事实上并不需要用子弹来实现我将在后文中讲述它的实现方式。

子弹系统的实现大致可以划分为如下几个鋶程:

创建追踪子弹需要的参数主要有:

Owner:表示子弹的创建者
Ability:表示子弹关联的技能
Target:子弹追踪的目标
Speed:子弹的飞行速率
创建线性子弹需偠的参数主要有:

追踪子弹基于直线速度飞向目标运动轨迹如下图:

线性子弹基于插值更新位置,运动轨迹如下图:

Class中执行命中逻辑这樣它就可以在技能模块中通过执行策划配置来实现各种效果了。子弹本身是没有任何特殊逻辑的它只有位置更新,检查命中目标和是否結束并销毁这几个简单的功能

子弹系统的逻辑本质上就是这么简单。当然这并不意味着子弹系统到此就结束了。以上所讲述的仅仅是孓弹的逻辑运行部分这些过程都是完全运行在服务端的,客户端并不会收到任何消息

4. 子弹的客户端表现

子弹系统如果仅仅包含这些逻輯代码当然是完全不够的。假如不通过某些手段将这个过程在客户端渲染出来那么玩家感知到的情况可能就是这样的:角色抬手施法后什么也没发生,过了几秒后目标突然就受到了一个伤害这样很明显是不符合直观感受的,那么为了让这个过程看起来合乎情理我们就需要借助一个媒介在客户端向玩家来展示子弹的行为。

我们选择通过特效系统来向客户端展现绚丽的子弹效果这就意味着整个客户端其實是没有Projectile这个概念的,有的只是各种各样的自定义特效

开发者非常容易陷入的思维误区就是服务器与客户端子弹是一致的,它们必须有楿同的逻辑而实际上这完全没有必要

比如说最常见的子弹需求:法师释放一个小火球飞向目标如果为了让这个技能表现效果更好,峩们会让火球从法师的手上(攻击挂点)创建出来然后飞向目标的胸前(受击挂点),而且我们可能还会给火球加一些随机弧线来让每┅个火球的轨迹都不一样同时火球命中目标后还会有个火焰溅散开来的效果。

那么在上面的需求中我们也需要让服务器的子弹做这些複杂的模拟吗?当然没必要了服务器只需要创建一个线性子弹,同时告诉客户端创建一个特效:这个特效需要在施法者手部的攻击挂点位置被创建目的点是目标的胸口受击挂点,特效的飞行速度为N米/秒特效是纯客户端的,而且有自己的行为脚本本例中的特效可能会掛载一个TrackingMoveScript,它会在创建的时候读取服务器发过来的数据来确定特效的初始位置速度和目标,同时它还有一些自己的配置参数比如说随機弧线偏移,碰撞时是否触发其他特效等等信息

这样整个的实际流程就是

服务器创建追踪子弹直线飞向目标,同时告诉客户端创建火浗特效并设置相关数据子弹经过一段飞行时间命中后调用Ability::OnProjectileHit造成伤害。

客户端创建一个火球特效特效读取数据运行自定义特效脚本,从角色手上创建出来并以不同的弧线飞向目标命中目标后火球溅射然后飞散消失。

整个过程都是完全独立并行的

也许会有开发者会觉得這样不就会有位置误差吗?也许服务器命中目标后客户端的火球还没有到达目标地点呢

然而这并不会有什么问题。MMO并不是格斗游戏也鈈是FPS有爆头需求,它不需要保证严格的位置一致性只要肉眼看起来没有明显误差就可以。事实上以正常的网速基本不会出现不符合直觀感受的情况。但是如果存在一个大型BOSS比如向恐龙那样身体前后很长,并且从嘴里发射子弹的话那么此时我们就需要在子弹的创建参數FromPosition那里取恐龙的位置并加上ForwardOffset才行。

为什么抛物线炮弹不是子弹

我们知道游戏中发射炮弹的需求通常都是这样的:

有个炸弹哥布林它的技能就是朝玩家所在的地点附近扔炮弹,炮弹落地后会发生爆炸并对周围的玩家造成一定量的伤害

通常开发者第一反应就是创建个抛物线孓弹,然后在飞行途中检查是否碰到了地面如果碰撞则直接触发一个爆炸范围伤害。这样的设计是很直观的然而游戏往往是欺骗视觉嘚艺术。

让我们再细细思考下这个技能的核心是什么爆炸,落地的瞬间爆炸让我们摒除画面的误导,想想如果一个技能仅仅是N秒后指萣地点造成范围伤害那么我们需要用到子弹吗?很明显不需要一个buff延迟触发伤害就解决了。那么炮弹本质上不正是这样的逻辑吗我們有起始点和目标点,有飞行速度自然而然的整个炮弹的生命周期就可以求出来了。我们创建一个Buff设置它的持续时间为炮弹的生命周期,Buff销毁时触发爆炸逻辑Buff初始化时创建一个特效,由这个特效负责在客户端去模拟抛物线运动

所以在这个需求中,其实根本没有什么拋物线子弹有的只是服务器Buff和客户端模拟抛物线飞行的炮弹特效。

同理类似于龙持续喷火这样的技能也不需要用到子弹。服务器只需偠创建一个buff持续触发范围伤害同时客户端则由特效根据服务器给出的位置数据去模拟喷火效果就行了。

在我们的系统中子弹实际上是汾为两部分。服务器逻辑层和客户端表现层逻辑层实现简单,表现层实现复杂由特效系统统一实现各种表现效果。同时子弹的伤害效果由技能触发从而保证与技能系统具有一致的扩展性。

我们在开发的过程中不要被表面的绚丽多彩的特效迷花了双眼应该去探求它真囸的内在逻辑是怎样的。它是否真的是一个子弹还是说仅仅只需要一个Buff就能实现了。

我们将举出一些子弹相关的例子供大家直观理解

問:闪电链这样的技能如何实现?

答:技能在施法(Spell)的时候的创建一个追踪子弹目标为当前技能选择的目标A,同时告诉客户端创建一個闪电特效(特效Handle由服务器创建后下发)客户端设置闪电特效的初始点为角色的手,终点为目标A的胸口受击点服务器记录特效的Handle用于茬后面更新闪电特效的位置。当追踪子弹命中目标A的时候技能保存当前的目标A信息,同时筛选下一个其他单位B此时再创建一个新的追蹤子弹设置起始点为当前目标A的位置,追踪目标为B并且通过特效Handle通知客户端更新特效的起始点为A的胸口受击点,终点为B的胸口受击点這样依次执行,就达到闪电链的效果了

问:一个技能创建多种子弹并且命中效果不一样怎么实现?

答:你可以在创建子弹的时候保存下孓弹的Handle然后在OnProjectileHit的时候对比下Handle,从而决定是否执行哪种效果

问:如果存在一个Buff,能够监听某个技能创建的子弹并让该子弹在命中目标嘚时候有几率分叉创建出两个新的子弹怎么办?

答:让子弹在Ability::OnProjectileHit的时候抛出事件此时Buff监听子弹命中事件,获取这个子弹的Ability检查该Ability是否为Buff指定的Ability,如果是则让该技能再创建出新的子弹即可。

问:像钩子这样能钩住人并拖回来的技能用子弹怎么实现

答:技能施法时创建线性子弹向前方运动,同时创建一个钩子特效设置起始点为手目标点为子弹终点位置,钩子以一定速度向前飞行

当线性子弹飞行到终点並且没有命中到任何单位时:

此时创建一个新的线性子弹,初始点为终点位置目标点为施法者位置。同时更新钩子特效的速度方向为返囙方向当线性子弹回到施法者位置后触发OnProjectileHit,此时手动Destroy钩子特效

当线性子弹飞行过程中命中单位时:

此时立即销毁该子弹并给命中目标添加一个拖拽Buff,它修改目标的运动让目标沿直线向施法者的位置移动,速度和钩子飞行速度一致到达目标点后Buff自我销毁。子弹命中目標的同时会创建一个新的线性子弹初始点为子弹当前位置,目标点为施法者位置以相同的速度返回施法者位置,同时修改钩子特效参數设置特效的末端钩子为绑定目标胸口受击点,特效速度为向施法者位置飞行当线性子弹到达施法者的位置时销毁钩子特效。

我要回帖

 

随机推荐