ぼっちプログラマのメモ

Unreal Engineについて書いたりしてます

【UE5, C++】 AnimSequence から AnimComposite, Montage を少し便利に作る方法

はじめに

AnimSequenceアセットの右クリックメニューから AnimComposite, Montageアセットを作れます。しかし、アセット生成先のフォルダを指定できなかったり、アセット名末尾に _Composite/Montage が強制的につけられます。

それぞれ手動でアセットを移動・リネームすればいい話ではありますが…それが数十、数百回となると流石にストレスですし、ヒューマンエラーも時々発生してしまいそうです。

ということで、この処理をC++で自作して、プロジェクトのルールに沿った場所・命名規則AnimComposite, Montageアセットを作成できるようにします。

サンプルコード

細かい話はいいからコードを教えて!という方向けに、まずは結論から

AnimSequence → AnimComposite

UAnimComposite* XXX::CreateAnimCompositeFromSequence(UAnimSequence* SourceAnimSequence,
    const FString& DirectoryPath, const FString& AssetName, const FString& InPrefix)
{
    if (ensure(SourceAnimSequence))
    {
        if (UAnimComposite* NewAnimComposite = AnimationEditorUtils::CreateAnimationAsset<UAnimComposite>(
            SourceAnimSequence->GetSkeleton(), FPaths::Combine(DirectoryPath, AssetName), InPrefix))
        {
            // 指定のAnimSequenceをCompositeに登録
            FAnimSegment NewSegment;
            NewSegment.SetAnimReference(SourceAnimSequence, true);
            NewAnimComposite->AnimationTrack.AnimSegments.Add(NewSegment);
            NewAnimComposite->SetCompositeLength(NewAnimComposite->AnimationTrack.GetLength());

            // TagetFramerateが60000fpsになる挙動を回避
            NewAnimComposite->UpdateCommonTargetFrameRate();

            return NewAnimComposite;
        }
    }

    return nullptr;
}

使い方の例(EditorUtility系のBP, Widgetから呼び出し)

AnimSequence ( Composite ) → AnimMontage

UAnimMontage* XXX::CreateAnimMontageFromAnimSequenceBase(
    UAnimSequenceBase* SourceAnimSequenceBase, const FString& DirectoryPath, const FString& AssetName,
    const FString& InPrefix)
{
    if (ensure(SourceAnimSequenceBase))
    {
        // UAnimSequenceBaseだとAnimMontageも選択対象に入るが、
        // AnimMontage から AnimMontage を作ることはできないため
        if (SourceAnimSequenceBase->GetClass() == UAnimMontage::StaticClass())
        {
            // UE_LOG(XXX);
            return nullptr;
        }

        if (UAnimMontage* NewAnimMontage =
            AnimationEditorUtils::CreateAnimationAsset<UAnimMontage>(SourceAnimSequenceBase->GetSkeleton(),
                                                                     FPaths::Combine(DirectoryPath, AssetName),
                                                                     InPrefix))
        {
            // 指定のAnimSequenceBaseアセットをMontageに登録
            FAnimSegment NewSegment;
            NewSegment.SetAnimReference(SourceAnimSequenceBase, true);

            FSlotAnimationTrack& NewTrack = NewAnimMontage->SlotAnimTracks[0];
            NewTrack.AnimTrack.AnimSegments.Add(NewSegment);
            NewAnimMontage->SetCompositeLength(SourceAnimSequenceBase->GetPlayLength());

            // // 生成したMontageを開いた際にリファレンスポーズになる挙動を回避
            UAnimMontageFactory::EnsureStartingSection(NewAnimMontage);

            return NewAnimMontage;
        }
    }

    return nullptr;
}

使い方の例(EditorUtility系のBP, Widgetから呼び出し)

もう少し「いい感じに」するために

アセットを生成するフォルダを外部から設定可能に

上述の例ではフォルダパスを直接入力していますが、実際に運用する際は外部でルールを設定できるようにしておくと便利です。外部から設定する方法はいくつかありますが、こういったケースで個人的にオススメなのはプロジェクト・エディタ設定を使う方法です。

【UE4】UDeveloperSettingsでプロジェクト設定に項目を追加する|株式会社ヒストリア

命名規則を外部から可能に

命名規則に関しても外部からルールを設定できるようにしておくと便利です。また、正規表現を使ってルール化しておくとより複雑な条件でリネームができるようになります!

第1回:アセットリネームツール【新連載】 | UE5:Editor Utilityを活用したツール制作術

[UnrealC++]UE4における正規表現について

なお、今回の「AS_XXX を AM_XXX にリネーム」を1つ目の参考サイトにあるPython正規表現で実現する場合は以下のようになります(はず)

[ReplaceFrom]:AS_(\w+)

[ReplaceTo]:AM_\1

正規表現は…定期的に遭遇しては忘れるを繰り返すので、AIに頼るのが楽ですね!)

参考コード

今回のサンプルコードを作るうえで参考にした部分を…(全部載せると規約違反なので関数部分だけ

AnimMontageの生成処理

AnimMontageFactory.cpp

UObject* UAnimMontageFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)

AnimSequence右クリックメニューからのComposite, Montage生成処理

AssetDefinition_AnimSequence.cpp

void ExecuteNewAnimComposite(const FToolMenuContext& InContext)
void ExecuteNewAnimMontage(const FToolMenuContext& InContext)

SkeletonアセットからのComposite, Montage生成処理

AnimationEditorUtils.h

 template <typename TFactory, typename T>
    void ExecuteNewAnimAsset(TArray<TSoftObjectPtr<UObject>> SkeletonsOrSkeletalMeshes, const FString InSuffix, FAnimAssetCreated AssetCreated, bool bInContentBrowser, bool bAllowReplaceExisting)

AnimMontageからChildAnimMontageを作成する処理

AssetDefinition_AnimMontage.cpp

void ExecuteCreateChildAnimMontage(const FToolMenuContext& InContext)

AnimationEditorUtils.h

template< class T >
    T* CreateAnimationAsset(UObject* SkeletonOrSkeletalMesh, const FString& AssetPath, const FString& InPrefix)

おわりに

ちょっとしたことではありますが、開発初期から終盤にかけて何度も何度も行われる操作に関しては早期から便利にしていきたいですね!(不便に慣れるのはオワリノハジマリ)

AnimComposite, MontageからAnimSequenceへの逆変換もしたい所ですが、こちらはベイク処理になるのでちょっと複雑に…近い将来に記事化したいなぁとちょっとずつ実装を進めてたりします。

おしまい