游戏技能(GameplayAbility)

角色所能做的任何事几乎都是技能!

在GAS框架中,GameplayAbility(GA)所能做的事情非常广泛,一个角色几乎所有能称之为”能力“的事情都可以用它实现,比如:

  • 攻击
  • 跳跃
  • 翻滚
  • 吃药
  • 采集
  • 受到伤害时20%的概率获得一层护盾
  • 开锁
  • 骑马
  • 被击飞(没错,挨打也是一种能力!)
  • 死亡

但一些基本的功能不要用GA实现,如基础移动、UI等。

(这是官方的建议,大钊的分享中也强调了这一点,但我在实际开发时,给基础移动配了一个空的GA,在每次输入移动时,调用CanActivateAbility()利用其标签判断当前能否移动。

并且,在做商店购买时,我也想用这个套路去做一些逻辑判断,比如眩晕时不能购买物品之类的,因为GA的标签判断很好用,我不想自己写一套。)

GA的定义

技能等级:GA默认实现了一个Level,在赋予技能时,需要传入技能的等级,这个等级可以同步到GE,或者直接在GA中使用。

赋予技能

角色想使用一个技能必须先被赋予,也就是学习技能。想赋予技能,只要调用ASC的GiveAbility()即可,这个方法有多个重载,包括通过技能类还是具体技能赋予,指定技能等级,以及绑定的输入等。

技能既可以在角色创建时赋予,也可以在游戏过程中动态赋予,甚至可以与一个持续性的GE绑定,让角色获得一个临时技能。

激活技能

GAS提供了四个激活技能的方式,总得开说分为两种:直接调用、通过事件激活。一般主动技能都是通过直接调用激活,而事件技能则用来处理一些触发技能。

主动技能

客户端预测的技能过程与服务器端激活技能的过程略有不同,但关键的调用点有这些:

1.调用CanActivateAbility()检查Tag、冷却时间、消耗等是否满足条件,你可以重写这个方法附加自己的条件。

2.调用PreActivate()。这是执行技能前最后的处理,源码在这里同步了移动,并且为技能注册了一些标签相关的委托。你可以重写这个方法添加一些技能初始化的逻辑。

3.调用ActivateAbility()。执行施放技能逻辑,你的技能子类应该重写它并且编写自己的技能逻辑。

被动技能

我的设计中,将被动技能和触发技能进行了区分,被动技能指那些在赋予时自动激活,效果固定的技能。而触发技能则是在特定情况下被激活的技能。

这里我考虑了被动技能在角色创建前就添加的情况以及角色创建之后再添加被动技能的情况,这两种情况的回调分别对应UGameplayAbility中的OnAvatarSet()和OnGiveAbility()。

比如我将ASC注册到PlayerState上,那么在角色创建之前,被动技能就已经被赋予了,另外可能有切换角色或者角色重生的情况,此时可在OnAvatarSet()中调用TryActivateAbility()。

而对于那些在角色创建之后,在游戏中动态获得的被动技能,则可在OnGiveAbility()中调用TryActivateAbility()。

我建议在你的Ability子类中添加一个bool值来标记它是否是被动技能,然后在上述两个方法中调用一个类似TryActivatePassiveAbility()的方法,来判断是否要在技能添加时立即执行。

*被动技能的移除:被动技能移除时,可以用OnRemoveAbility()接口来撤销被动技能添加的GE等永久效果。关于这些GE是否会跟随GA的移除自动移除,我暂时还不知道也没做过实验(TODO)。

*如果你担心被动技能重复添加,只要把GA的Instant策略设置成单例即可。

触发技能

触发技能要通过事件激活,最终会调用GA的ActivateAbilityFromEvent节点,但要注意,如果你的蓝图中存在ActivateAbility节点存在,那么ActivateAbilityFromEvent节点将永远不会被调用。

要实现一个触发技能,你需要做以下步骤:

1.给GA添加一个Trigger,它可以监听Tag的添加,或者自定义的GameplayEvent。

2.你可以通过UAbilitySystemblueprintLibrary::SendGameplayEventToActor()来向一个角色发送事件,同时传送一个FGameplayEventData作为载荷数据,这个数据里可以包含标签、目标信息或是其他自定义的信息。

3.如果是监听Tag添加的Trigger,只要向Actor添加Tag即可。

技能标签

AbilityTags:该技能的标签。

CancelAbiliiesWithTag:当这个技能执行时,打断具有这些标签的技能。

BlockAbilitiesWithTag:当这个技能执行期间,阻止具有这些标签的技能激活。

ActivationOwnedTags:这个技能执行期间赋予技能施放者的标签。

ActivationRequiredTags:当Owner拥有全部这些标签时,技能才能施放。

ActivationBlockedTags:当Owner拥有这些标签之一时,技能无法施放。

SourceRequiredTags:当Source拥有全部这些标签时,技能才能施放。

SourceBlockedTags:当Source拥有这些标签之一时,技能无法施放。

TargetRequiredTags:当目标拥有全部这些标签时,技能才能施放。

TargetBlockedTags:当目标拥有这些标签之一时,技能无法施放。

后四个标签组,仅在由事件触发的GA中生效,对于这里Source和Target到底指的是谁我还没研究.....Source可能指的是事件的发送者,Target可能指的是事件的接受者,或者载荷数据中的TargetData,待研究。

技能冷却与消耗

GA内置了消耗与冷却的判断和计算,这部分的详细内容放在GE中做说明,请参见GameplayEffect。

//这里放一个链接

技能升级

有两种方式可以做到技能升级:

1.移除旧的GA,然后根据新的等级再赋予一个新的GA。

2.在ASC中找到GA的Spec,修改其等级。

第二种方式明显是更加方便合理的,但需要确认的一点是,当一个持续性的GE使用GA的Level作为参数修改属性时,GALevel的改变会不会自动更新到修改器上,如果不能的话,被动技能应该采用第一种方式进行升级,因为它在赋予时就激活了。这个事我还没确认。

也不能无脑采用第一种,因为技能被删除时,如果一个技能正在被使用,它将强行被停止。所以如果有战斗中升级技能的需求,就只能采用第二种方法。

技能逻辑的组织技巧

  • 技能模板:一些常用的技能套路,比如“朝目标方向发动近战攻击”、“朝目标发射子弹“、”使用物品“等,可以实现一个父类GA蓝图,然后在具体角色使用时进行子类化,然后将其中的不同开放成变量在子类中配置,如要播放的动作,要发射的子弹等。
  • 逻辑分组:把一些常用逻辑包装到函数中,对策划隐藏其实现,如”选目标“等,这里要拿捏好粒度,打包过多逻辑会降低灵活性,打包过少则起不到抽象的目的,另外还需要谨慎判断逻辑是放在C++还是蓝图中,一般来说,放在C++中的逻辑更加通用,更加抽象,一般通过拓展Task实现。
  • 用Tag而非id/字符串key,要习惯将Tag作为GAS中做条件判断的第一选择,包括蒙太奇触发的事件等,这可能需要自行去包装一些收集Tag标记的接口,但总体来说是值得的。
  • 一些需要综合GE、Task等其他功能的技巧,会单开一个单元进行总结。
posted @   yutuer  阅读(726)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示