UE TargetingSystem插件介绍

张开发
2026/4/21 1:25:40 15 分钟阅读
UE TargetingSystem插件介绍
最近看代码的时候偶然看到了UE添加了一个新的插件TargetingSystem大致浏览了代码后我认为这可以比较方便的作为一个目标选择框架的底层代码。一、引言 — 为什么需要 TargetingSystem在游戏开发中在空间中找到一组目标是一个极其高频的需求比如动作游戏角色攻击的打击判定。如果基于GAS开发的话很多项目一开始都会使用TargetActor用于碰撞检测/判定。但是TargetActor作为Actor它的创建销毁的开销比较大随着项目的进行往往都会优化掉TargetActor然后基于引擎的碰撞检测逻辑如BoxTraceMulti去实现打击判定的碰撞检测逻辑。其它系统也有类似的在空间中找到一组目标需求如AI 需要选取最优攻击目标、拾取系统需要找到附近的物品、自动锁定需要筛选视野内的敌人……传统做法是在每个系统中各自编写碰撞查询逻辑然后加上各自的过滤条件导致大量重复代码且难以统一配置和调试。最近我翻代码的时候发现UE5.2引入了一个新插件TargetingSystem翻看代码过后我认为它对在空间中找到一组目标这一目的可以作为一个通用的数据驱动的目标查询框架。它的核心思想是将寻找目标这一行为抽象为一条可配置的任务管线Task Pipeline由 Selection选取→ Filter过滤→ Sort排序 三类任务组成通过数据资产DataAsset驱动无需编写硬编码的碰撞查询逻辑。同时TargetingSystem还支持了异步的查询对于性能优化也很有利。二、整体架构概览TargetingSystem 的设计大致如下通过 UTargetingPreset 数据资产作为一个独立的目标检测规则如角色的攻击判定。配置检测流程TargetingTasksTargetingTask为具体的目标选择逻辑比如OverlapTrace等它大致可以分为选择过滤排序三种任务。可以自己实现对应的逻辑适配项目。比如玩家释放一个冲刺技能目标是攻击距离最近的敌人。那对应的TargetingTask如下SelectionTask选择一定范围内的actorsFilterTask选择敌人的怪物作为目标过滤掉其余的actorsSortTask对目标候选进行排序距离越近越优先。目标检测的行为通过创建TargetingHandle在全局的DataStore中进行维护不涉及Spawn任何的Actor创建的TargetingHandle由TargetingSubsystem进行管理负责调度执行具体的目标选择逻辑。支持同步和异步两种执行模式。目标选择逻辑执行完后通过Delegate回调给发起者执行后续的逻辑。三、如何应用有了TargetSystem的架构设计以后我这里打算找一个具体的应用场景作为使用的示例。我这里打算使用Lyra将它的近战攻击GA_Melee改为使用TargetingSystem执行首先打开对应的蓝图文件可以看到近战攻击的流程如下播放攻击动画碰撞检测找到目标对目标施加效果TargetingSystem可以替换的是上述的第二步接下来分析一下近战攻击寻找目标的流程通过胶囊体碰撞检测获取角色正前方的目标通过队伍系统检测是否为敌人过滤掉墙后的敌人按照TargetingSystem的架构设计我们可以通过TargetingTask来复刻上述的功能。TargetingSelectionTask_Melee使用胶囊体碰撞检测角色身前的目标直接使用CapsuleTraceMultiForObjects接口即可TargetingFilterTask_Team比较碰撞目标和角色的队伍需要为敌对的TargetingFilterTask_Block目标和角色之间不存在墙体遮挡具体的逻辑很简单就不写了相关代码我贴到最后面。有了Task以后然后需要配置具体的TargetingPreset。创建TargetingPreset的DataAsset然后配置上对应的Task。接下来打开GA_Melee使用AbilityTask-PerformTargetingRequest去替代原有的逻辑InTargetingRequest配置前面创建的DA_TP_Melee。逻辑完成运行游戏按C键普攻测试即可。四、测试代码UTargetingSelectionTask_Meleevoid 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; TArrayFHitResult 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 TArrayFHitResult 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_Teambool 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::GetSubsystemULyraTeamSubsystem(SourceContext-SourceActor-GetWorld())) { return TeamSubsystem-CompareTeams(TargetActor, SourceContext-SourceActor) ! TeamComparison; } } } } return true; }UTargetingFilterTask_Blockbool 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; TArrayAActor* 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; }

更多文章