概要
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
を追加
PublicDependencyModuleNames.AddRange(new string[]
{
...
...
"AnimGraphRuntime" // ←追加
});
アニメグラフノードクラス(AnimGraphNode)
アニメ―ションBPエディタでのアニムグラフで表示される情報を定義したクラス(UAnimGraphNode_Base
などを継承)
エディタに対して必要なので、エディタ用モジュールに用意する
"Engine/Source/Editor/AnimGraph/Classes/AnimGraphNode_Base.h"
Build.csへの追加
PrivateDependencyModuleNames に AnimGraph
BlueprintGraph
を追加
PrivateDependencyModuleNames.AddRange(new string[]
{
...
...
"AnimGraph", // ←追加
"BlueprintGraph" // ←追加
});
ボーンをトランスフォームするアニメ―ションノード作成例
ボーンを2つ変化させる処理を作成してみます。
元になる処理イメージは以下のようなものです。
Translationでの制御と、Rotationでの制御をそれぞれ行います。
1.AnimNode構造体を用意する
実処理を書いた構造体です。
ボーンをトランスフォームするための FAnimNode_ModifyBone
とそれをやりとりするための変換 FAnimNode_ConvertLocalToComponentSpace
FAnimNode_ConvertComponentToLocalSpace
を定義します。
#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()
に処理を書く必要があります。
#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構造体を紐づける必要があります。
#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のカテゴリ名をそれぞれ継承して変更しています。
#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.結果
以下のようなアニメ―ショングラフが使えるようになります。
プロパティについて
ノードに引き数となるプロパティの設定は通常のBPと同じようにメンバ変数で行うことができます。
プロパティの定義のコード例です。
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)
で制御できます。
その他
カスタムアニメノードの引き数に配列(TArray)を使う場合
TArray<>
でメンバを設定しても、ノード上では個別展開されてしまうようです。
この場合、引き数用の struct
を定義してから渡す用に修正してみるのも手だと思われます。
アニムグラフノード(AnimGraphNode)のモジュール設定について
アニムグラフノードを含めるモジュール設定は Type
を UncookedOnly
、LoadingPhase
を PreDefault
にします。
以下設定例。
{
"Name": "MyAnimGraph",
"Type": "UncookedOnly",
"LoadingPhase": "PreDefault",
"AdditionalDependencies": [
"DataValidation",
"AnimGraph"
]
}
まとめ
struct FAnimNode_StateMachine
などの定義でステートマシンもC++で制御ができるようです。
複数のアニメ―ションBPに似たようなアニムグラフの処理を書くような場合は、自作アニムグラフを作成して処理をまとめてしまえれば便利だと思われます。