主要参考GASDocument项目。
初始化
ASC需要知道OwnerActor(实际持有ASC的Actor)和AvatarActor(玩家控制的Actor)来初始化,它们可以是同一个,如果我们需要玩家死亡复活后仍旧保留之前的属性等数据,那么ASC一般放在PlayerState上。然后在PossessedBy
(服务器)和OnRep_PlayerState
(客户端)上分别调用初始化函数InitAbilityActorInfo
,它会将ActivatableAbilities
中储存的技能设置上Owner。
属性初始化一般是定义一个GE来完成,具体来说就是创建一个默认属性的GE,在Modifiers中配置需要设置的属性类型和值,持续时间设置为Instant,然后配置在对应的角色蓝图上,在ASC和属性集初始化完毕后就可以将其Apply到角色身上。
输入绑定
技能的输入需要单独定义一套InputID,其名称要和ProjectSettings中定义的Input一致,然后在定义GA的时候进行指定,GiveAbility的时候也要用对应的ID去Give,ASC会去监听按键的事件,TryActivate相应的GA。
Meta Attribute(GASDocument 4.3.3)
属性集中可以定义一些占位符(placeholder)属性,称为Meta属性,它的目的是和其他属性进行交互,如常见的伤害,我们把伤害作为一个单独的属性,角色收到伤害时就去修改这个属性,然后属性集中检测到它的变化后就去做处理,如减少HP,这样就可以把伤害计算和伤害处理的逻辑分离开。
冷却GE
技能的cd,输入绑定等通用逻辑,需要放在基类里面,尽管GAS已经把冷却、消耗帮我们做了,但是还是需要一个可以复用的冷却GE,通过Tag去区分每个技能,然后通过SetByCaller的方式去设置其Duration,就可以实现多个GA公用一个冷却GE了。
消耗GE
某个GA可能需要消耗一些属性,比如体力或魔法才能释放,
瞄准GA
开枪GA
开枪的过程可以分为以下几个阶段: 朝目标位置射出子弹 子弹未击中目标 子弹击中目标造成伤害
流星GA
朝指定位置落下一个流星造成范围伤害并眩晕。
AbilityTask
做瞄准的时候GAS文档项目中用到一个TargetActor,它用来指示目前玩家瞄准的位置,使用UAbilityTask_WaitTargetData
提供的接口
Demo尝试
射击技能(2022.10.8)
本来分开做了手雷和射箭的GA,完了发现其中很多逻辑是重复的,于是干脆做了一个GA_ShootProjectile
,然后手雷和射箭继承它。
射击需要的基本信息如下:
- Projectile类型
- 伤害值
- 伤害Tag
- 伤害GE
- 射击的蒙太奇资源
伤害实际上是由ProjectileActor去做,GA在实现Shoot方法时会生成Projectile,然后将与伤害有关的信息放入一个FGameplayEffectSpecHandle
,然后Projectile根据自己的逻辑去给目标上这个GE即可。
速度、朝向(velocity)等信息用BlueprintNativeEvent的接口去拿,C++先提供一个默认的,每个具体的Projectile类可以在lua中去实现自己的版本。由于ProjectileMovementComponent
的一些逻辑在初始化后就开始执行,我们需要使用延迟Spawn,在信息传递完成后再主动FinishSpawning
,保证其使用我们传递的数据。
手雷
手雷在使用ProjectileComponent的时候出现坐标持续变化的情况,是Actor的RootComponent没有规划好导致,StaticMesh开启了物理效果,会被阻挡,而Actor本体其实一直在移动。
弓箭(2022.9.21)
射箭需要播放一个蒙太奇,使用UAbilityTask_PlayMontageAndWait
中的接口进行播放,同时得到播放相关的回调,默认它会在EndAbility时停止播放,因此需要掌握好End的时机。
AbilityTask在创建完成初始化,拿到Owner(GA)的TasksComponent,然后在Activate时通过它来告知Owner(GA),GA便会将其存放在ActiveTasks数组中,以便在EndAbility时去告知激活的task去结束。
阅读代码得知这里的TasksComponent其实就是我们的ASC,ASC是其子类,它在这里的作用是同步task的各种状态给Owner处理,与技能本身并无关系,因此作为基类存在。
伤害计算时,每个GA最好对应单独的伤害GE,但是它们可以共用ExecCalc。
遇到的问题
- 将跳跃做成技能后客户端始终没有表现,只看得到属性变化了,查了半天发现是Activate的时候判断了
HasAuthority
,因此只在服务器调用了,而文档项目中使用的是HasAuthorityOrPredictionKey
。 - 之前HUD放在AHUD里创建,但是那样会找不到ASC,后来在Controller中的
OnRep_PlayerState
中创建,但是这样Standalone模式走不到,解决方式是在BeginPlay中专门处理Standalone的情况。 - 之前做UI的时候一直不理解那个Focusable是干嘛的,现在要接入手柄适配了终于理解这个属性的意义,它是为了确定玩家在用键盘、手柄这种外设时正在操作哪个UI,也就是正在Focus哪个UI。
- AbilityTask在没有手动调用的情况下执行了Activate,后面阅读文档发现是蓝图节点封装了一层调用,外部看不出来
- 遇到蒙太奇不播放的情况,发现是PlayMontageAndWait接口有一个选项是会在EndAbility后停止播放。
-
服务器客户端判断在Spawn的时候判断就行了,蒙太奇播放时不用判断,否则客户端看不见效果
- 服务器上去拿骨骼的Socket信息得到不更新的Pitch值,Yaw却能正常更新?
- 服务器上生成的Component在BeginPlay时就变成空了?似乎是引擎问题,重启就好了