Help us understand the problem. What is going on with this article?

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

概要

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

関連:UE4 ボーンのTransform制御について

環境

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);

    ComponentToLocalNode.Initialize_AnyThread(Context);
}

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

// ノード関連グラフ更新処理
void FMyAnimNode_TransformBone::Update_AnyThread(const FAnimationUpdateContext& Context)
{
    // ノード接続[コンポーネントからローカルへ]
    ComponentToLocalNode.ComponentPose.SetLinkNode(&ModifyBoneNode1);
    // ボーントランスフォーム
    ModifyBoneNode1.ComponentPose.SetLinkNode(&ModifyBoneNode2);
    ModifyBoneNode2.ComponentPose.SetLinkNode(&LocalToComponentNode);
    // ノード接続[ローカルからコンポーネントへ]
    LocalToComponentNode.LocalPose = SourcePose;


    // ボーンをトランスフォーム1("Root"のZ座標を変える)
    ModifyBoneNode1.BoneToModify.BoneName = FName("Root");
    ModifyBoneNode1.BoneToModify.Initialize(Context.AnimInstanceProxy->GetSkeleton());
    ModifyBoneNode1.Translation = FVector(0.f, 0.f, 100.0f);
    ModifyBoneNode1.TranslationMode = BMM_Replace;

    // 回転値の計算
    RollVal = FMath::Fmod( (RollVal+60.0f), 360.0f);

    // ボーンをトランスフォーム2("Propeller"のロールを回転させる)
    ModifyBoneNode2.BoneToModify.BoneName = FName("Propeller");
    ModifyBoneNode2.BoneToModify.Initialize(Context.AnimInstanceProxy->GetSkeleton());
    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"

#define LOCTEXT_NAMESPACE "MyNamespace"

// コンストラクタ
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( LOCTEXT("Title", "MyAnimGraphNode_TransformBone") );
}

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

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

#undef LOCTEXT_NAMESPACE

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

まとめ

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

unknown_ds
都内在住。 テレワークは環境が整っていることが重要だと思います。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした