この記事はUnreal Engine 4 (UE4) その3 Advent Calendar 2021の25日目の記事です。
アドベントカレンダーのとりですが、内容は大した事はありません。
泥臭く調査したという話になります。
今回は、FoliageTypeについての内容になります。
やりたい事
ランタイム動作時に、ブループリントから、レベルに配置されているフォリッジの情報(例えば、ScaleXやZOffsetやOverrideMaterials)にアクセスを行う事です。
結構、エンジンのソースコードを探し周り、自分なりの解決策を見つけた為、メモも兼ねて記事に残しておきたいと思います。
動作環境
UnrealEngie 4.27.2
フォリッジの種類
フォリッジには、以下の4種類があります。
- スタティックメッシュフォリッジ
- アクタフォリッジ
- ランドスケープグラスタイプ
- スタティックメッシュ
今回は、「スタティックメッシュフォリッジ」と「スタティックメッシュ」についての話になり、以降のフォリッジとは、この二つの事を指します。
スタティックメッシュフォリッジ
事前に作成可能なフォリッジタイプになります。別のレベルでも設定を使いまわす事が出来ます。
注意点があり、レベルのフォリッジタイプの設定とアセットのフォリッジタイプの設定が同期しており、レベルのフォリッジタイプを変更してレベルを保存してしまうと、フォリッジタイプのアセットにも変更が反映されてしまいます。
スタティックメッシュ
スタティックメッシュをフォリッジの追加で追加を行うと、このタイプのフォリッジタイプになります。
機能的なところは、スタティックメッシュフォリッジと同じです。
マウスオーバーさせると下のように、ソースアセットタイプが「スタティックメッシュ」と「フォリッジタイプ」と違っており、どのタイプのアセットか確認する事ができます。
このフォリッジタイプは、それぞれ別のアセットして扱われ、スタティックメッシュフォリッジを追加していて、同じ名前のスタティックメッシュをペイントするフォリッジタイプとして登録する事が可能です。
また、アセットのフォリッジタイプが別になっていれば、いくつでも同じスタティックメッシュのフォリッジタイプを登録する事ができます。
ブループリントから見たフォリッジタイプの特徴
今回、ブループリントからアクセスする際の調査で分かった特徴です。
InstancedFoliageActorで管理されている
ブループリントからフォリッジのインスタンスにアクセスする場合は、InstancedFoliageActorを探す必要があります。
InstancedFoliageActorは、新規レベルを作成した時点でインスタンスにアクセス可能な為、レベル作成時に生成されるようです。
FoliageInstancedStaticMeshComponentを継承している
フォリッジの実体は、FoliageInstancedStaticMeshComponentであり、StaticMeshComponentが持っているような、マテリアルやキャストシャドウなどの設定を持っています。例えば、ブループリントからフォリッジタイプのマテリアルを動的に変更する事も可能です。
また、フォリッジの設定として、ComponentClassを設定できるようになっており、FoliageInstancedStaticMeshComponentを継承していれば、自作のブループリントに差し替える事も可能です。
ブループリントから簡単にアクセスするなら、下のノードの組み方で、FoliageInstancedStaticMeshComponentにアクセスする事が出来ます。
Foliageの生成
新規にレベルを開き、フォリッジが配置されていない状態では、まだ、InstancedFoliageActorはFoliageInstancedStaticMeshComponentを持っていません。
一度でも、フォリッジをペイントする事で、FoliageInstancedStaticMeshComponent生成され、フォリッジをレベルから完全に消してもFoliageInstancedStaticMeshComponentは残ります。
フォリッジのペイントから登録されているフォリッジタイプを削除すると、該当するフォリッジタイプのFoliageInstancedStaticMeshComponentも削除されます。
FoliageInstancedStaticMeshComponentの持ち方については、例えば、下のフォリッジが登録されていた場合に、
InstancedFoliageActor の FoliageInstancedStaticMeshComponent を書き出すと、下のログがように出力されました。
つまり、以下のようにInstancedFoliageActorにFoliageInstancedStaticMeshComponentがぶらさがっているようです。
InstancedFoliageActor
┣FoliageInstancedStaticMeshComponent_0
┣FoliageInstancedStaticMeshComponent_1
┗FoliageInstancedStaticMeshComponent_2
ログにFoliageInstancedStaticMeshComponent_1が無いのは、調査の過程で一度、登録したフォリッジタイプを削除し、登録し直した為です。
コンポーネント名は、削除された番号もカウントして名前が付与されていくようです。
フォリッジに細工する
ここからが、この記事の本題です。
静的なフォリッジに、動的な変化を与えたい場合がありました。
FoliageInstancedStaticMeshComponentを持っている為、キャストシャドウやマテリアルの変更は簡単に行え、ゲームの実行中にも変化を与える事は可能です。
また、FoliageInstancedStaticMeshComponentを継承するクラスをブループリントで用意し、ComponentClassに設定すれば、動的な処理もさせやすそうです。
一見、なんでも出来そうに思えますが、FoliageInstancedStaticMeshComponentは、自身が使用しているフォリッジタイプの情報を持っておらず、フォリッジタイプのへのアクセスは出来ませんでした。
フォリッジタイプが持っている情報
詳細は、公式のドキュメントをご覧ください。
ほとんどの情報は、フォリッジをペイントする為の情報で、ランタイム時にアクセスする必要はないでしょう。今回は、フォリッジタイプが持っているメッシュと「OverrideMaterials」にアクセスを参照する必要がありました。
フォリッジタイプに関するクラスは非ランタイム用
InstancedFoliageActorは、C++では、AInstancedFoliageActorというクラスになります。
同じく、FoliageInstancedStaticMeshComponentは、UFoliageInstancedStaticMeshComponentクラスになり、
FoliageTypeは、UFoliageTypeになります。
フォリッジを管理するクラスのAInstancedFoliageActorは、Actorではありますが、実装されているメソッド関数のほとんどは、WITH_EDITORで括られており、非ランタイム時に使用する事を想定されています。
また、参照したいUFoliageTypeクラスもメソッドを見る限り、WITH_EDITORで提供されているメソッドばかりで、エディットモードで使用する事が想定されているようです。
それでもフォリッジタイプを知りたい
最初に行きついたのが、AInstancedFoliageActor::FindFoliageTypeです。
これを実行するには、FFoliageInfoを持っている必要があり、UFoliageInstancedStaticMeshComponentには、FFoliageInfoが取れる情報が無く断念しました。
次に、見つけたのが、AInstancedFoliageActor::GetAllFoliageTypesForSourceになります。
こちらは、スタティックメッシュを引数に渡す事で、FoliageTypeを取れました!
これで解決かと思ったものの、GetAllFoliageTypesForSourceを実行した際に取得可能なフォリッジタイプは、スタティックメッシュの場合だけでした。これでは、動作に支障がでます。
更に調査を進めた結果、UFoliageTypeをIsAで判別している個所をエンジンのソースコードを見つけました。更に、IsAssetやUClassなども見分ける判断に使えるかもしれません!
下のコードは、IsA判定部分を抜粋しています。
#if WITH_EDITOR
void FFoliageInfo::CreateImplementation(const UFoliageType* FoliageType)
{
check(!Implementation.IsValid());
// Change Impl based on FoliageType param
Type = EFoliageImplType::Unknown;
if (FoliageType->IsA<UFoliageType_InstancedStaticMesh>())
{
Type = EFoliageImplType::StaticMesh;
Implementation.Reset(new FFoliageStaticMesh(nullptr));
}
else if (FoliageType->IsA<UFoliageType_Actor>())
{
Type = EFoliageImplType::Actor;
Implementation.Reset(new FFoliageActor());
}
check(Type != EFoliageImplType::Unknown);
}
メソッドの戻り値の結果は、以下の通りです。
StaticMesh | FoliageType | |
---|---|---|
IsAsset | false | true |
IsA | UFoliageType_InstancedStaticMesh | UFoliageType_InstancedStaticMesh |
UClass | FoliageType_InstancedStaticMesh | FoliageType_InstancedStaticMesh |
GetSource | 使用しているスタティックメッシュ | 使用しているスタティックメッシュ |
色々やったものの、「スタティックメッシュ」と「スタティックメッシュフォリッジ」の判別する決め手が得られませんでした。。。
それでもフォリッジタイプを知りたい(解決)
更に調べた結果、AInstancedFoliageActor::FoliageInfos に、AInstancedFoliageActorが持っているUFoliageTypeとFFoliageInfoが入っている事が分かりました。
また、FFoliageInfoが持っているコンポーネント名が、AInstancedFoliageActorが持っているUFoliageInstancedStaticMeshComponent の名前と一致している事も分かり、これで解決できそうです。
手順
- ブループリントから、AInstancedFoliageActorと探したいフォリッジタイプのUFoliageInstancedStaticMeshComponentのオブジェクト名をもらう
- AInstancedFoliageActorのFoliageInfosの中から、FFoliageInfoを取得する。FFoliageInfoのKeyにUFoliageType、ValueにFFoliageInfoが入っている
- FFoliageInfoから、UHierarchicalInstancedStaticMeshComponentを参照し、引数で貰ったFoliageInstancedStaticMeshComponentと同じ名前の物を探す
- 3 で見つかった場合は、そのKey(UFoliageType)を返す
下のコードは、AInstancedFoliageActorからUFoliageTypeを見つけるまでの流れです。
// InInteractiveFoliageActor : ブループリントからもらうAInstancedFoliageActor
// InComponentName : FoliageInstancedStaticMeshComponentのコンポーネント名
//
UFoliageType* GetFoliageType(AInstancedFoliageActor* InInteractiveFoliageActor, const FString& InComponentName)
{
for (auto& Pair : InInteractiveFoliageActor->FoliageInfos)
{
// KeyにUFoliageTypeが、ValueにFFoliageInfoが入っている
UFoliageType* foliageType = Pair.Key;
FFoliageInfo& foliageInfo = *Pair.Value;
// FFoliageInfoの持っているコンポーネントは、UHierarchicalInstancedStaticMeshComponentで取得可能
UHierarchicalInstancedStaticMeshComponent* component = foliageInfo.GetComponent();
if (component)
{
FString componentName;
component->GetName(componentName);
// コンポーネント名が一致しているコンポーネントを探す
if (componentName == InComponentName)
{
// Pair.Keyに入っているUFoliageTypeを返す
return foliageType;
}
}
}
return nullptr;
}
FoliageInfosは、WITH_EDITORONLY_DATAで括られていない為、ランタイムでも呼び出しが可能です。
参考
- 公式ドキュメント
- 使用アセット