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

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

UE5 シーケンサーで自作トラックを活用する(2)~トラック追加対象制御~

前回自作トラックをひとまず作ってみました。
前回の内容では、マスタートラックとしてもどのアクターに対しても追加できる状態となっており、またそのアクターの情報を制御する方法も作っておりません。

今回はそのあたりを対応していこうと思います。

 

 

やりたいこと

前回作成したトラックに、以下のような対応を行う

  • トラックを追加できる対象を制限
  • トラックを追加した対象をトラック内で制御する

具体的には、ACharacterクラスに対してトラックを追加できるようにし、トラック内でACharacterの座標を変更させるようにしてみましょう。

 

今回は以下の記事の続きになります。

naoxgames.hatenablog.jp

 

環境

UE 5.4.4
VisualStudio 2022 17.11.1

 

作戦

前回調べたクラスを基にまたエンジンコードを眺めていた感じ、以下が参考になりそうなのでそれらを基に対応する。

  • バインド対象指定系
    • Engine\Plugins\Animation\ControlRig\Source\ControlRigEditor\Private\Sequencer\
      ControlRigParameterTrackEditor.h
    • Engine\Plugins\Animation\ControlRig\Source\ControlRigEditor\Private\Sequencer\
      ControlRigParameterTrackEditor.cpp

  • Excute内でバインド対象を操作する系
    • Engine\Plugins\Animation\ControlRig\Source\ControlRig\Public\Sequencer\
      MovieSceneControlRigParameterTemplate.h
    • Engine\Plugins\Animation\ControlRig\Source\ControlRig\Private\Sequencer\
      MovieSceneControlRigParameterTemplate.cpp

ということで今回はControlRigParameterTrackに倣って対応してみよう!

 

やってみた

先に結果を以下に記述します。

関連コミットのみはこちら

github.com

 

全体はこちら

github.com

 

 

今回の対応で、以下のようにシーケンサーのマスタートラックの追加から選択できなくなりました。

そして以下のように「ACharacter」を継承しているアクターに対してはトラックの追加が可能になっています。

 

挙動は、以下のようにキーの中にTransformの値を設定できるように項目を追加し、

 

キーが配置されているタイミングでキャラクターに処理が反映されます。

※移動はテレポート的な挙動になります。

 

という感じになりました。

以前のトラックよりもそれなりに使い物になるようになったと思います。
今回はTransformの制御にしましたが、この方法で制御するのはあまり適切ではないですね。
今回のようなTransfrom制御では、既存のTransformトラックやControlRigParameterトラックのように各パラメーターがトラックにぶら下がっていて、各パラメーターがカーブで制御できるのが望ましいです。

今後そのような対応も試して記事にしていきます。

 

対応内容

マスタートラックを封じる

「FMyEventTrackEditor::BuildAddTrackMenu」の中身を丸ごとコメントアウトしました。
この関数はシーケンサーの「+追加」ボタンから呼び出されるときの処理となっており、ここの処理によってマスタートラックとして追加できるようになります。

今回は追加できる対象を制限したいので丸ごとコメントアウトします。

 

トラック追加可能対象を指定する

「FControlRigParameterTrackEditor::BuildObjectBindingTrackMenu」に倣って「FMyEventTrackEditor::BuildObjectBindingTrackMenu」に処理を追加します。

内容はシンプルで以下を追加します。

if (!ObjectClass)
{
    return;
}
if (!ObjectClass->IsChildOf(ACharacter::StaticClass()))
{
    return;
}
「BuildObjectBindingTrackMenu」の「ObjectClass」は、トラックを追加した対象が取得でき、今回は「ACharacter」ですが、「Component」でも可能です。

これにより、「ACharacter」以外の場合はトラック追加メニューに表示されなくなります。

 

バインド対象をトラック内で制御する

今回は対象のTransformを制御するので、キーに情報を追加します。
「struct FMyMovieSceneEventParameters」に以下のように「FTransform」を追加します。

UPROPERTY(EditAnywhere)
FTransform TestTransform;

次に、「MyMovieSceneEventEvalTemplate.cpp」で「FMyEventTrackExecutionToken」(※今回名前変えてます)の「Execute」の中身を変更します。
こちらも「ControlRigParameter」関連の「FControlRigParameterExecutionToken」の「Execute」に倣って対応します。


virtual void Execute(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand, FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) override { FMovieSceneSequenceID SequenceID = Operand.SequenceID; TArrayView<TWeakObjectPtr<>> BoundObjects = Player.FindBoundObjects(Operand); const UMovieSceneSequence* Sequence = Player.State.FindSequence(Operand.SequenceID); UObject* BoundObject = BoundObjects.Num() > 0 ? BoundObjects[0].Get() : nullptr; ACharacter* TargetCharacter = Cast<ACharacter>(BoundObject); if (TargetCharacter==nullptr) { return; } TArray<UObject*> EventContexts = Player.GetEventContexts(); for (UObject* EventContextObject : EventContexts) { if (!EventContextObject) { continue; } for (FMyMovieSceneExcuteEventData& Event : Events) { TargetCharacter->SetActorTransform(Event.Payload.Parameters.TestTransform); } } }

「IMovieScenePlayer」から「FMovieSceneEvaluationOperand」で取得できてしまうようで、お手軽ですね!

あとは前の処理同様の流れで、最後に対象の「ACharacter」に「Transform」を設定することでバインド対象の制御が行えます。

 

 

懸念点

今回の対応でトラックを追加できる対象を制限しましたが、実はシーケンサー上でコピペすると他のにトラックを設定できてしまいます。
C++でも普通に追加できちゃうので、気を付けましょう!

 

思ったこと

ということで、バインド対象の制限と制御をしてみたわけですが。
今回の対応でわりと使える幅が広がって、だいたいのことはできるようになった気がしています。

わりとエンジンのコードを参考にするだけでいろんなことが簡単にできることがわかりますね!

そういえば今回参考にしたControlRigParameterTrackでも「FMovieSceneEvalTemplate」を使った処理になっていて、ほぼ同じような処理の流れになっています。
前の記事でLegacyなのかもって心配していましたが、特に問題なく現役で使われているようなので大丈夫そうだなと感じています。

 

 

なにかあればご指摘お願いいたします!!!

 

UE5 シーケンサーで自作トラックを活用する(1)~最小構成で作ってみる~

シーケンサーでいろいろ制御するときに、プロジェクト都合に合わせた独自の処理をしたくなることありますよね。
それでトラックとか自作したくなるわけですが、いろいろと調べて自分なりにやってみました。

 

 

やりたいこと

シーケンサーのトラックを自作する。
ひとまずエンジン純正のEventTrackのTriggerのような挙動を目指す。
※キーに情報を仕込んで、そのタイミングでそれに応じた処理が行われる感じ。
また、今回はプラグインとして実装します。

 

環境

UE 5.4.4
VisualStudio 2022 17.11.1

 

作戦

エンジンコードを眺めていた感じ以下が参考になりそうなので、それらを基に対応する。

トラック系のサンプルのようなコード
Engine\Source\Runtime\MovieScene\Private\Tests\
 MovieSceneTestObjects.h
 MovieSceneTestObjects.cpp

純正のイベントトラック関連
Engine\Source\Runtime\MovieSceneTracks\Public\Tracks\
 MovieSceneEventTrack.h

Engine\Source\Runtime\MovieSceneTracks\Private\Tracks\
 MovieSceneEventTrack.cpp

Engine\Source\Runtime\MovieSceneTracks\Public\Sections\
 MovieSceneEventSection.h

Engine\Source\Runtime\MovieSceneTracks\Private\Sections\
 MovieSceneEventSection.cpp

Engine\Source\Runtime\MovieSceneTracks\Private\Evaluation\
 MovieSceneEventTemplate.h

Engine\Source\Runtime\MovieSceneTracks\Private\Evaluation\
 MovieSceneEventTemplate.cpp

Engine\Source\Editor\MovieSceneTools\Private\TrackEditors\
 EventTrackEditor.h
 EventTrackEditor.cpp

トラック関連をシーケンサーと紐づけている様子
Engine\Source\Editor\MovieSceneTools\Private\
MovieSceneToolsModule.cpp

 

やってみた

先に結果を以下に記述します。
※すべて書くとなかなかの量になってしまうのでGithubにて確認お願いします。

関連コミットのみはこちら

github.com

 

全体はこちら

github.com

 

この対応により以下のようにシーケンサーからトラックを設定できたり。


セクションの追加やキーの追加、またキーの詳細に指定したパラメーターが設定できたり。

 

シーケンサーを再生するとそのキーのタイミングで処理がされるようにできたり。
※今回の対応ではログがでるようにしているだけなので、以下のようになってます。

 

という感じです!!!!

 

いやいや、まだまだこれじゃやりたいことに達してないよ!! って気持ちはわかりますので今後この続きの記事を書いていければと思っております!!!

 

対応内容

それとなく対応内容を記述していきます。

プラグイン作成

適当にプロジェクトを作成して、
「SequencerCustomPlugin」というプラグインを作成します。
この時、エディター用モジュールも追加しておきましょう。

トラック関連のクラス、処理作成

主にMovieSceneTestObjects.h、MovieSceneTestObjects.cppを見てそれっぽいクラスを確認し、それっぽいクラスを検索するとMovieSceneEventTrack系をパクるのがわかりやすいかも ってを感じました。

なのでMovieSceneEventTrack系を思いっきりパクっています。

主にトラック関連で必要になるのは以下

  • MovieSceneTrack
    • ランタイムのトラッククラス。
  • UMovieSceneSection
    • ランタイムのセクションクラス。
  • FMovieSceneEvalTemplate
    • 実行時の処理を行ってます。
  • FMovieSceneChannel
    • セクションのキーの中身です。
  • FMovieSceneTrackEditor
    • エディター用クラス。
    • シーケンサー上でのトラックの処理に関わっています。
    • これがないとメニューに追加されなかったりする。
  • (FSequencerSection)※今回作ってません。

これらをEventTrackに倣って作成します。

今回は「FMovieSceneChannel」ではいくつかのパラメーターを設定できるようにし、「FMyMovieSceneEventEvalTemplate」でキーを判定してそのパラメーターをログで表示する という処理で作成しています。

シーケンサーに登録

シーケンサーのメニューからトラックを追加できるようにする。

FMovieSceneToolsModule::StartupModule()の処理に倣って、プラグインのエディターモジュールの「FSequencerCustomPlugin_Editor::StartupModule()」で同様の登録処理を行っています。

これによって作成したトラックが追加できるようになります。

 

これらにより、トラックが利用できるようになりました。

 

懸念点

今回参考にした「UMovieSceneEventTrack」の「UMovieSceneEventTrack::CreateTemplateForSection」で以下のような処理がある。

if (const UMovieSceneEventSection* LegacyEventSection = Cast<const UMovieSceneEventSection>(&InSection))

「LegacyEventSection」という印象から、この方式の処理はもしかしてLegacyなの?って感じちゃったりします。

※実際現在のバージョンで普通の手順で作成する場合この処理は通らない。

ちょっと心配ですね。

でも使えてるし大丈夫っしょ!!!

 

思ったこと

ということで自作トラックやってみたわけですが、一応これでも十分使える気はしています。
とはいえもうちょっといろいろやりたいところです。
パッと浮かぶところでは以下

  • トラック追加対象を絞る
  • Transformトラックのようなパラメーター追加
  • セクション前後のリストア処理
  • セクション上の表示
  • トラック、セクションクラスの活用

という感じ。

今後もこれらについて記述していきます。

ということで今年もよろしくお願いします。

 

以下に続きを書いています。

naoxgames.hatenablog.jp

 

なにかあればご指摘お願いいたします!!!

 

 

 

 

 

 

 

 

 

 

 

 

 

UE4 私がよく使うエディター上での便利な機能(初心者向けだと思う)

この記事の内容は「エディターを使う上での便利な機能」という感じで、最適化やデバッグに便利なものというわけではありません。
長くUEを使っている人にとっては大した内容ではない可能性が高いです。

最近UE4を触り始めた人からふとした質問をされたりして、初めて触る人は意外とこれ知らない人多いかもなぁ~ってのがありました。
なので今回は、その時に聞かれたものや自分がよく使っているちょっと便利?な機能を紹介してみます。





環境

・UE 4.27.2


各BPからノードを検索

ノード名や変数名、文字列などを指定した文字列で検索できます。
使い方は
ウィンドウ->ブループリントを検索->ブループリントを検索1~4を選びます。


「ブループリントを検索」のウィンドウが開きます。


例えば「Tick」で検索した場合こんな感じ


これはめっちゃ便利、使ってください。

ショートカット系

エディタの環境設定の一般->キーボードショートカット でショートカットキー設定が見れて設定もできます。



実行状態をパースペクティブビューで見たい

実行中にアクターを選択して状態を見たり、好きな場所にカメラを移動したりできます。 使い方は
Alt+S
でできます。

操作はパースペクティブビューのときと同じ操作になります。
わりと当たり前の操作ですが、触り始めたばかりの人は知らないこともあるっぽいです。


ブループリントのコンパイル

ブループリントのイベントグラフで「コンパイル」を押すのと同じことをしてくれる感じ。
使い方は
F7キー
押すだけ。

これが地味ーーーーーーに便利なんです。地味にね。やってみてちょうだい。


コンソールコマンド系

実行中に「@」キー押したら画面下部に出てくる入力欄や、アウトプットログの下部にある入力欄に入力することで利用できます。

画面に映ってるウィジェットを検知

今画面に映っているウィジェットや、エディターを構成している部分のクラスを検知することができます。
使い方は
WidgetReflector
入力すると以下のような画面が表示されます。


「ヒットテストできるウィジェットを選択」をクリックすると

以下のような画面になり、カーソルのある場所のウィジェットを検知します。

その状態でESCキーを押すと、検知した対象がハイライトされた状態で固定できます。
クラス名や状態が見れて便利です。



時間経過速度操作

時間の速さを変えられるコマンドです。
使い方は
Slomo [速度倍率の数値]
これによってモーションやエフェクトなどのスローの状態を見たり、逆に速くしてゲームの時間を経過させたりできます。
モーションのこのタイミングで出るエフェクトがうんぬんかんぬん!!とか言われたときに確認するのに便利でした。


フリーカメラ

Alt+Sでもパースペクティブビューで見れますが、コマンドでも似たような機能があります。
使い方は
ToggleDebugCamera

これによってゲーム中にカメラを自由に操作でき、そこにあるメッシュやマテリアルの情報も取得できます。




という感じ。

思ったこと

もっと便利なものはあるでしょうし、紹介したものも実装次第でうまく機能しないものもある、、のかもしれません。
とは言え紹介したものも場合によっては便利に扱えますし、各機能についてさらに情報を集めればより便利な使い方も見つけられるとおもいます。
まずは知らないと始まらないので、とりあえず紹介してみました。なにかもっといいのあれば教えてください!


UE4 エディター処理でプログレスバーを表示する

エディター系処理で結構長い時間がかかる処理をするときに、プログレスバーとか出して、今これくらいだよーって表示したくなることありますよね。
例えばエディターでレベルとか開くときに以下のような表示がされたりします。
f:id:naoxgames:20220411233815p:plain

こういうのを自分で操作するときも使いたい!

ということでやってみました。




目標

エディター処理でプログレスバーを表示してみる。
ついでに「キャンセル」とか、今処理中のファイル名とかも表示してみる。


環境

・UE 4.27.0 (Gitのコミット:99b6e203a15d04fc7bbbf554c421a985c1ccb8f1 をビルド)
・VisualStudio 2019 16.11.2


調べる

よくわからないので調査する。
とりあえず確定でプログレスバーが表示される操作を探して、その処理を追いかけてみます。

始めに画像を貼ったように「レベルを開く」と絶対表示されます。

いつものように「エディタの環境設定」→「一般」→「未分類」→「UIの拡張点の表示」にチェックを入れて再起動します。
f:id:naoxgames:20220411234748p:plain

ファイルから「レベルを開く」を見ると「OpenLevel」と記述されているので、コードから「OpenLevel」を探します。
f:id:naoxgames:20220411235057p:plain


このあたりの調査は過去に以下の記事で行いました。
naoxgames.hatenablog.jp

この時の調査結果でFileHelpers.cppの

bool FEditorFileUtils::LoadMap(const FString& InFilename, bool LoadAsTemplate, bool bShowProgress)

の処理を通ることがわかっています。

明らかに怪しい「bShowProgress」という引数を追ってると以下の処理に入る様子

GEditor->Exec( NULL, *LoadCommand );


この処理である「UEditorEngine::Exec」の中を、「FEditorFileUtils::LoadMap」の「LoadCommand」を元に追うと、
UEditorEngine::HandleMapCommand
UEditorEngine::Map_Load

int32 bShowProgress = 1;
FParse::Value(Str, TEXT("SHOWPROGRESS="), bShowProgress);
~
FScopedSlowTask SlowTask(100, LocalizedLoadingMap, bShowProgress != 0);
SlowTask.MakeDialog();

SlowTask.EnterProgressFrame(10, FText::Format( NSLOCTEXT("UnrealEd", "LoadingMapStatus_CleaningUp", "{0} (Clearing existing world)"), LocalizedLoadingMap ));

というコードにたどり着きます。

「FScopedSlowTask」がプログレスバーの処理をやってくれているようです。

これを調べてみるといろいろ出てくる。
それらを参考に目標の要件を満たしてみる。


やってみる

今回は以下の記事で対応したレベルを開いて一括変換を行う、という処理に対してプログレスバーを付けてみようと思います。
naoxgames.hatenablog.jp

過去の内容と今回対応する内容を混ぜたものが以下の感じです。
内容は、すべてのレベルを開いて、すべてのActorの「ActorHiddenInGame」をtrueにするというもの。

MyBlueprintFunctionLibrary.h

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"

#include "MyBlueprintFunctionLibrary.generated.h"

/**
 * 
 */
UCLASS(BlueprintType)
class PROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
    
public:
    UFUNCTION(BlueprintCallable)
    static void SetAllHiddenForAllLevels();

    UFUNCTION(BlueprintCallable)
    static void OpenLevelForEditor(const FAssetData AssetData);

    UFUNCTION(BlueprintCallable)
    static void SaveCurrentLevel();
};



MyBlueprintFunctionLibrary.cpp

#include "MyBlueprintFunctionLibrary.h"
#include "FileHelpers.h"
#include "AssetRegistryModule.h"
#include "Misc/ScopedSlowTask.h"
#include "Kismet/GameplayStatics.h"
#include "Editor.h"


void UMyBlueprintFunctionLibrary::SetAllHiddenForAllLevels()
{
    FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
    IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

    TArray<FAssetData> AssetDatas;
    AssetRegistry.GetAssetsByClass(UWorld::StaticClass()->GetFName(), AssetDatas);

    FScopedSlowTask LevelTask(AssetDatas.Num());
    //プログレスバー表示
    LevelTask.MakeDialog(true);

    for (int i = 0; i < AssetDatas.Num(); i++)
    {

        //キャンセル処理
        if (LevelTask.ShouldCancel())
        {
            break;
        }

        //プログレスバー更新
        LevelTask.EnterProgressFrame(1, FText::FromString(AssetDatas[i].GetFullName()));

        OpenLevelForEditor(AssetDatas[i]);

        if (!GEditor)
        {
            continue;
        }

        TArray<AActor*> Actors;
        UGameplayStatics::GetAllActorsOfClass(GEditor->GetEditorWorldContext(false).World(),AActor::StaticClass(), Actors);

        for (AActor* Actor : Actors)
        {
            Actor->SetActorHiddenInGame(true);
        }

        SaveCurrentLevel();
    }
}

void UMyBlueprintFunctionLibrary::OpenLevelForEditor(const FAssetData AssetData)
{
    const FString FileToOpen = FPackageName::LongPackageNameToFilename(AssetData.PackageName.ToString(), FPackageName::GetMapPackageExtension());
    const bool bLoadAsTemplate = false;
    const bool bShowProgress = false;
    FEditorFileUtils::LoadMap(FileToOpen, bLoadAsTemplate, bShowProgress);
}

void UMyBlueprintFunctionLibrary::SaveCurrentLevel()
{
    FEditorFileUtils::SaveCurrentLevel();
}



また、プロジェクトの依存関係に以下を追加します。(.Build.cs)

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



これの「UMyBlueprintFunctionLibrary::SetAllHiddenForAllLevels()」をEditorUtilityWidgetなどで呼び出しましょう。


実行!!!!



という具合で、すべてのレベルを処理する際にプログレスバーを表示して、一つ毎に表示されている文字列を処理中のレベル名にできました。
コードのコメントにも書いてますが、キャンセル用の処理を書くことで、キャンセルボタン押したときの挙動を作成できます。
今回の対応でもキャンセルは有効です。


思ったこと

まぁググればすぐに出るような内容ですが、エンジンから該当の処理見つけt改めてちゃんとやってみましたっていう感じです。
かなり長い処理には、途中でキャンセルしたりできるのは結構有効だと思われます。



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


UE4 シーケンサーを一時停止する

シーケンサーを使っていて一時停止で困ることがあります。
タップ待ちなどでシーケンサーの進行は一時停止したいが、キャラのモーションは継続させたい。
もしくはシーケンサー関係なくゲーム自体を一時停止させることでシーケンサーもキャラも、ゲーム内すべてを停止させたい。
そんな感じで使い分けるならどんな感じでやるのかなとふと思ったので対応してみる。




目標・要件


シーケンサーの一時停止を2種類対応する。
・キャラのモーションは継続させるがシーケンサーの進行は停止させる。
・キャラのモーションもシーケンサーも、ゲームの進行自体を停止させる。


環境

・UE 4.27.0 (Gitのコミット:99b6e203a15d04fc7bbbf554c421a985c1ccb8f1 をビルド)
・VisualStudio 2019 16.11.2
・サードパーソンのテンプレートを元にします。


やってみる

作戦としては
一時停止系に関しては調べてみると
シーケンサーを一時停止させる
→「UMovieSceneSequencePlayer::Pause」を利用する。

・ゲームの進行自体を停止させる
→「UGameplayStatics::SetGamePaused」を利用する。

とするのがよさそうです。

シーケンサー一時停止中にモーションを継続させる。
→AnimationTrackを使うと一時停止してしまうので、AnimationBlueprintで処理させる。

という対応を検討してみます。


準備

まずはテスト用のシーケンサーを作成します。
私は以下のような感じで作成しました。

f:id:naoxgames:20220211000724p:plain
内容としては
・サードパーソンのプロジェクトにある「ThirdPersonCharacter」をSpawnableで配置
・「ThirdPersonCharacter」の開始と終わりに適当な座標を設定し、移動させる
・「CameraActor」をLevelに配置し、Possessableで設定(Spawnableでも問題なし)
また、名前は「LS_Pause_Test」としています。

このシーケンサーを再生すると、立ちモーションのまま移動をするだけのものになっているはずです。

作成したらレベルに配置して、「自動プレイ」「ループ」を以下のように設定しておきましょう、確認が楽になります。
f:id:naoxgames:20220211001415p:plain
これを元にこの先の対応を行っていきます。


シーケンサーを一時停止させる

特に凝ったことはせず、あっさり対応します。
イベントトラックで以下のように設定します。
f:id:naoxgames:20220210235621p:plain
f:id:naoxgames:20220210235648p:plain
「LevelSequenceDirector」(イベントトラックの中身設定するところ)で「LevelSequencePlayer」である「Player」を呼び出し、「Pause」を呼ぶだけです。
タイミングは任意に設定してください。

これだけでは一時停止して何もできなくなるだけなので、再開させる処理も対応します。

ここではレベルブループリントに書くことにします。
レベルに配置した「LS_Pause_Test」を選択した状態でレベルブループリントを開き、リファレンスを作成でアクセスできるようにします。
f:id:naoxgames:20220211001709p:plain
そして以下のようにします。
f:id:naoxgames:20220211001921p:plain
この例では「1」キーを押したときにPlayをするようになっており、一時停止時は再開をするようになります。

ここまでの対応で実行をすると、
シーケンサーが途中で止まる
・キャラは立ちモーションを継続している
・「1」キーを押すとシーケンサーが再開する
となります。


ゲームの進行自体を停止させる

次にゲームの進行を停止、つまりキャラのモーションすらも止めてなにも動かない状態にします。
f:id:naoxgames:20220211002312p:plain
これを呼べば終わりです。
ですがこれを呼ぶと、復帰することができなくなるので復帰処理を書きたくなります。
例えば以下のようにします。(レベルブループリントに書いてます)
f:id:naoxgames:20220211003341p:plain
このようにすると、
・「2」キーを押すと一時停止
・「3」キーを押すと再開

と思いきや、このままではうまくいきません。(試してみてください。)

調べてみると、Widgetなどでしかイベントを取得できなくなるようです。
ですが入力処理に設定をすることで呼ばれるようにすることが可能です。

インプットイベントの以下の詳細から「Execute when paused」にチェックを入れます
f:id:naoxgames:20220211003843p:plain
これによって呼ばれるようになりました。(試してみてください。)

例として「2」「3」キーに処理を割り当てましたが、このままにしておきましょう。


ここまでの対応で実行をすると、
シーケンサーが途中で止まる
・キャラは立ちモーションを継続している
・「1」キーを押すとシーケンサーが再開する
・「2」キーを押すとゲームが一時停止して、キャラのモーションも停止する
・「3」キーを押すとゲームが再開する
となります。


アレ?できたんじゃね?


シーケンサー一時停止中に任意のモーションを継続させる

とりあえず要件満たしましたが、現状モーションは立ちモーション一択です。
かといってアニメーショントラックを使ってモーションをさせると、シーケンサー一時停止時にそのタイミングのセクションの位置でモーションが止まってしまいます。
なので任意のモーションに変更できる足がかりを考えてみます

そもそもシーケンサー一時停止を使うときは、おそらくタップ待ちなどの処理になると思われます。
となると、基本的にはループモーション的なものが設定されるのでしょう!という前提で考えます。

作戦としては、
・AnimationBlueprintに変数を追加
・その値に応じてモーションを変化させる
・その値はシーケンサーから設定できるようにする
という感じで行こうと思います。

まずはAnimationBlueprintについて、以下のようにしてみます。
f:id:naoxgames:20220211005526p:plain
こちらはサードパーソンのプロジェクトにある「ThirdPerson_AnimBP」を編集しています。
「IsOverride」というboolの変数を追加する必要があります。
「IsOverride」は以下のように「シネマティクスに公開」にチェックを入れましょう、シーケンサーでいじれるようになります。
f:id:naoxgames:20220211005922p:plain
C++では以下のようにします。

UPROPERTY(Interp, BlueprintReadWrite)
    bool IsOverride;


この処理で、「IsOverride」が「True」だったら「ThirdPersonRun」モーションが流れる ということになります。
(「ThirdPersonRun」はサードパーソンのプロジェクトにあるマネキンの走るモーション)


次にシーケンサーでAnimationBlueprintの値を設定します。

以下のように対応します。
・キャラのSkeletalMeshComponentを設定します。(ここでは「CharacterMesh0」) f:id:naoxgames:20220211010313p:plain

・SkeletalMeshComponentからアニメーションインスタンスを設定します。(ここでは「ThirdPersonAnimBP」)
f:id:naoxgames:20220211010501p:plain

・先ほど追加した「ThirdPersonAnimBP」の「IsOverride」の変数を探し、設定します。
f:id:naoxgames:20220211010750p:plain
以下のように「IsOverride」を「False」にしたり「True」にしたりする
f:id:naoxgames:20220211011223p:plain


これによりモーションは
シーケンサーの特定タイミングで走りモーションになったり、立ちモーションになったりする
シーケンサーから指定できる
シーケンサー一時停止の影響を受けない
ということになります。

※ここではboolでやりましたが、使い勝手悪すぎるので、実際はEnumとかにしてC++側で判定したモーションをAnimBPに渡して流すとか、そういう感じにするのが良いと思います。


思ったこと

物自体はわりと簡単でしたね。
一時停止したーーいって言われて、一時停止だけならすぐだけと、モーションとかAnimationBlueprintとかも絡んでくるからさほどあっさりではないよねっていう。
今回調べるまで「Execute when paused」知らなかったし、わりと普通に一時停止系やると困ることがちょこちょこありそうだなと思いました。




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


UE4 各Level内のBP設定値を一括で修正

例えばとあるアクターのメンバ変数名を変更したくなったとします。
ですがC++で変更するとその変数に設定されていた値はリセットされてデフォルトの値になります。
思いつく対応策は一時的に変更前と変更後の変数を共存させて、変更前の値を変更後の変数に代入する、そして変更前の変数を削除 みたいなことをすればできそうです。
ですが、レベルに直接配置され、各レベルで違った設定をしていた場合、どうやってすべてを適切に変更後の変数に設定できるのでしょう。
すべてのレベルを開いて設定しなおす!が通ればそれがいいかもしれませんが、実際はそうもいきません。
今回はそんな時にどうしたらいいのか考えてみました。




目標・要件

各レベルに配置されたBPの設定値を変更する。
この変更処理はエディターから行う。


環境

・UE 4.27.0 (Gitのコミット:99b6e203a15d04fc7bbbf554c421a985c1ccb8f1 をビルド)
・VisualStudio 2019 16.11.2


やってみる

作戦としては、
・何かしらの手段で、エディターから任意のレベルを開く
・エディターからレベルに配置されているアクターを取得して変更を加える
・レベルを保存する
・レベルの数分繰り返す
という感じです。

ですがすべてはエディター上という縛りがあり、通常思いつく手段が使えない可能性があります。
頑張ります。

今回の検証は以下の内容を行います。
・複数あるレベルに配置された「AActor」の「ActorHiddenInGame」を「True」にする。
これができたら要件を満たしたとします。
また、対応は「エディターユーティリティウィジェット」で行おうと思います。


エディターユーティリティウィジェットを作成

今回はエディターからの処理を簡単に行いたいのでエディターユーティリティウィジェットを使います。
コンテンツブラウザを右クリックし、「エディターユーティリティウィジェット」を選択します。
f:id:naoxgames:20220214032858p:plain
こんな感じ
f:id:naoxgames:20220214033025p:plain

エディターユーティリティウィジェットを開き、ボタンを配置します。
f:id:naoxgames:20220214033106p:plain

ボタンを選択し、詳細から「イベント」の「onClicked」のところにある「+」をクリックし、クリックイベントを作成します。
f:id:naoxgames:20220214033228p:plain

イベントグラフに以下のようなイベントが作成されます。
f:id:naoxgames:20220214033315p:plain

コンテンツブラウザから作成したエディターユーティリティウィジェットを右クリックし、「エディターユーティリティウィジェット」をクリックします。
f:id:naoxgames:20220214033359p:plain
すると以下のように先ほど設定したボタンが表示されます。
f:id:naoxgames:20220214033445p:plain

このボタンを押すことで、先ほどのボタンのイベントが呼ばれるようになります。


何かしらの手段で、エディターから任意のレベルを開く

とりあえず、パッと思いつくレベル開く系の処理は使えませんでした!!!!
OpenLevel的なやつです。

他の道がないか探したところ、エディターのメニューの「ファイル」に「レベルを開く」があることに気づく。
f:id:naoxgames:20220214033637p:plain
この処理をパクろう!!!!

ということで、エンジンからこの処理を探します。

まずはUIの拡張点を表示します。
「編集」→「エディタの環境設定」→「拡張点」で検索→「UIの拡張点の表示」にチェックを入れます。
f:id:naoxgames:20220214033745p:plain
エンジンの再起動をしてください。

再度「レベルを開く」を確認すると、緑色の文字で「OpenLevel」と表示されているのがわかります。
f:id:naoxgames:20220214033955p:plain
※私は始めから設定していたので前のスクショと見た目変わってません。

このキーワードをエンジンコードから探します。
「"Open Level"」と検索したところ以下がヒットしました。
FileHelpers.cpp

OpenAssetDialogConfig.DialogTitleOverride = LOCTEXT("OpenLevelDialogTitle", "Open Level");


この処理付近を調査すると、この後のダイアログを開く処理の引数に、レベル選択時のコールバックを設定しています。

ContentBrowserModule.Get().CreateOpenAssetDialog(OpenAssetDialogConfig,
                                                     FOnAssetsChosenForOpen::CreateStatic(&FLocal::OnLevelsSelected, OnLevelsChosen),
                                                     FOnAssetDialogCancelled::CreateStatic(&FLocal::OnDialogCancelled, OnLevelPickingCancelled));

この「OnLevelsChosen」はこの処理が書かれている関数の引数に入ってきたものなので、この処理あたりにブレイクを張って実際に「レベルを開く」を実行してみます。

呼び出し履歴から追うと、
FEditorFileUtils::LoadMap()で行っていることがわかります。

さらに追っていくと
FEditorFileUtils::LoadMap(引数あり版)で処理している様子。

この処理を同じように使えば自分でもエディター上でレベル開けるのでは?ということで試してみます。

以下のようなクラスを作成します。(今回はBlueprintFunctionLibraryで行います。)

MyBlueprintFunctionLibrary.h

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"

#include "MyBlueprintFunctionLibrary.generated.h"

/**
 * 
 */
UCLASS(BlueprintType)
class PROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
    
public:
    UFUNCTION(BlueprintCallable)
    static void OpenLevelForEditor(const FAssetData AssetData);
};



MyBlueprintFunctionLibrary.cpp

#include "MyBlueprintFunctionLibrary.h"
#include "FileHelpers.h"

void UMyBlueprintFunctionLibrary::OpenLevelForEditor(const FAssetData AssetData)
{
    const FString FileToOpen = FPackageName::LongPackageNameToFilename(AssetData.PackageName.ToString(), FPackageName::GetMapPackageExtension());
    const bool bLoadAsTemplate = false;
    const bool bShowProgress = true;
    FEditorFileUtils::LoadMap(FileToOpen, bLoadAsTemplate, bShowProgress);
}



また、プロジェクトの依存関係に以下を追加します。(.Build.cs)

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


これでエディター上でレベルを開く準備ができました。
エディターユーティリティウィジェットのイベントで以下のように処理を作成します。
f:id:naoxgames:20220214035325p:plain
内容としては、AssetRegistryを利用してレベルのアセット(Worldというクラスらしい)を取得、それらを上記で作成した処理を使いレベルを開きます。
PrintStringは画面に文字は表示されませんが、アウトプットログで確認できるので使用しています。

これによってエディターでレベルを開けるようになりました。


エディターからレベルに配置されているアクターを取得して変更を加える

GetAllActorsOfClassを使いたい!!!
そうするとエディター上でワールドを取得する処理が必要になります。
いろいろ調べてみると、取得する方法がありそうです。
「編集」→「プラグイン」から以下のプラグインを有効にします。
f:id:naoxgames:20220214040050p:plain
f:id:naoxgames:20220214035951p:plain

すると「GetEditorWorld」が使えるようになります。
f:id:naoxgames:20220214040113p:plain

先ほどエディターユーティリティウィジェットで作成したレベルを開く処理の続きで以下のように処理を追加します。
f:id:naoxgames:20220214040511p:plain

これにより、各レベルを開き、Actorを取得し、変更を加えることができました。


レベルを保存する

先ほどのレベルを開く方法があったFileHelpers.cppで「Save」とかで検索すると「SaveCurrentLevel」というそれっぽい関数を見つけました。
実際にメニューの「ファイル」→「現在のレベルを保存」を選択するとこの処理を通ることもわかります。

なので、これを使おう!
ということで、先ほどの処理に追加します。
f:id:naoxgames:20220214041133p:plain
そのままノードでいけるので、これでOKです。


実行!!!!

実行!!!!
すると、各レベルを開き、アクターを取得して編集し、すべてのアクター編集後にレベルを保存、という流れができました。
動画やスクショで示すものがありませんが、お試しいただけるとわかります。

今回は「ActorHiddenInGame」というパラメーターを対象にしましたが、これによってとある値を判定して、それに応じたパラメーターを別の変数に与える(変換する)ようなことにも使えます。
また、今回はすべてのレベルを開いてますが、名前やフォルダとかである程度判別できるならそうしたほうがよいかと思います。なんだかんだ全レベル開くのはプロジェクトによっては大変かと。。。
あとは保存処理、今回のは変更対象のアクターがいなくても保存する感じになってるので、不要ならスキップしましょう。
という感じかなと。


思ったこと

これがあればレベルの中を特定の条件で一括変更できると思われるので、非常に有用かと。
今回は「一括変更」をメインにしましたが、チェック機能としても利用できるかと思われます。
とはいえ対象のレベルをすべて開くので、場合によってはめちゃくちゃ重いと思われるので、実際実用的なのかはその場合次第・・・・?



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


UE4 AnimBPを使いまわす方法(2)~Animation Layerを使う~

以前に書いた「AnimBPを使いまわす方法」を見た友人から「Animation Layerを使うことで各子AnimBP毎のアニムグラフが設定できるよ!」という話をいただいたので、試してみようと思います。

前回の記事は以下
naoxgames.hatenablog.jp
これの続きで今回は対応していきます。




目標・要件


AnimBP継承先毎に固有のアニムグラフを設定できるようにする。

環境

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


調べる

よくわからないので調べます。
Animation Blueprint Linking を使用する | Unreal Engine ドキュメント
これかしら?
「実験的機能」と表示されていてドキドキが隠せない。


やってみる

作戦としては、
継承元のAnimBPの変数にリンクする「AnimInstance」のクラスを指定できるようにして、継承先で必要な「AnimInstance」クラスを設定できるようにする。
あとはおおよそドキュメントに沿った対応をしてみる。
今回は簡単に、それぞれの固有のAnimBPの中でそれぞれのモーションをさせて、それをメインのAnimBPに反映させるようにしてみます。
なので、
・AnimationLayerには各キャラのモーションをさせる処理をする
・メインのAnimBPにはAnimationLayer+視線制御をさせる
となり、
AnimationLayerで設定したモーションをしつつ視線制御ができていたら成功!ということで。


Animation Layer Interfaceを作る

ドキュメントに倣って作ってみます。
コンテンツブラウザから右クリックをし、「アニメーション」→「アニメーションレイヤーインターフェーズ」を選択します。
f:id:naoxgames:20220211025337p:plain
ここでは「MyAnimLayerInterface」とします。

作成された「MyAnimLayerInterface」を開き、アニメーションレイヤー名を変更します。
ここでは「UniqueMotion」とします。
また、「入力」の「+」をクリックしてポーズの入力を追加します。
f:id:naoxgames:20220211025711p:plain


Animation Layer用AnimBPを作る

各キャラ用にAnimLayerのアニムグラフを書くAnimBPを作成します。
コンテンツブラウザから右クリックで「アニメーション」→「アニメーションBP」を選択します。
f:id:naoxgames:20220211030426p:plain
以下のような画面になるので、今回作ろうとしてるキャラのスケルトンを選択してください。
f:id:naoxgames:20220211030620p:plain
ここでは「ABP_UniqueMannequin」とします。

作成したAnimBPを開き、「クラス設定」を選択して詳細から「実装インターフェース」に「MyAnimLayerInterface」を追加します。
f:id:naoxgames:20220211030944p:plain

すると「アニメーションレイヤー」の項目に「UniqueMotion」が増えています。
f:id:naoxgames:20220211031022p:plain

UniqueMotionを開き、アニムグラフが開かれるのでそこでモーションをアウトプットへ繋げましょう。
f:id:naoxgames:20220211031154p:plain
私は適当にジャンプモーション設定しました。


このAnimBPを作成する手順をキャラ数分繰り返します。
今回作成したAnimBPは
「ABP_UniqueAnimal01」(鹿用)
「ABP_UniqueAnimal02」(豚用)
とします。


メインのAnimBPに設定する

メインのAnimBPで使えるようにします。
まずキャラのAnimBPの基底となっているAnimBPを開きます。ここでは「AnimBP_Base」と名付けています。
「クラス設定」から詳細で「実装インターフェース」に「MyAnimLayerInterface」を追加します。
f:id:naoxgames:20220211033524p:plain

先ほどと同じくアニメーションレイヤーに項目が追加されます。
f:id:naoxgames:20220211033722p:plain

アニムグラフで右クリック→「リンクされたアニメーションレイヤー」を選択
f:id:naoxgames:20220211033825p:plain

以下のようなノードができるので、詳細から「Layer」を「UniqueMotion」にします。
f:id:naoxgames:20220211033935p:plain

これによって、リンクされたAnimBPの「UniqueMotion」レイヤーを使えるようになります。

次にリンクするAnimInstanceのクラス指定をメインのAnimBPに追加しておきます。
以下のような変数をAnimBPに追加してください。
型はAnimInstanceのクラス参照型です。
f:id:naoxgames:20220211034239p:plain
ひとまずデフォルト値は「ABP_UniqueMannequin」にしておきます。


次に各キャラのメインのAnimBPを開き、「クラスのデフォルト」を選択して詳細から上の手順で追加した「LinkAnimInstClass」を設定します。
f:id:naoxgames:20220211034536p:plain
各キャラ用のAnimLayer用AnimBPを指定しましょう。


最後に各キャラのBPで以下のような処理を行います。
f:id:naoxgames:20220211035507p:plain
主に「AnimLayer用」の部分の処理になります。
SkeletalMeshComponentに先ほどのAnimBPで設定した「AnimLayer用AnimBP」のクラス参照を渡してリンクさせます。


実行!!!!!

以下のようになりました!


基底AnimBPで行っている共通処理を全キャラに反映しつつ、各キャラ毎の処理をAnimLayerのアニムグラフを使って行った感じです。
便利!!!


思ったこと

Mさん!これであってますか!?
実験的機能って書いてあったけど大丈夫なんですか!?
ひとまず要件はクリアして、動作も確認できてるのでよさそうだとは思います。




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