GAS 系统深度学习指南UE 5.4 / 5.5

09 TargetData 目标与瞄准

目标:掌握 GAS 中"技能要打谁"的标准数据通道——FGameplayAbilityTargetData 与 Handle 的完整结构、自定义 TargetData 子类化(网络序列化)、TargetActor 的工作原理与四种确认类型、目标过滤器、World Reticle 准星系统、服务器端目标生成、GE Container 便捷封装,以及四种经典目标系统场景。


9.1 为什么需要 TargetData

技能的"目标"远不止"一个敌人"那么简单:可能是指向性(鼠标点谁打谁)、范围(AOE 圈)、方向(扇形)、甚至无目标(自身 Buff)。GAS 用统一的 FGameplayAbilityTargetData 结构承载目标信息,让技能逻辑与"目标怎么选"解耦。

关键:FGameplayAbilityTargetData 是基类,不应该直接实例化——必须用它的内置子类或自定义子类。

9.2 TargetData 的结构体系

9.2.1 内置子类

类型 说明
FGameplayAbilityTargetData_SingleTargetHit 单个命中(射线/HitResult),含撞击点/法线
FGameplayAbilityTargetData_ActorArray 一组 Actor(AOE 命中角色)
FGameplayAbilityTargetData_LocationInfo 一个位置(地面 AOE 中心、指向点)
FGameplayAbilityTargetData_SingleActor 单个 Actor

9.2.2 FGameplayAbilityTargetDataHandle(包装器)

直接传 FGameplayAbilityTargetData 指针有内存管理风险(基类多态、生命周期),GAS 用 Handle 包装 TArray< TSharedPtr<FGameplayAbilityTargetData> >:

// 创建 Handle 并添加目标
FGameplayAbilityTargetDataHandle Handle;

// 霰弹枪多弹丸命中
for (int32 i = 0; i < HitResults.Num(); i++)
{
    FGameplayAbilityTargetData_SingleTargetHit* HitData =
        new FGameplayAbilityTargetData_SingleTargetHit();
    HitData->HitResult = HitResults[i];
    Handle.Add(HitData);
}

// 读取
if (Handle.Num() > 0)
{
    const FGameplayAbilityTargetData* Data = Handle.Get(0);
    const FHitResult* Hit = Data->GetHitResult();
    AActor* Target = Hit->GetActor();
}

9.2.3 自定义 TargetData 子类(携带任意数据)

USTRUCT(BlueprintType)
struct MYGAME_API FGameplayAbilityTargetData_CustomData : public FGameplayAbilityTargetData
{
    GENERATED_BODY()
public:
    UPROPERTY() FName CustomName = NAME_None;
    UPROPERTY() int32 HitBoneIndex = -1;

    // 必须重写 1:返回自身 ScriptStruct(用于复制类型识别)
    virtual UScriptStruct* GetScriptStruct() const override
    {
        return FGameplayAbilityTargetData_CustomData::StaticStruct();
    }

    // 必须重写 2:网络序列化
    bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
    {
        CustomName.NetSerialize(Ar, Map, bOutSuccess);
        Ar.SerializeIntPacked(HitBoneIndex);
        bOutSuccess = true;
        return true;
    }
};

// 必须 3:启用网络序列化类型特性
template<>
struct TStructOpsTypeTraits<FGameplayAbilityTargetData_CustomData> :
    public TStructOpsTypeTraitsBase2<FGameplayAbilityTargetData_CustomData>
{
    enum { WithNetSerializer = true };   // 必须为 true,否则 Handle 无法跨网络传输
};

常见陷阱:忘记 WithNetSerializer = true → Handle 的网络序列化失效,客户端与服务器之间无法传递 TargetData。

9.2.4 TargetData 生命周期

1. 产生(Production)
   ├─ TargetActor 的射线检测/碰撞重叠
   ├─ 手动创建(GA 内构造)
   └─ GE Container 的即时目标选择

2. 传递(Transfer)
   ├─ 存入 EffectContext(AddHitResult)
   ├─ 通过 RPC 从客户端发到服务器
   └─ 通过 Handle 在蓝图/C++ 传递

3. 消费(Consumption)
   ├─ AbilityTask 接收并处理
   ├─ GE 应用时使用
   ├─ ExecCalc / MMC 中读取
   └─ GameplayCue 中访问
// 存入 EffectContext,随 GE 传递
FGameplayEffectContextHandle Ctx = ASC->MakeEffectContext();
Ctx.AddHitResult(HitResult);
FGameplayEffectSpecHandle Spec = MakeOutgoingGameplayEffectSpec(DamageGE, Level);
Spec.Data.Get()->SetContext(Ctx);
ASC->ApplyGameplayEffectSpecToTarget(*Spec.Data.Get(), TargetASC);

9.3 TargetActor:谁来"选目标"

AGameplayAbilityTargetActor 由 WaitTargetData AbilityTask 生成,负责可视化 + 捕获目标信息。常见子类:

TargetActor 功能 适用
AGameplayAbilityTargetActor_SingleLineTrace 单条射线检测 射击武器、指向性技能
AGameplayAbilityTargetActor_Radius 半径范围(可拖拽) AOE 放置
AGameplayAbilityTargetActor_ActorPlacement 放置预览 Actor 建筑/陷阱放置
AGameplayAbilityTargetActor_GroundTrace 地面追踪(鼠标指地) 地面 AOE

工作流程

GameplayAbility 激活
  ↓
WaitTargetData AbilityTask 启动
  ↓
生成 TargetActor(可选:可视化组件:贴花/静态网格/准星)
  ↓
TargetActor 在 Tick 中执行射线检测 / 碰撞重叠
  ↓
玩家确认目标(或自动确认)
  ↓
TargetData 就绪,广播给 GameplayAbility
  ↓
GA 使用 TargetData 应用 GE
  ↓
TargetActor 销毁

可视化组件

TargetActor 是 AActor,可挂任意可视化组件:

AMyTargetActor::AMyTargetActor()
{
    // AOE 地面贴花指示器
    DecalComponent = CreateDefaultSubobject<UDecalComponent>(TEXT("AOEDecal"));
    DecalComponent->SetRelativeRotation(FRotator(-90.f, 0.f, 0.f));
    DecalComponent->DecalSize = FVector(500.f, 500.f, 500.f);
    RootComponent = DecalComponent;
    PrimaryActorTick.bCanEverTick = true;
}

目标捕获方式

捕获方式 说明 适用场景
射线检测(Line Trace) 起点到终点的直线检测 射击武器、指向性技能
碰撞重叠(Overlap) 用碰撞体检测重叠 AOE 技能、范围效果
球形检测(Sphere Trace) 带半径的射线检测 需要容错的射击

9.4 四种确认类型

EGameplayTargetingConfirmation::Type 决定目标何时被确认:

确认类型 行为 适用场景
Instant 立即生成 TargetData 并销毁,不调 Tick 即时命中武器
UserConfirmed 等待玩家按确认键(绑定 Confirm 输入) AOE 目标选择
Custom 由 GA 通过 ConfirmTaskByInstanceName() 确认 自定义确认逻辑
CustomMulti 同 Custom,但确认后不结束 Task 多次选择目标(连锁技能)

重要:不是每种确认类型都被所有 TargetActor 支持。例如 AGameplayAbilityTargetActor_GroundTrace 不支持 Instant 确认。

标准用法:WaitTargetData Task

void UGA_Fireball::ActivateAbility(...)
{
    UAbilityTask_WaitTargetData* TargetTask =
        UAbilityTask_WaitTargetData::CreateWaitTargetData(
            this, NAME_None,
            EGameplayTargetingConfirmation::UserConfirmed,
            TargetActorClass);
    TargetTask->ValidData.AddDynamic(this, &UGA_Fireball::OnTargetDataReady);
    TargetTask->Cancelled.AddDynamic(this, &UGA_Fireball::OnTargetDataCancelled);
    TargetTask->ReadyForActivation();
}

void UGA_Fireball::OnTargetDataReady(const FGameplayAbilityTargetDataHandle& Data)
{
    if (Data.Num() > 0)
    {
        const FGameplayAbilityTargetData* First = Data.Get(0);
        AActor* Target = First->GetHitResult()->GetActor();
        // 服务端权威:生成投射物 / 施加 GE
    }
}

服务器端目标生产(ShouldProduceTargetDataOnServer)

配置 行为 优缺点
false(默认) 客户端生成 TargetData → RPC 发到服务器 更响应,但需服务器验证防作弊
true 客户端只发送确认事件,服务器自行生成 更安全、无作弊风险,但客户端可能有预测偏差
// TargetActor 构造函数中设置
ShouldProduceTargetDataOnServer = true;

9.5 目标过滤器(TargetData Filter)

基础过滤

// Blueprint 节点:Make GameplayTargetDataFilter + Make Filter Handle
FGameplayTargetDataFilter Filter;
Filter.ObjectFilter = SelfActor;                              // 排除的对象
Filter.FilterType = ETargetDataFilterSelf::Type::TDFS_NoSelf; // 排除自己
FGameplayTargetDataFilterHandle FilterHandle = MakeFilterHandle(Filter, SelfActor);

子类化过滤器

USTRUCT(BlueprintType)
struct FMyNameTargetDataFilter : public FGameplayTargetDataFilter
{
    GENERATED_BODY()
    virtual bool FilterPassesForActor(const AActor* ActorToBeFiltered) const override;
};

bool FMyNameTargetDataFilter::FilterPassesForActor(const AActor* ActorToBeFiltered) const
{
    if (!Super::FilterPassesForActor(ActorToBeFiltered)) return false;
    const AMyCharacter* MyChar = Cast<AMyCharacter>(ActorToBeFiltered);
    if (!MyChar) return false;
    return MyChar->GetTeam() != OwningTeam;   // 只接受敌方
}

配套 Make Filter Handle 蓝图函数:

FGameplayTargetDataFilterHandle UMyBlueprintLibrary::MakeMyFilterHandle(
    FMyNameTargetDataFilter Filter, AActor* FilterActor)
{
    FGameplayTargetDataFilter* NewFilter = new FMyNameTargetDataFilter(Filter);
    NewFilter->InitializeFilterContext(FilterActor);
    FGameplayTargetDataFilterHandle FilterHandle;
    FilterHandle.Filter = TSharedPtr<FGameplayTargetDataFilter>(NewFilter);
    return FilterHandle;
}

9.6 World Reticle(瞄准准星/指示器)

AGameplayAbilityWorldReticle 用于可视化"你在瞄准谁",由 TargetActor 生成和销毁。

实现(GASShooter 用 WidgetComponent)

UCLASS()
class AMyReticle : public AGameplayAbilityWorldReticle
{
    GENERATED_BODY()
public:
    AMyReticle()
    {
        ReticleWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("ReticleWidget"));
        ReticleWidget->SetWidgetSpace(EWidgetSpace::Screen);   // 屏幕空间 UI
        RootComponent = ReticleWidget;
    }
    // Blueprint 事件供设计师用
    UFUNCTION(BlueprintImplementableEvent) void OnValidTargetChanged(bool bNewValue);
    UFUNCTION(BlueprintImplementableEvent) void OnTargetingAnActor(bool bNewValue);
    UFUNCTION(BlueprintImplementableEvent) void OnParametersInitialized();
};

关键 Blueprint 事件

事件 触发时机
OnValidTargetChanged 目标有效性变化时
OnTargetingAnActor 是否瞄准到 Actor 时
OnParametersInitialized Reticle 参数初始化完成
SetReticleMaterialParamFloat/Vector 设置材质参数

目标持久化(锁定系统)

默认 TargetActor 目标只在射线/碰撞范围内有效。要"记住最后一个有效目标"(追踪锁定),自定义实现:

void AMyTargetActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    FHitResult HitResult;
    if (PerformTrace(HitResult))
    {
        LastValidTarget = HitResult.GetActor();   // 找到新目标更新
        LastValidHitResult = HitResult;
    }
    // 没找到则保持 LastValidTarget(目标持久化)
    if (Reticle && LastValidTarget)
        Reticle->SetActorLocation(LastValidTarget->GetActorLocation());
}

Reticle 默认只在目标有效时显示;持久化需要自定义 TargetActor 管理 Reticle 生命周期。

9.7 GE Container(Action RPG 便捷封装)

Epic 的 Action RPG 提供 FGameplayEffectContainer——把 TargetData 生产 + GE 应用封装为数据驱动结构,用 CDO 运行、不生成 Actor:

// 基类:目标选择器
UCLASS(Blueprintable, EditInlineNew)
class URPGTargetType : public UObject
{
    GENERATED_BODY()
public:
    virtual void GetTargets_Implementation(
        AActor* TargetingActor, FGameplayEventData EventData,
        TArray<FGameplayAbilityTargetDataHandle>& OutTargetData) const;
};

// 子类:事件目标
UCLASS()
class URPGTargetType_EventTarget : public URPGTargetType
{
    GENERATED_BODY()
public:
    virtual void GetTargets_Implementation(
        AActor* TargetingActor, FGameplayEventData EventData,
        TArray<FGameplayAbilityTargetDataHandle>& OutTargetData) const override
    {
        if (EventData.Target)
        {
            FGameplayAbilityTargetDataHandle Handle;
            Handle.Add(new FGameplayAbilityTargetData_SingleActor(
                const_cast<AActor*>(EventData.Target)));
            OutTargetData.Add(Handle);
        }
    }
};

// 子类:AOE 球形检测
UCLASS()
class URPGTargetType_AOE : public URPGTargetType
{
    GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere) float Radius = 500.f;
    virtual void GetTargets_Implementation(
        AActor* TargetingActor, FGameplayEventData EventData,
        TArray<FGameplayAbilityTargetDataHandle>& OutTargetData) const override
    {
        TArray<FOverlapResult> Overlaps;
        FCollisionQueryParams Params;
        Params.AddIgnoredActor(TargetingActor);
        TargetingActor->GetWorld()->OverlapMultiByChannel(
            Overlaps, TargetingActor->GetActorLocation(), FQuat::Identity,
            ECC_Pawn, FCollisionShape::MakeSphere(Radius), Params);
        FGameplayAbilityTargetDataHandle Handle;
        for (const FOverlapResult& Overlap : Overlaps)
            if (Overlap.GetActor())
                Handle.Add(new FGameplayAbilityTargetData_SingleActor(Overlap.GetActor()));
        OutTargetData.Add(Handle);
    }
};

GE Container 优点:CDO 运行不生成 Actor(性能优)、即时执行无需确认、数据驱动配置。适合瞬发自我治疗、AOE 范围伤害。

9.8 四种经典目标系统场景

场景一:射击游戏即时命中

射击输入 → WaitTargetData(Instant) → 单线射线 → 命中 → 应用伤害 GE
  • SingleLineTrace + Instant 确认 + 排除自己 + 不需要 Reticle。

场景二:AOE 地面目标选择

技能激活 → WaitTargetData(UserConfirmed) → 地面检测 → 贴花显示范围
  → 鼠标调整 → 点击确认 → TargetData(位置+范围) → AOE 伤害 GE
  • GroundTrace + UserConfirmed + 地面贴花 + Reticle。

场景三:锁定目标系统

锁定输入 → 射线找到目标 → 锁定 → Reticle 持续显示 → 攻击指向锁定目标
  → 解锁输入 或 目标死亡 → 解除锁定
  • 自定义 TargetActor 目标持久化 + WidgetComponent 锁定标记。

场景四:追踪弹

发射 → 锁定目标 → 生成追踪弹 → 追踪弹飞向目标 → 碰撞 → 伤害 GE
  • 自定义 TargetActor 保持锁定,目标信息存入 EffectContext 传给投射物。

9.9 目标有效性过滤与二次校验

在施加 GE 前做二次校验(防御"目标已死亡/免疫"):

// 方式一:GE 配置 Target Ignore Tags = State.Dead, State.Immune
// 方式二:代码判断
if (TargetASC->HasMatchingGameplayTag(StateDeadTag)) { /* 不施加 */ }

9.10 性能注意点

  • 非 Instant 的 TargetActor 每个 Tick 执行射线检测 + Debug 绘制;
  • 通常只有一个 TargetActor 运行,性能影响小;
  • 复杂 TargetActor 可降低 Tick 频率(SetActorTickInterval)优化。

9.11 本章验证

  • [ ] 用 WaitTargetData + SingleLineTrace 实现"指向目标火球";
  • [ ] 切换 UserConfirmed,体验"选中 → 点击释放"流程;
  • [ ] 自定义一个扇形 TargetActor(或子类化 TargetData 携带命中部位),验证扇形内多目标命中;
  • [ ] 写一个自定义过滤器只接受敌方,配合 WaitTargetData 使用;
  • [ ] 给 AOE 技能加 Reticle 准星(OnValidTargetChanged/OnTargetingAnActor);
  • [ ] 用 GE Container 实现瞬发自我治疗 + AOE 范围伤害;
  • [ ] 联机验证:客户端选目标 → 服务端结算,模拟代理不越权。

下一章:10 Execution 与 MMC 高级计算