总览
Ability 经常需要等待:等待输入释放、等待动画事件、等待目标选择、等待服务器确认。把这些等待逻辑直接写在 Ability 里,会让流程又长又难复制。AbilityTask 的作用就是把异步步骤封装成可复用节点,而 TargetData 则把“我命中了谁”变成可复制、可确认、可校验的数据。
本篇讲 AbilityTask、TargetData 的执行路径,以及射击、点选、范围技能如何落地。
源码依据
关键路径:
AbilitySystemComponent.hAbilities/TasksAbilities/GameplayAbilityTargetTypes.hGameplayAbility.h
ASC 提供 TargetData 相关 RPC 和确认函数:
ServerSetReplicatedTargetDataServerSetReplicatedTargetDataCancelledConfirmAbilityTargetDataCancelAbilityTargetDataCallReplicatedTargetDataDelegatesIfSet
这说明 TargetData 不是临时参数,而是 GAS 网络模型的一等公民。
AbilityTask 解决什么
AbilityTask 适合封装这些等待:
| Task | 用途 |
|---|---|
| WaitInputPress | 等下一次输入按下 |
| WaitInputRelease | 等输入释放,蓄力技能常用 |
| WaitGameplayEvent | 等外部 GameplayEvent |
| WaitTargetData | 等目标选择 |
| PlayMontageAndWait | 播 Montage 并等结束、打断 |
| WaitDelay | Ability 内延迟 |
Task 的好处是:Ability 保持流程清晰,异步节点能统一处理预测、取消和结束。
TargetData 里放什么
常见 TargetData:
| 类型 | 内容 | 场景 |
|---|---|---|
| Actor Data | 目标 Actor 列表 | 点选、锁定技能 |
| HitResult Data | 命中点、法线、骨骼、物理材质 | 射击、近战 |
| Location Data | 位置、半径、方向 | 范围技能 |
| 自定义 Data | 项目自定义结构 | 复杂弹道、格子、弱点 |
目标选择的关键不是客户端找到了谁,而是客户端提交了一份服务器能验证的数据。
射击技能流程
推荐流程:
- 客户端输入激活 Ability。
- Ability 播本地开火表现。
- 客户端 Trace 生成 HitResult TargetData。
- 通过 ASC 复制 TargetData 到服务器。
- 服务器验证距离、视线、武器状态、射击时间。
- 服务器构建 Damage GE。
- 服务器应用 GE,复制属性和 Cue。
void UGA_RifleFire::OnTargetDataReady(const FGameplayAbilityTargetDataHandle& Data)
{
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
if (!ASC)
{
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, true);
return;
}
FScopedPredictionWindow PredictionWindow(ASC, IsPredictingClient());
if (HasAuthority(&CurrentActivationInfo))
{
ApplyDamageToTargetData(Data);
}
else
{
SendTargetDataToServer(Data);
}
}
这里的示例强调结构:本地可以预测,但最终应用伤害必须走服务器校验。
服务器校验
服务器收到 TargetData 后不能直接相信。至少校验:
| 校验 | 说明 |
|---|---|
| 距离 | 命中点不能超过武器射程 |
| 视线 | 是否有遮挡 |
| 时间 | 是否过期,是否和开火时间匹配 |
| 阵营 | 是否可以攻击 |
| 状态 | 攻击者是否死亡、眩晕、无武器 |
| 频率 | 是否超过开火速率 |
| 目标有效性 | Actor 是否仍存在且可受击 |
服务器校验失败时,应取消或拒绝本次 Ability 副作用,并让客户端预测回滚。
自定义 TargetData
当 HitResult 和 Actor 列表不够时,可以定义自定义 TargetData。比如格子战棋技能需要格子坐标,弹幕技能需要种子和时间戳。
自定义 TargetData 要注意:
- 实现 NetSerialize。
- 数据足够服务器验证。
- 不放不可信的大对象指针。
- 不放纯客户端才能理解的 UI 状态。
- 版本变化要兼容旧客户端或做协议保护。
AbilityTask 与 EndAbility
Task 必须考虑 Ability 结束:
- Ability 被取消时 Task 要清理委托。
- Montage 被打断时要走失败路径。
- TargetData 取消时要
EndAbility或回到等待状态。 - 输入释放晚于服务器拒绝时不能继续应用 GE。
一个常见实践是:Ability 内所有 Task 绑定都收束到少量处理函数,每个函数第一行检查 Ability 是否仍 active、ActorInfo 是否有效、ASC 是否有效。
项目落地模板
推荐把目标选择抽象成项目级 Task:
| Task | 说明 |
|---|---|
UAT_WaitWeaponHitData |
武器 Trace、散布、HitResult TargetData |
UAT_WaitGroundTargetData |
地面点选、范围半径 |
UAT_WaitLockOnTargetData |
锁定目标 |
UAT_PlayMontageAndWaitForEvent |
动画和 GameplayEvent 合并 |
Ability 使用这些 Task,而不是每个 Ability 自己写 Trace 和输入。
常见坑
- Ability 里临时找目标。 服务器无法验证客户端意图。
- TargetData 不校验。 客户端可以提交超远距离或穿墙命中。
- 异步回调不检查 Ability 状态。 Ability 已结束还继续应用 GE。
- 自定义 TargetData 不做 NetSerialize。 多人下直接失效。
- Task 里强引用 Actor 不清理。 取消或死亡后容易悬挂。
- 预测窗口丢失。 输入释放、目标确认等后续事件需要新的预测窗口。
本篇结论:AbilityTask 让异步流程可复用,TargetData 让目标选择可复制和可校验。多人 GAS 项目里,“命中了谁”必须是一条清晰的数据流,而不是客户端一句口头声明。
源码路径索引
Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemComponent.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cppEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cppEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/Abilities/GameplayAbility.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffect.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AttributeSet.hEngine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayPrediction.h