1
1

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 3 years have passed since last update.

UE4 アニメ―ションノードの作成(ボーンのトランスフォーム)を試してみる

Last updated at Posted at 2020-04-11

概要

UnrealEngine の自作アニメ―ションノードを作成する際のメモです。

関連:UE4 ボーンのTransform制御について
カスタム アニメーション ノードの作成

更新履歴

日付 内容
2020/10/23 サンプルコード修正、その他追記
2020/12/16 アニムグラフノードのモジュールについて追記

環境

Windows10
Visual Studio 2017
UnrealEngine 4.24

参考

以下を参考にさせて頂きました、ありがとうございます。

アニメーションノードをまとめたアニメーションノードを作ってみた
ボーンを制御するカスタムアニメノードグラフを作る
UnrealEngine : アニメーション ノードのテクニカル ガイド
UnrealEngine : FAnimNode_ModifyBone
[UE4]Transform skeletal bone in C++

アニメノード作成に必要なもの

アニメノードでは以下2つが必要で、そのためのモジュールの追加をする必要があります。

ランタイム構造体(AnimNode)

実際に行われる実処理を記述した構造体(FAnimNode_Baseなどを継承)
ランタイム時に使用される

"Engine/Source/Runtime/Engine/Classes/Animation/AnimationNodeBase.h"

Build.csへの追加

PublicDependencyModuleNames にAnimGraphRuntimeを追加

**Build.cs
PublicDependencyModuleNames.AddRange(new string[]
{
	...
	...
	"AnimGraphRuntime"	// ←追加
});

アニメグラフノードクラス(AnimGraphNode)

アニメ―ションBPエディタでのアニムグラフで表示される情報を定義したクラス(UAnimGraphNode_Baseなどを継承)
エディタに対して必要なので、エディタ用モジュールに用意する

"Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Base.h"

Build.csへの追加

PrivateDependencyModuleNames に AnimGraph BlueprintGraph を追加

**Build.cs
PrivateDependencyModuleNames.AddRange(new string[]
{
	...
	...
	"AnimGraph",		// ←追加
	"BlueprintGraph"	// ←追加
});

ボーンをトランスフォームするアニメ―ションノード作成例

ボーンを2つ変化させる処理を作成してみます。
元になる処理イメージは以下のようなものです。
AnimGraphNode_BP.jpg
Translationでの制御と、Rotationでの制御をそれぞれ行います。

1.AnimNode構造体を用意する

実処理を書いた構造体です。
ボーンをトランスフォームするための FAnimNode_ModifyBone とそれをやりとりするための変換 FAnimNode_ConvertLocalToComponentSpace FAnimNode_ConvertComponentToLocalSpace を定義します。

MyAnimNode_TransformBone.h

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimNodeBase.h"
#include "Animation/AnimNodeSpaceConversions.h"
#include "BoneControllers/AnimNode_ModifyBone.h"
#include "MyAnimNode_TransformBone.generated.h"


USTRUCT(BlueprintType)
struct GAME_API FMyAnimNode_TransformBone : public FAnimNode_Base
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Links")
	FPoseLink SourcePose;

public:
	FMyAnimNode_TransformBone();

	virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
	virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override;
	virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override;
	virtual void Evaluate_AnyThread(FPoseContext& Output) override;

private:
	FAnimNode_ConvertLocalToComponentSpace LocalToComponentNode;
	FAnimNode_ConvertComponentToLocalSpace ComponentToLocalNode;

	FAnimNode_ModifyBone ModifyBoneNode1;
	FAnimNode_ModifyBone ModifyBoneNode2;

	// 回転用
	float RollVal = 0.0f;
};

ボーンをトランスフォームする処理が最初の1回で良ければ、 Initialize_AnyThread() に処理を書いてもいいのですが、回転処理などの毎フレーム処理では Update_AnyThread() に処理を書く必要があります。

MyAnimNode_TransformBone.cpp

#include "MyAnimNode_TransformBone.h"

#include "Animation/AnimInstanceProxy.h"
#include "Animation/AnimTypes.h"

// コンストラクタ
FMyAnimNode_TransformBone::FMyAnimNode_TransformBone()
	: LocalToComponentNode()
	, ComponentToLocalNode()
	, ModifyBoneNode1()
	, ModifyBoneNode2()
{
	
}

// 初期化処理(ノードが最初に実行される時に呼ばれる処理)
void FMyAnimNode_TransformBone::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
	FAnimNode_Base::Initialize(Context);

	// 初期化1
	ModifyBoneNode1.BoneToModify.BoneName = FName("Root");
	ModifyBoneNode1.BoneToModify.Initialize(Context.AnimInstanceProxy->GetSkeleton());
	// 初期化2
	ModifyBoneNode2.BoneToModify.BoneName = FName("Propeller");
	ModifyBoneNode2.BoneToModify.Initialize(Context.AnimInstanceProxy->GetSkeleton());
	

	// ノード接続[コンポーネントからローカルへ]
	ComponentToLocalNode.ComponentPose.SetLinkNode(&ModifyBoneNode1);
	// ボーントランスフォーム
	ModifyBoneNode1.ComponentPose.SetLinkNode(&ModifyBoneNode2);
	ModifyBoneNode2.ComponentPose.SetLinkNode(&LocalToComponentNode);
	// ノード接続[ローカルからコンポーネントへ]
	LocalToComponentNode.LocalPose = SourcePose;

	ComponentToLocalNode.Initialize_AnyThread(Context);
}

// ボーンをキャッシュする処理
void FMyAnimNode_TransformBone::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
	ComponentToLocalNode.CacheBones_AnyThread(Context);
}

// ノード関連グラフ更新処理
void FMyAnimNode_TransformBone::Update_AnyThread(const FAnimationUpdateContext& Context)
{
	UObject* _Obj = Context.AnimInstanceProxy->GetAnimInstanceObject();
	if (_Obj) {
		// アニムインスタンスとの処理
	}
	
	// ボーンをトランスフォーム1("Root"のZ座標を変える)
	ModifyBoneNode1.Translation = FVector(0.f, 0.f, 100.0f);
	ModifyBoneNode1.TranslationMode = BMM_Replace;
	
	// 回転値の計算
	RollVal = FMath::Fmod( (RollVal+60.0f), 360.0f);
	
	// ボーンをトランスフォーム2("Propeller"のロールを回転させる)
	ModifyBoneNode2.Rotation = FRotator(0.f, 0.f, RollVal);
	ModifyBoneNode2.RotationMode = BMM_Additive;
	ModifyBoneNode2.RotationSpace = EBoneControlSpace::BCS_ParentBoneSpace;
	
	ComponentToLocalNode.Update_AnyThread(Context);
}

// ボーン変換評価処理
void FMyAnimNode_TransformBone::Evaluate_AnyThread(FPoseContext& Output)
{
	ComponentToLocalNode.Evaluate_AnyThread(Output);
}

2.AnimGraphNodeを用意する

エディタに対してのコードなのでエディタ用モジュールに用意しないとパブリッシュ時に問題がでます。
先に定義したAnimNode構造体を紐づける必要があります。

MyAnimGraphNode_TransformBone.h

#pragma once

#include "CoreMinimal.h"
#include "AnimGraphNode_Base.h"
#include "???/???/MyAnimNode_TransformBone.h"	// 定義したAnimNode構造体のパス

#include "MyAnimGraphNode_TransformBone.generated.h"

UCLASS()
class TESTEDITOR_API UMyAnimGraphNode_TransformBone : public UAnimGraphNode_Base
{
	GENERATED_UCLASS_BODY()


	UPROPERTY(EditAnywhere, Category = Settings)
	FMyAnimNode_TransformBone Node;

public:
	// UEdGraphNode interface
	virtual FLinearColor GetNodeTitleColor() const override;
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
	virtual FText GetTooltipText() const override;


	// UAnimGraphNode_Base interface
	virtual FString GetNodeCategory() const override;

};

タイトル名、ノードのカラー、チップス、BPのカテゴリ名をそれぞれ継承して変更しています。

MyAnimGraphNode_TransformBone.cpp

#include "MyAnimGraphNode_TransformBone.h"

// コンストラクタ
UMyAnimGraphNode_TransformBone::UMyAnimGraphNode_TransformBone(const FObjectInitializer& ObjectInitializer)
	:Super(ObjectInitializer)
{
}

// ノードタイトルカラー
FLinearColor UMyAnimGraphNode_TransformBone::GetNodeTitleColor() const
{
	return( FLinearColor::Blue );
}

// ノードタイトル
FText UMyAnimGraphNode_TransformBone::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	return( FText::FromString("MyAnimGraphNode_TransformBone") );
}

// ツールチップ
FText UMyAnimGraphNode_TransformBone::GetTooltipText() const
{
	return( FText::FromString("自作のアニメ―ションノードです。") );
}

// ノードカテゴリ名
FString UMyAnimGraphNode_TransformBone::GetNodeCategory() const
{
	return TEXT("MyAnimNode");
}

3.結果

以下のようなアニメ―ショングラフが使えるようになります。

MyAnimGraphNode.jpg

プロパティについて

ノードに引き数となるプロパティの設定は通常のBPと同じようにメンバ変数で行うことができます。
プロパティの定義のコード例です。

MyAnimNode_TransformBone.cpp
USTRUCT(BlueprintType)
struct GAME_API FMyAnimNode_TransformBone : public FAnimNode_Base
{
	//...省略...

public:
	// ボーンリファレンス
	UPROPERTY(EditAnywhere, Category = "Settings")
	FBoneReference TargetBone;

	// 変数
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings", meta=(PinShownByDefault))
	float Alpha;
};

ピンが表示され、ノードの[詳細]に[Settings]カテゴリで表示されます。
ピンの表示オン/オフは meta=(PinShownByDefault) で制御できます。
Property.jpg

その他

カスタムアニメノードの引き数に配列(TArray)を使う場合

TArray<>でメンバを設定しても、ノード上では個別展開されてしまうようです。
この場合、引き数用の structを定義してから渡す用に修正してみるのも手だと思われます。

アニムグラフノード(AnimGraphNode)のモジュール設定について

アニムグラフノードを含めるモジュール設定は TypeUncookedOnlyLoadingPhasePreDefault にします。
以下設定例。

.uproject
{
	"Name": "MyAnimGraph",
	"Type": "UncookedOnly",
	"LoadingPhase": "PreDefault",
	"AdditionalDependencies": [
		"DataValidation",
		"AnimGraph"
	]
}

まとめ

struct FAnimNode_StateMachineなどの定義でステートマシンもC++で制御ができるようです。
複数のアニメ―ションBPに似たようなアニムグラフの処理を書くような場合は、自作アニムグラフを作成して処理をまとめてしまえれば便利だと思われます。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?