UE TargetingSystem插件介绍
2026/4/21 1:25:35 网站建设 项目流程

最近看代码的时候,偶然看到了UE添加了一个新的插件TargetingSystem,大致浏览了代码后,我认为这可以比较方便的作为一个目标选择框架的底层代码。

一、引言 — 为什么需要 TargetingSystem

在游戏开发中,"在空间中找到一组目标"是一个极其高频的需求,比如动作游戏角色攻击的打击判定。如果基于GAS开发的话,很多项目一开始都会使用TargetActor用于碰撞检测/判定。但是TargetActor作为Actor,它的创建销毁的开销比较大,随着项目的进行,往往都会优化掉TargetActor,然后基于引擎的碰撞检测逻辑如BoxTraceMulti去实现打击判定的碰撞检测逻辑。

其它系统,也有类似的"在空间中找到一组目标"需求,如:AI 需要选取最优攻击目标、拾取系统需要找到附近的物品、自动锁定需要筛选视野内的敌人……

传统做法是在每个系统中各自编写碰撞查询逻辑,然后加上各自的过滤条件,导致大量重复代码,且难以统一配置和调试。最近我翻代码的时候,发现UE5.2引入了一个新插件TargetingSystem,翻看代码过后,我认为它对"在空间中找到一组目标"这一目的,可以作为一个通用的数据驱动的目标查询框架。

它的核心思想是:将"寻找目标"这一行为抽象为一条可配置的任务管线(Task Pipeline),由 Selection(选取)→ Filter(过滤)→ Sort(排序) 三类任务组成,通过数据资产(DataAsset)驱动,无需编写硬编码的碰撞查询逻辑。

同时TargetingSystem还支持了异步的查询,对于性能优化也很有利。

二、整体架构概览

TargetingSystem 的设计大致如下:

  • 通过 UTargetingPreset 数据资产作为一个独立的目标检测规则,如角色的攻击判定。配置检测流程TargetingTasks

  • TargetingTask为具体的目标选择逻辑,比如Overlap,Trace等,它大致可以分为选择,过滤,排序三种任务。可以自己实现对应的逻辑适配项目。比如玩家释放一个冲刺技能,目标是攻击距离最近的敌人。那对应的TargetingTask如下:

    1. SelectionTask选择一定范围内的actors

    2. FilterTask选择敌人的怪物作为目标,过滤掉其余的actors

    3. SortTask对目标候选进行排序,距离越近越优先。

  • 目标检测的行为通过创建TargetingHandle在全局的DataStore中进行维护,不涉及Spawn任何的Actor

  • 创建的TargetingHandle由TargetingSubsystem进行管理,负责调度执行具体的目标选择逻辑。支持同步和异步两种执行模式。

  • 目标选择逻辑执行完后,通过Delegate回调给发起者,执行后续的逻辑。

三、如何应用

有了TargetSystem的架构设计以后,我这里打算找一个具体的应用场景,作为使用的示例。我这里打算使用Lyra,将它的近战攻击GA_Melee改为使用TargetingSystem执行

首先打开对应的蓝图文件,可以看到近战攻击的流程如下:

  1. 播放攻击动画

  2. 碰撞检测找到目标

  3. 对目标施加效果

TargetingSystem可以替换的是上述的第二步,接下来分析一下近战攻击寻找目标的流程:

  • 通过胶囊体碰撞检测,获取角色正前方的目标

  • 通过队伍系统,检测是否为敌人

  • 过滤掉墙后的敌人

按照TargetingSystem的架构设计,我们可以通过TargetingTask来复刻上述的功能。

  1. TargetingSelectionTask_Melee,使用胶囊体碰撞检测角色身前的目标,直接使用CapsuleTraceMultiForObjects接口即可

  2. TargetingFilterTask_Team,比较碰撞目标和角色的队伍,需要为敌对的

  3. TargetingFilterTask_Block,目标和角色之间,不存在墙体遮挡

具体的逻辑很简单,就不写了,相关代码我贴到最后面。

有了Task以后,然后需要配置具体的TargetingPreset。创建TargetingPreset的DataAsset,然后配置上对应的Task。

接下来打开GA_Melee,使用AbilityTask-PerformTargetingRequest去替代原有的逻辑,InTargetingRequest配置前面创建的DA_TP_Melee。

逻辑完成,运行游戏按C键普攻测试即可。

四、测试代码

UTargetingSelectionTask_Melee

void UTargetingSelectionTask_Melee::Execute(const FTargetingRequestHandle& TargetingHandle) const { FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle); if (!SourceContext) return; AActor* SourceActor = SourceContext->SourceActor; if (!SourceActor) return; UWorld* World = GetSourceContextWorld(TargetingHandle); if (!World) return; FVector Direction = SourceActor->GetActorForwardVector(); FVector Start = SourceActor->GetActorLocation(); FVector End = Start + Direction * TraceLength; TArray<FHitResult> OutHits; UKismetSystemLibrary::CapsuleTraceMultiForObjects(World, Start, End, Radius, HalfHeight, {ObjectType}, false, {SourceActor}, EDrawDebugTrace::None, OutHits, true); ProcessHitResults(TargetingHandle, OutHits); SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed); } void UTargetingSelectionTask_Melee::ProcessHitResults(const FTargetingRequestHandle& TargetingHandle, const TArray<FHitResult>& Hits) const { if (TargetingHandle.IsValid() && Hits.Num() > 0) { FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(TargetingHandle); for (const FHitResult& HitResult : Hits) { if (!HitResult.GetActor()) { continue; } bool bAddResult = true; for (const FTargetingDefaultResultData& ResultData : TargetingResults.TargetResults) { if (ResultData.HitResult.GetActor() == HitResult.GetActor()) { bAddResult = false; break; } } if (bAddResult) { FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData(); ResultData->HitResult = HitResult; } } } }

UTargetingFilterTask_Team

bool UTargetingFilterTask_Team::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const { if (AActor* TargetActor = TargetData.HitResult.GetActor()) { if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle)) { if (SourceContext->SourceActor) { if (ULyraTeamSubsystem* TeamSubsystem = UWorld::GetSubsystem<ULyraTeamSubsystem>(SourceContext->SourceActor->GetWorld())) { return TeamSubsystem->CompareTeams(TargetActor, SourceContext->SourceActor) != TeamComparison; } } } } return true; }

UTargetingFilterTask_Block

bool UTargetingFilterTask_Block::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const { if (AActor* TargetActor = TargetData.HitResult.GetActor()) { if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle)) { if (SourceContext->SourceActor) { FHitResult OutHitResult; TArray<AActor*> ActorsToIgnore = {SourceContext->SourceActor, TargetActor}; return UKismetSystemLibrary::LineTraceSingle(TargetActor, SourceContext->SourceActor->GetActorLocation(), TargetData.HitResult.ImpactPoint, UEngineTypes::ConvertToTraceType(TraceChannel), false, ActorsToIgnore, EDrawDebugTrace::None, OutHitResult, true); } } } return false; }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询