エンジニアっぽくなりたい

UnityやUnrealEngine 4などでなにか役に立ちそうなことを発信していく。

UE4 AnimBPを使いまわす方法

UE4でアニメーションを制御するときにAnimationBlueprint(以後AnimBP)を使うことがありますよね。
私の認識では、スケルトンが異なる場合は別のAnimBPを作成する必要があり、AnimGraphなどの処理を別のスケルトンに使い回す方法がないと思っていました。
このままでは、同じような動作をするキャラクターに、追加で処理を加えるとき、C++でできない変更の場合はキャラ数分変更をする必要があるということになりますよね。
流石に不便だよなーーと思っていたのですが、いい感じの方法があったので紹介してみます。


※この記事の続きとしてより便利に使える方法も記事にしています。合わせて見ていただけると良いかと思います。

naoxgames.hatenablog.jp




目標


AnimBPを異なるスケルトンで使い回す!
今回は、LookAtノードを設定したAnimBPを別スケルトンでも使い回し、同じように腰や首が対象物の方を向くようにしてみます。
※汎用性を高めるためにCustomAnimNodeとかにも手を出します。


環境

・UE 4.26.1 (エンジンビルドはしていません)
・VisualStudio 2019 16.7.2


やってみる

作戦、というかもはや本質ですが、AnimBPを継承します。
まずは継承元となるAnimBPを作成し、LookAtの設定をします。

AnimBP作成して視線制御

コンテンツブラウザから右クリックでAnimBPを作成しましょう。
ケルトンの指定は「UE4_Mannequin_Skeleton」とします。
f:id:naoxgames:20210811231212p:plain
f:id:naoxgames:20210811231445p:plain

注視の設定します。
以下の画像のように設定してみます。
f:id:naoxgames:20210811232021p:plain
内容としては、
・適当なアニメーションを指定(ここではThirdPersonIdle)
・注視点を示すVector3の変数を作成(ここではLookAtPos)
・「注視点(LookAt)」ノードを3つ配置して、「Bone to Modify」に上から「head」「neck1」「spine_03」を指定し、「LookAtLocation」のピンを公開します。
f:id:naoxgames:20210811232352p:plain
・LookAtPosを各注視点ノードの「LookAtLocation」ピンにつなげる。
・「注視点(LookAt)」ノード3つの「Alpha」ピンの値を合計1.0になるように設定する(注視点を見るときにその骨が動く割合を示します。)
・出力ポーズにつなげる。

これで注視の設定ができました。
設定がうまくいっていると真下を向いているはずです。
f:id:naoxgames:20210811232708p:plain


継承してAnimBP作成

コンテンツブラウザ上で、前の手順で作成したAnimBPを右クリックして「子ブループリントクラスを作成します」をクリック。
f:id:naoxgames:20210811232814p:plain
これで子AnimBPが作成されました。


AnimBPの対象スケルトンを変更

別のスケルトンをAnimBPに設定します。
ここでは、以下のアセットが無料だったのでこれを利用して動物2匹ほど動かしてみます。
marketplace-website-node-launcher-prod.ol.epicgames.com

AnimBPのスケルトンですが、継承できるのは知っていましたが、スケルトンに依存していると認識していたため使うことがありませんでした。
がしかし!!!!
継承したAnimBPで対象のスケルトンを変更することができます。
AnimBPの「クラス設定」から設定できます。
f:id:naoxgames:20210811233413p:plain

ここでこの継承したAnimBPに別のスケルトンを設定しましょう。
ここでは「SK_DeerStag_Skeleton」と「SK_Pig_Skeleton」を設定してみます。

ケルトンを変更して、一度そのAnimBPを閉じて開きなおすと、スケルトンに応じたスケルタルメッシュがプレビューに表示されます。
f:id:naoxgames:20210811233658p:plain

LookAtを拡張したAnimNodeを作成する

AnimBPを継承してスケルトンを変更することで異なる骨でもロジックを使いまわすことはできました。
しかし、LookAtノードのように、詳細ビューから直接骨を指定しているパターンもあり、その値を継承先で上書きできなければ大きな問題になります。
そのため、今回は「AnimInstance」を継承したクラスと、LookAtノード一式を継承・コピーしたCustomAnimNodeを作成してみます。
LookAtノードのクラスは「AnimGraphNode_LookAt」「AnimNode_LookAt」となり、これらを継承、もしくはコピーした新クラスを作成します。
それに合わせてモジュールの設定もお願いします。(今回はランタイムのモジュールに書いてしまいますが、実際はEditorと分けないとパッケージ化するときにエラーになります。)


ここでは
MyAnimInstance      → AnimInstanceを継承したクラス
MyAnimGraphNode_LookAt  → AnimGraphNode_LookAtをコピーしたクラス
MyAnimNode_LookAt    → AnimNode_LookAtを継承した構造体
とします。


作戦としては、MyAnimInstanceに設定された値をMyAnimNode_LookAtで受け取って処理を行うようにします。
MyAnimInstanceに設定された変数は、AnimBPの「クラスのデフォルト」からAnimBP毎に設定ができるため、これで継承先で別の設定ができます。
※本当はキャラによってLookAtの対象にする骨の数が違う可能性があるため、それも考慮した実装にしたいが、今回は単純にAnimNode_LookAtの機能を改造するだけにします。

それぞれコードは以下のようにします。

Build.cs

~
        PublicDependencyModuleNames.AddRange(new string[] { "AnimGraphRuntime" });

        //Editor用
        PublicDependencyModuleNames.AddRange(new string[] { "BlueprintGraph" });
~



MyAnimInstance.cpp

#pragma once

#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "BoneContainer.h"

#include "MyAnimInstance.generated.h"


USTRUCT(BlueprintType)
struct FBoneInfo
{
    GENERATED_USTRUCT_BODY()
    UPROPERTY(EditAnywhere)
    FBoneReference BoneRef;

    UPROPERTY(EditAnywhere)
    FVector RotAxis;

};

UCLASS()
class PROJECT_API UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
    
public:
    UPROPERTY(EditAnywhere, Category = SkeletalControl)
        TMap<FString, FBoneInfo> BoneMap;
};


MyAnimGraphNode_LookAt.h
※基本はAnimGraphNode_LookAt.hのコピーとなるため、変更した箇所のみ記述します。Nodeの型を変えただけです。

~
#include "MyAnimNode_LookAt.h"
#include "MyAnimGraphNode_LookAt.generated.h"


UCLASS(meta = (Keywords = "Look At, Follow, Trace, Track"))
class PROJECT_API UMyAnimGraphNode_LookAt : public UAnimGraphNode_SkeletalControlBase
{
    GENERATED_UCLASS_BODY()

    UPROPERTY(EditAnywhere, Category = Settings)
        FMyAnimNode_LookAt Node;
~
};


MyAnimGraphNode_LookAt.cpp
※基本はAnimGraphNode_LookAt.cppのコピーとなるため、変更した箇所のみ記述します。Nodeの名前変えただけです。

~
#include "MyAnimGraphNode_LookAt.h"
#include "AnimGraphNode_LookAt.h"
#include "Animation/AnimInstance.h"
#include "UObject/AnimPhysObjectVersion.h"
~
#define LOCTEXT_NAMESPACE "MyAnimGraph_LookAt"
~
FText UMyAnimGraphNode_LookAt::GetControllerDescription() const
{
    return LOCTEXT("LookAtNode", "My Look At");
}
~
FText UMyAnimGraphNode_LookAt::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
~
        if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
        {
            CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("MyAnimGraphNode_LookAt_ListTitle", "MyAnimLookAt{ControllerDescription} - Bone: {BoneName}"), Args), this);
        }
        else
        {
            CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("MyAnimGraphNode_LookAt_Title", "MyAnimLookAt{ControllerDescription}\nBone: {BoneName}"), Args), this);
        }
~
}
~


MyAnimNode_LookAt.h

~
#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BoneIndices.h"
#include "BoneContainer.h"
#include "BonePose.h"
#include "BoneControllers/AnimNode_SkeletalControlBase.h"
#include "BoneControllers/AnimNode_LookAt.h"
#include "CommonAnimTypes.h"
#include "MyAnimNode_LookAt.generated.h"

USTRUCT()
struct PROJECT_API FMyAnimNode_LookAt : public FAnimNode_LookAt
{
    GENERATED_USTRUCT_BODY()

    UPROPERTY(EditAnywhere)
        FString BoneKey;

    virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
};


MyAnimNode_LookAt.cpp

#include "MyAnimNode_LookAt.h"
#include "MyAnimInstance.h"
#include "Animation/AnimInstanceProxy.h"

void FMyAnimNode_LookAt::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
    UObject* animInstanceObj = Context.AnimInstanceProxy->GetAnimInstanceObject();
    UMyAnimInstance* animInstance = Cast<UMyAnimInstance>(animInstanceObj);
    if (animInstance != nullptr && animInstance->BoneMap.Find(BoneKey) != nullptr)
    {
        BoneToModify = animInstance->BoneMap[BoneKey].BoneRef;
        LookAt_Axis.Axis = animInstance->BoneMap[BoneKey].RotAxis;
    }

    FAnimNode_LookAt::Initialize_AnyThread(Context);
}



これでAnimBPを使い回すための新LookAtノードが作成できました。


キャラ毎に設定する

キャラ毎に設定をして、違うスケルトンだけど同じロジックが動いている状態を作ります。


まずは継承元の注視ノードを先ほど作成したMyLookAtに差し替えます。
ノードの詳細から、「BoneKey」を設定します。
f:id:naoxgames:20210812002855p:plain
上から順に「head」「neck」「spine」とします。

AnimBPのクラスのデフォルトの設定は以下のようにします。
UE4_Mannequin_Skeleton
f:id:naoxgames:20210812002615p:plain 「head」「neck」「spine」それぞれに適切な骨を設定します。
「RotAxis」は(0,1,0)にします。

先ほど継承して作成した動物用のAnimBPのクラスのデフォルトにも設定を行います。
動物は骨の軸が「UE4_Mannequin_Skeleton」と違うようなので合わせて「RotAxis」を設定します。
f:id:naoxgames:20210812011520p:plain

SK_DeerStag_Skeleton
f:id:naoxgames:20210812003235p:plain

SK_Pig_Skeleton
f:id:naoxgames:20210812003400p:plain


あとはAnimBPの注視点を示す「LookAtPos」に注視する座標を設定します。
BPとかで対象を受け取ってAnimBPの「LookAtPos」に代入したりしましょう。

これで準備はできた!!!


実行!!!!!

f:id:naoxgames:20210812012323p:plain


こんな感じになった!みんないい感じにこっちみてる。
骨に制限かかってないから、すんごいこっちみてる。(制限をかけましょう。)


思ったこと

これみんな知ってるのかな?
私は人に教えてもらうまでこの方法を知らず、知った上で検索してもそれっぽい情報をなかなか得られませんでした。
あまり知られていないのか?問題がある手法なのか?分かりませんが少なからず上記で要件は達成できそうに思えます。
CustomAnimNodeやAnimInstanceの実装がこうするのが適切かはわかりません、ノードのInitializeThread時取得する方法がこれしか浮かばず、複数の骨指定のためにTMapにしてみました。より良い実装があればご指摘いただきたいです。

また、この記事の続きとしてAnimLayerを使いより便利に使える方法も記事にしています。合わせて見ていただけると良いかと思います。
naoxgames.hatenablog.jp




正しい対応方法やより良い方法があればご指摘お願いいたします!!!