概要
UnrealEngine5.2 から追加された GameplayTargetingSystem についてのメモ。
更新履歴
日付 | 内容 |
---|---|
2023/09/30 | 初版 |
2023/11/21 | C++リクエストコードのハンドル解放処理の修正 |
参考
以下の記事を参考にいたしました、ありがとうございます。
UE公式:ゲームプレイ ターゲット システム
UE公式:Gameplay Targeting System のデバッグ
ゲームプレイ ターゲット システムのリファレンス
Gameplay Targeting System Plugin について
環境
Windows10
Visual Studio 2022
UnrealEngine 5.2.1
関連ソース
"Engine\Plugins\Experimental\GameplayTargetingSystem\Source\GameplayTargetingSystem\TargetingSystem\TargetingSubsystem.h"
"Engine\Plugins\Experimental\GameplayTargetingSystem\Source\GameplayTargetingSystem\Types\TargetingSystemTypes.h"
TargetingSystem とは
任意の位置からターゲットを探す仕組みを提供するプラグインです。データドリブンなシステムで、同期型と非同期型の両方が用意されているようです。
導入準備
[プラグイン]からTargeting System
を有効にすることで利用可能になります(UEエディタ再起動が入ります)。
また、C++上で扱うには プロジェクト名.Build.cs
にモジュール名 TargetingSystem
を追加する必要があります。
using UnrealBuildTool;
public class MyProject : ModuleRules
{
public MyProject(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore"
});
PrivateDependencyModuleNames.AddRange(new string[] {
"EnhancedInput",
"GameplayAbilities",
"GameplayTags",
"GameplayTasks",
"TargetingSystem" // ←追加
});
// ..省略..
ターゲットリクエスト
ターゲットリクエストを行うためには入力データとして TargetingPreset
とTargetingSorceContext
が必要です。
また出力結果を受け取るには FTargetingRequestHandle
を使って TargetingSubsystem
経由で受け取ります。
TargetingPreset
ターゲッティングシステムが行うタスクの定義の設定です。タスクは複数設定可能で設定した順序に実行されます。
Targeting Preset
を継承したデータアセットです。以下3タスクを設定したターゲットプリセットアセット例。
TargetingSourceContext
ターゲッティングシステムの処理でのソース情報(開始位置など)を定義します。
前述のTargeting Preset
で設定したタスクがこの情報を使用します。
Source Location
がターゲッティング開始位置となりますが、Source Actor
を設定している場合はそちらの情報を優先して使うようです。(各Taskの作りによる?)
BPでの即時リクエスト
ExecuteTargetingRequest
ノードに対し、TargetingPreset
とTargetingSourceContext
を入れて受け取り用のイベントを設定します。
ターゲット処理が完了したらイベントが実行されるので、TargetingRequestHandle
を引数に TargetingSubsystem
から結果を受け取ります(GetTargetingResult
もしくは GetTargetingResultActors
)。
終わったら、ハンドルを RemoveAsyncTargetingRequestWithHandle
で始末します。このハンドルを始末する処理を行わないとターゲットリクエストの結果はいつまでも残り続けるようです。
RemoveAsyncTargetingRequestWithHandle
は入力にハンドルを指定できないため正しく動作していません(UE5.3でも同様の模様)。ターゲッティングリクエストハンドルを削除するには現状C++コードを使うしかないようです。
BPでの非同期リクエスト
Start Async Targeting Request
を使用するとできるようです。
ただ TargetingSelectionTask_AOE
などの一部タスクは非同期での処理はされないようです。
C++での即時リクエスト
C++で行う場合は以下のように。
UTargetingPreset
は継承したBPに持っておくようになっていますが、 ConstructorHelpers
を使って取得するようにすることも可能だと思います。
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
class UTargetingPreset;
UCLASS()
class MYPROJECT_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
// ..省略..
public:
// ターゲッティングリクエスト
UFUNCTION(Category = "Targeting", BlueprintCallable)
void RequestTargeting();
// ターゲットティング結果受け取り
UFUNCTION(Category = "Targeting", BlueprintCallable)
void ResultTargeting(FTargetingRequestHandle TargetingHandle);
// ターゲッティングプリセット
UPROPERTY(EditAnywhere, Category="Targeting")
TObjectPtr<UTargetingPreset> TargetingPreset;
};
#include "GameplayTargetingSystem/TargetingSystem/TargetingSubsystem.h"
#include "GameplayTargetingSystem/Types/TargetingSystemTypes.h"
// リクエスト
void AMyCharacter::RequestTargeting()
{
auto _TargetingSubsystem = UTargetingSubsystem::GetTargetingSubsystem(this->GetWorld());
FTargetingSourceContext _Context;
_Context.SourceActor = this;
_Context.InstigatorActor = this;
_Context.SourceLocation = this->GetActorLocation();
_Context.SourceSocketName = NAME_None;
_Context.SourceObject = this;
UTargetingPreset* _Preset = TargetingPreset;
FTargetingRequestDelegate _CompletionDelegate;
_CompletionDelegate.BindUObject(this, &ThisClass::ResultTargeting);
// ターゲッティングハンドル作成
auto _TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(_Preset, _Context);
// 即時ターゲッティングリクエスト実行
_TargetingSubsystem->ExecuteTargetingRequestWithHandle(_TargetingHandle, _CompletionDelegate);
}
// 結果受け取り
void AMyCharacter::ResultTargeting(FTargetingRequestHandle _TargetingHandle)
{
auto _TargetingSubsystem = UTargetingSubsystem::GetTargetingSubsystem(this->GetWorld());
TArray<FHitResult> _OutTargets;
// サブシステムから結果受け取り
_TargetingSubsystem->GetTargetingResults(_TargetingHandle, _OutTargets);
// アクター名を表示
for(const auto& _It : _OutTargets){
UE_LOG(LogTemp, Log, TEXT("%s"), *_It.GetActor()->GetFName().ToString());
}
#if 0
// ターゲットハンドル解放(このタイミングではスレッド競合する可能性があるため1フレーム遅らせて実行するべき)
//UTargetingSubsystem::ReleaseTargetRequestHandle(_TargetingHandle);
#else
// ターゲットハンドル解放
_TargetingSubsystem->RemoveAsyncTargetingRequestWithHandle(_TargetingHandle);
#endif
}
上記コードでターゲットハンドル解放に ReleaseTargetRequestHandle
を使っていますが、このタイミングでの解放はスレッド競合を起こす可能性があるため、1フレーム遅らせて実行するか、非同期解放の RemoveAsyncTargetingRequestWithHandle
を使うべきです。
C++での非同期リクエスト
ExecuteTargetingRequestWithHandle
を StartAsyncTargetingRequestWithHandle
に変えることで非同期リクエストになります。結果受け取りは即時リクエストと同じです。
#include "GameplayTargetingSystem/TargetingSystem/TargetingSubsystem.h"
#include "GameplayTargetingSystem/Types/TargetingSystemTypes.h"
// リクエスト
void AMyCharacter::RequestTargeting()
{
auto _TargetingSubsystem = UTargetingSubsystem::GetTargetingSubsystem(this->GetWorld());
FTargetingSourceContext _Context;
_Context.SourceActor = this;
_Context.InstigatorActor = this;
_Context.SourceLocation = this->GetActorLocation();
_Context.SourceSocketName = NAME_None;
_Context.SourceObject = this;
UTargetingPreset* _Preset = TargetingPreset;
FTargetingRequestDelegate _CompletionDelegate;
_CompletionDelegate.BindUObject(this, &ThisClass::ResultTargeting);
// ターゲッティングハンドル作成
auto _TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(_Preset, _Context);
// 非同期ターゲッティングリクエスト実行
_TargetingSubsystem->StartAsyncTargetingRequestWithHandle(_TargetingHandle, _CompletionDelegate);
}
ソート結果の受け取り
ソートタスクを使った場合、その結果の受け取りはFTargetingTaskSet
から取得します。以下サンプルコード。
// リクエストハンドルからソート結果を取得
auto _TDResultsSet = FTargetingDefaultResultsSet::Find(_TargetingHandle);
for(const auto& _It : _TDResultsSet->TargetResults){
// ソートに使ったスコアを取得
float _Score = _It.Score;
UE_LOG(LogTemp, Log, TEXT("%s [Score = %.2f]"), *_It.HitResult.GetActor()->GetFName().ToString(), _Score);
}
Score
はソート用に計算された値です。UE5.2ではTargetingFilterTask_SortByDistance
のソート処理にバグがあり正しく動作しません。UE5.3では修正されています。
タスクの作成
選択、フィルタ、ソートの各タスクはそれぞれ
TargetingTask
、TargetingFilterTask_BasicFilterTemplate
、TargetingSortTask_Base
を継承して自作タスクを作成できます。
フィルタタスク
フィルタタスクの作成例です。ソースアクター前方ベクトルとの内積をフィルタ条件にしています。
#pragma once
#include "GameplayTargetingSystem/Tasks/TargetingFilterTask_BasicFilterTemplate.h"
#include "GameplayTargetingSystem/Types/TargetingSystemTypes.h"
#include "TargetingFilterTask_Test.generated.h"
UCLASS(Blueprintable)
class UTargetingFilterTask_Test : public UTargetingFilterTask_BasicFilterTemplate
{
GENERATED_BODY()
public:
UTargetingFilterTask_Test(const FObjectInitializer& ObjectInitializer);
// ターゲティングリクエストを処理するために派生クラスによって呼び出される評価関数
virtual bool ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const override;
protected:
// 有効な内積(ValidDotProduct ~ 1,0f)
UPROPERTY(EditAnywhere, Category = "Targeting Filter | Data", Meta = (AllowAbstract=true))
float ValidDotProduct = 0.8f;
};
#include "TargetingFilterTask_Test.h"
#include "GameFramework/Actor.h"
UTargetingFilterTask_Test::UTargetingFilterTask_Test(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
// フィルター評価関数
bool UTargetingFilterTask_Test::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
{
AActor* _SourceActor = nullptr;
if (FTargetingSourceContext* _SourceContext = FTargetingSourceContext::Find(TargetingHandle)){
_SourceActor = _SourceContext->SourceActor;
}
AActor* _TargetActor = TargetData.HitResult.GetActor();
if(_SourceActor && _TargetActor){
FVector _SourceLoc = _SourceActor->GetActorLocation();
FVector _TargetLoc = _TargetActor->GetActorLocation();
FVector _Fwd = _SourceActor->GetActorForwardVector();
FVector _Tar = (_TargetLoc - _SourceLoc).GetSafeNormal();
// 内積計算
float _Dot = FVector::DotProduct(_Fwd, _Tar);
if( _Dot < ValidDotProduct){
// 内積が有効値以下
return(true);
}else{
return(false);
}
}
return(true);
}
GameplayAbilityでの使用
ゲームプレイアビリティでターゲッティングを行う PerformTargetingRequest
と、フィルタリングを行う PerformFilteringRequest
が用意されています。
PerformTargetingRequest
Target Preset
を入力し使用します。TargetingSourceContext
はタスク内部で作成され、SourceActor
にアビリティシステムコンポーネントのAvatarActor
が設定されるようです(他はデフォルト)。
PerformFilteringRequest
フィルタリング処理を行います。InTargets
に予めフィルタリングを行うアクターのリストを入れます。
まとめ
データ定義でターゲットを検索できるため用途によってタスクの差し替えができ、柔軟に対応ができそうです。ただ用意されているタスクだけではやや不十分な感じがするので自作タスクを用意する必要が結構ありそうです。