5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

UE5 アニメーションのマルチスレッド処理についてのメモ

Posted at

概要

UnrealEngine5のアニメーションでのマルチスレッド処理についてのメモ書きです。
BPと(PropertyAccess)と、C++の両方を試してみます。

更新履歴

日付 内容
2023/04/04 初版

参考

以下の記事を参考にいたしました、ありがとうございます。
UE公式:アニメーションの最適化
UE公式:UAnimInstance
猫でも分かる UE5.0, 5.1 におけるアニメーションの新機能について
UE5 Property Accessを使ってAnimBPをマルチスレッドに更新しよう
スレッドセーフという幻想と現実

関連する過去記事

UE4 アニメ―ションBPをC++クラス継承にしてアクセスする
UE4 非同期処理についてのメモ

環境

Windows10
Visual Studio 2022
UnrealEngine 5.1.1

関連ソース

"\Engine\Source\Runtime\Engine\Classes\Animation\AnimInstance.h"
"\Engine\Source\Runtime\Engine\Private\Animation\AnimInstance.cpp"
"\Engine\Source\Runtime\Engine\Public\Animation\AnimInstanceProxy.h"

ブループリントでの実装方法

UE5からアニメーションBPでマルチスレッド処理ができるようになったようです。

BlueprintThreadSafeUpdateAnimation を使う

この関数はワーカースレッドで実行されアニムグラフの更新直前に実行されるようです。

BpThreadSafeUpdateAnimation.png

ただしこの関数でスレッドセーフ以外の関数ノードを使用すると以下のようにエラーになります。
TryGetOwner や GetMovementComponent がスレッドセーフではないと警告がでています。

BP1.png

この場合、右クリック[変数]の[プロパティアクセス]から
Menu.png

対象の関数を選び
ProertyAccess.png

差し替えることでエラーがでなくなります。
BP2.png

UE5にて追加された PropertyAccess という機能にて外部オブジェクトをローカル変数にコピーしてくれていることによりスレッドセーフを実現しているようです。
また自作関数の場合は、[詳細設定]より[スレッドセーフ]にチェックをいれることで BlueprintThreadSafeUpdateAnimation での使用が可能となりますが、実際にスレッドセーフであるかの責任は作成者が負う事になります。
ThreadSafeOption.png

C++での自作関数の場合はメタ情報に BlueprintThreadSafe の付与が必要です。これも実際にスレッドセーフ実装の責任は作成者が負うことになります。

スレッドセーフ情報の付与
	UFUNCTION(BlueprintCallable, meta=(BlueprintThreadSafe))
	void HeavyFunc(){ /* スレッドセーフな処理 */ }

C++ での実装方法

C++ではUE4時代からあるFAnimInstanceProxyを使う方法と、UE5から追加されたNativeThreadSafeUpdateAnimationを使う方法の2通りがあるようです。

NativeThreadSafeUpdateAnimation を使う

C++では UAnimInstance::NativeThreadSafeUpdateAnimation でワーカースレッドにて実行することができます。スレッドセーフではない外部のオブジェクトにアクセスする場合などは排他処理を行うか、 UAnimInstance::NativeUpdateAnimation にてローカルにコピーしておく等の対応が必要になります。

以下コード例。

MyAnimInstance.h
UCLASS()
class MYPROJECT_API UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	
public:
	// ネイティブ更新オーバーライドポイント。
	virtual void NativeUpdateAnimation(float _DeltaSeconds) override;

	// ネイティブスレッドセーフ更新オーバーライド ポイント。
	virtual void NativeThreadSafeUpdateAnimation(float _DeltaSeconds) override;

	// ネイティブ ポスト評価オーバーライドポイント
	virtual void NativePostEvaluateAnimation() override;

private:
	FVector	LocalVelocity;
};

MyAnimInstance.cpp
// ネイティブ更新
void UMyAnimInstance::NativeUpdateAnimation(float _DeltaSeconds)
{
	// 基底処理
	Super::NativeUpdateAnimation(_DeltaSeconds);
	
	// スレッドセーフではない処理
	const APawn* _Pawn = Cast<APawn>(TryGetPawnOwner());
	if (!_Pawn){
		return;
	}

	UCharacterMovementComponent* MoveComp = Cast<UCharacterMovementComponent>(_Pawn->GetMovementComponent());
	LocalVelocity = MoveComp->Velocity;
}

// ネイティブスレッドセーフ更新
void UMyAnimInstance::NativeThreadSafeUpdateAnimation(float _DeltaSeconds)
{
	// 基底処理
	Super::NativeThreadSafeUpdateAnimation(_DeltaSeconds);

	// ワーカースレッドでの処理
}

// ネイティブアニメーション評価後
void UMyAnimInstance::NativePostEvaluateAnimation()
{
	// 基底処理
	Super::NativePostEvaluateAnimation();
	
	// 外部オブジェクトへの更新など
}

AnimInstanceProxy を使う

UE4時代からあった方法として、FAnimInstanceProxy を使った方法があります。FAnimInstanceProxyは UAnimInstance の代わりにアニメーション更新中に渡されるプロキシオブジェクトです。

FAnimInstanceProxyを継承して差し替え、スレッドセーフ処理のために外部オブジェクトからのデータを PreUpdate で保持し、Updateでワーカースレッドでの処理を書きます。
以下コード例。

MyAnimInsntance.h
// アニムインスタンスプロクシ
USTRUCT()
struct MYPROJECT_API FMyAnimInstanceProxy : public FAnimInstanceProxy
{
	GENERATED_USTRUCT_BODY()
	
public:
	virtual void InitializeObjects(UAnimInstance* _InAnimInstance) override;

	virtual void PreUpdate(UAnimInstance* _InAnimInstance, float _DeltaSeconds) override;

	virtual void Update(float _DeltaSeconds) override;
	
	virtual void PostUpdate(UAnimInstance* _InAnimInstance) const override;


	// 収集データ
	UPROPERTY(Transient)
	TObjectPtr<APawn> Owner;

	UPROPERTY(Transient)
	FVector ActorLocation;

	UPROPERTY(Transient)
	TObjectPtr<UCharacterMovementComponent> MovementComponent;

	UPROPERTY(Transient)
	FVector Velocity;;

	UPROPERTY(Transient)
	bool bIsAnyMontagePlaying;
};

// アニメーションインスタンスクラス
UCLASS()
class MYPROJECT_API UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

private:
	UPROPERTY(Transient)
	FMyAnimInstanceProxy Proxy;

	virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override { return &Proxy; }

	virtual void DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy) override {}

	friend struct FMyAnimInstanceProxy;
};
MyAminInsntace.cpp
void FMyAnimInstanceProxy::InitializeObjects(UAnimInstance* _InAnimInstance)
{
	FAnimInstanceProxy::InitializeObjects(_InAnimInstance);

	Owner = _InAnimInstance->TryGetPawnOwner();
	if (Owner == nullptr) { return; }

	MovementComponent = Cast<UCharacterMovementComponent>(Owner->GetMovementComponent());

}

void FMyAnimInstanceProxy::PreUpdate(UAnimInstance* _InAnimInstance, float _DeltaSeconds)
{
	FAnimInstanceProxy::PreUpdate(_InAnimInstance, _DeltaSeconds);

	// 外部からデータ収集
	if (::IsValid(Owner)){
		ActorLocation = Owner->GetActorLocation();
	}
	if (::IsValid(MovementComponent)){
		Velocity = MovementComponent->Velocity;
	}
	if (_InAnimInstance){
		bIsAnyMontagePlaying = _InAnimInstance->IsAnyMontagePlaying();
	}
}

void FMyAnimInstanceProxy::Update(float _DeltaSeconds)
{
	FAnimInstanceProxy::Update(_DeltaSeconds);

	// ワーカースレッドの処理
}

void FMyAnimInstanceProxy::PostUpdate(UAnimInstance* _InAnimInstance) const
{
	FAnimInstanceProxy::PostUpdate(_InAnimInstance);

	// 外部オブジェクトへの更新
}

Proxyも継承する必要があるので少し煩雑です。あと、UE5から追加された NativeThreadSafeUpdateAnimationを使う方法が推奨されているようです。

まとめ

BPのみでマルチスレッド処理の実装ができるのはC++での実装にかかる難易度を考えるとかなり凄い機能だと思います。
スレッドセーフ処理については、アニメーション処理上では外部オブジェクトの参照がほとんどだと思われるので排他処理ではなくコピーしておくパターンが多いのではないかと思います。

5
4
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
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?