LoginSignup
1
1

UE5 GameplayTargetingSystem についてのメモ

Last updated at Posted at 2023-09-30

概要

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 を追加する必要があります。

Build.cs
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"     // ←追加
		});

  // ..省略..

ターゲットリクエスト

ターゲットリクエストを行うためには入力データとして TargetingPresetTargetingSorceContext が必要です。
また出力結果を受け取るには FTargetingRequestHandle を使って TargetingSubsystem経由で受け取ります。

TargetingPreset

ターゲッティングシステムが行うタスクの定義の設定です。タスクは複数設定可能で設定した順序に実行されます。
Targeting Presetを継承したデータアセットです。以下3タスクを設定したターゲットプリセットアセット例。

Preset.png

TargetingSourceContext

ターゲッティングシステムの処理でのソース情報(開始位置など)を定義します。
前述のTargeting Presetで設定したタスクがこの情報を使用します。
Source Location がターゲッティング開始位置となりますが、Source Actorを設定している場合はそちらの情報を優先して使うようです。(各Taskの作りによる?)

Context.png

BPでの即時リクエスト

ExecuteTargetingRequestノードに対し、TargetingPresetTargetingSourceContext を入れて受け取り用のイベントを設定します。
ターゲット処理が完了したらイベントが実行されるので、TargetingRequestHandle を引数に TargetingSubsystemから結果を受け取ります(GetTargetingResult もしくは GetTargetingResultActors)。
終わったら、ハンドルを RemoveAsyncTargetingRequestWithHandle で始末します。このハンドルを始末する処理を行わないとターゲットリクエストの結果はいつまでも残り続けるようです。

ExecuteTargetingRequest.png

RemoveAsyncTargetingRequestWithHandle は入力にハンドルを指定できないため正しく動作していません(UE5.3でも同様の模様)。ターゲッティングリクエストハンドルを削除するには現状C++コードを使うしかないようです。

BPでの非同期リクエスト

Start Async Targeting Request を使用するとできるようです。
ただ TargetingSelectionTask_AOEなどの一部タスクは非同期での処理はされないようです。

ASync.png

C++での即時リクエスト

C++で行う場合は以下のように。
UTargetingPresetは継承したBPに持っておくようになっていますが、 ConstructorHelpersを使って取得するようにすることも可能だと思います。

.h
#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;

};
.cpp
#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++での非同期リクエスト

ExecuteTargetingRequestWithHandleStartAsyncTargetingRequestWithHandle に変えることで非同期リクエストになります。結果受け取りは即時リクエストと同じです。

.cpp
#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から取得します。以下サンプルコード。

.cpp
	// リクエストハンドルからソート結果を取得
	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では修正されています。

タスクの作成

選択、フィルタ、ソートの各タスクはそれぞれ
TargetingTaskTargetingFilterTask_BasicFilterTemplateTargetingSortTask_Base を継承して自作タスクを作成できます。

フィルタタスク

フィルタタスクの作成例です。ソースアクター前方ベクトルとの内積をフィルタ条件にしています。

TargetingFilerTask_Test.h
#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;

};
TargetingFilerTask_Test.cpp
#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が設定されるようです(他はデフォルト)。

PerformTargetingRequest.png

PerformFilteringRequest

フィルタリング処理を行います。InTargets に予めフィルタリングを行うアクターのリストを入れます。

PerformFilteringRequest.png

まとめ

データ定義でターゲットを検索できるため用途によってタスクの差し替えができ、柔軟に対応ができそうです。ただ用意されているタスクだけではやや不十分な感じがするので自作タスクを用意する必要が結構ありそうです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1