こんにちはトイロジックのプログラマーGです。Unreal Engine は標準で多数の機能が備わっていますが、ある程度開発を続けているとプロジェクト内で「こういう機能が欲しい」という要望が出てきます。
本記事ではレベル班から実際に出た要望とそれに対して行ったエディタ拡張についてお話します。
なおUnreal Engine
のC++プロジェクトを前提としています。(エンジンバージョンは 4.27.2
です)よろしくお願いいたします。
レベル班からの要望
レベルエディタでの背景制作が進んできたころ、レベルデザイン関係者(レベル班と呼んでいました)との間で次のような話題が出ました。
- レベル内で 不必要な
ShadowCast
をしているメッシュを探しやすくしたい - レベル内で
Unwalkable
に設定し忘れたメッシュが無いか探したい
その頃のレベル上には数百の StaticMesh
が存在していて、ひとつひとつを確認していくのが大変な状況でした。プログラマとしても無駄な設定がされているオブジェクトをすぐに見つけられる仕組みは欲しいと感じていました。
エディタ拡張の方針
そこで、レベルエディタの Viewport
で該当する Mesh
を強調表示できれば視覚的にわかりやすいのでは、と考えエディタの拡張に取り掛かりました。
強調表示を行う方法についてはDrawDebugMesh
を使用することにしました。
エディタでの編集中にコンソールコマンドでDrawDebugMesh
を呼び出す形になります。
DrawDebugMeshを
利用した理由は主に以下の点です。
- 比較的単純なコード追加で
Mesh
形状を表示できる - レベルエディタの編集中でも実行できる
- デバッグ表示用のメッシュやマテリアルなどのアセットを用意せずに済ませたかった
- もともとデバッグ用の機能なので、レベル側への影響がない
- 1~2時間で実装を済ませたかった
実装サンプル
以下がサンプルコードとなります(わかりやすくするため不正値のチェック等は省いています)。
コマンド登録部分
MyProjectActor.h
UCLASS()
class AMyProjectActor : public AActor
{
GENERATED_BODY()
public:
AMyProjectActor();
UFUNCTION()
void Execute_ViewUnwalkable(UWorld* world);
UFUNCTION()
void Execute_ViewMeshesCastShadow(UWorld* world);
};
MyProjectActor.cpp
AMyProjectActor::AMyProjectActor()
{
if (!IsRunningCommandlet())
{
IConsoleManager::Get().RegisterConsoleCommand
(TEXT("ViewMeshesUnwalkable")
, TEXT("View unwalkable staticMeshes")
, FConsoleCommandWithWorldDelegate::CreateUFunction(this, TEXT("Execute_ViewUnwalkable"))
, ECVF_Default
);
IConsoleManager::Get().RegisterConsoleCommand
(TEXT("ViewMeshesCastShadow")
, TEXT("View staticMeshes casting shadow")
, FConsoleCommandWithWorldDelegate::CreateUFunction(this, TEXT("Execute_ViewMeshesCastShadow"))
, ECVF_Default
);
}
}
コマンドで実行される関数
MyProjectActor.cpp
template<typename judgedrawfunc="">
void DrawDebugStaticMesh(float lifeTime, FColor drawColor, UWorld* world, JudgeDrawFunc judgeFunc)
{
if (!world->IsEditorWorld())
return;
for (TObjectIterator<ustaticmeshcomponent> itr; itr; ++itr)
{
UStaticMeshComponent* smc = *itr;
if (AActor* ownerActor = smc->GetOwner())
{
#if WITH_EDITORONLY_DATA
if (ownerActor->IsHiddenEd())
continue;
#endif // WITH_EDITORONLY_DATA
if (ownerActor->HasAnyFlags(RF_ClassDefaultObject))
continue;
}
if(judgeFunc(smc))
{
UStaticMesh* staticMesh = smc->GetStaticMesh();
if (IsValid(staticMesh))
{
FStaticMeshLODResources& lodResource = staticMesh->GetRenderData()->LODResources[0];
FPositionVertexBuffer& vertexBuffer = lodResource.VertexBuffers.PositionVertexBuffer;
FIndexArrayView indicesView = lodResource.IndexBuffer.GetArrayView();
const FTransform& componentTransform = smc->GetComponentTransform();
TArray<fvector> Verts;
for (uint32 Index = 0; Index < vertexBuffer.GetNumVertices(); Index++)
{
const FVector vertexLocation = componentTransform.TransformPosition(vertexBuffer.VertexPosition(Index));
Verts.Add(vertexLocation);
}
TArray<int32> indices;
for (int32 i = 0; i < indicesView.Num(); ++i)
{
indices.Add(static_cast<int32>(indicesView[i]));
}
DrawDebugMesh(world, Verts, indices, drawColor, false, lifeTime);
}
}
}
}
// Unwalkable に設定されている Mesh を強調表示する
void AMyProjectActor::Execute_ViewUnwalkable(UWorld* world)
{
auto _judgeDrawFunc = [](UMeshComponent* MC) // <- Unwalkable かを判定する関数
{
auto& walkableSlopOverride = MC->GetWalkableSlopeOverride();
return (walkableSlopOverride.GetWalkableSlopeBehavior() == WalkableSlope_Unwalkable);
};
const float lifeTime = 10.f;
const FColor drawColor = FColor::Green;
DrawDebugStaticMesh(lifeTime, drawColor, world, _judgeDrawFunc);
}
// CastShadow が On になっている Mesh を強調表示する
void AMyProjectActor::Execute_ViewMeshesCastShadow(UWorld* world)
{
auto _judgeDrawFunc = [](UMeshComponent* MC) // <- CastShadow フラグを判定する関数
{
return (MC->CastShadow != 0);
};
const float lifeTime = 10.f;
const FColor drawColor = FColor::Orange;
DrawDebugStaticMesh(lifeTime, drawColor, world, _judgeDrawFunc);
}
</int32></int32></fvector></ustaticmeshcomponent></typename>
※補足
Viewport
のViewModeの追加も検討しましたが、エンジンコードの調整やシェーダの追加などが必要そうだったため時間の関係で断念しました。こちらは別の機会に挑戦したいです。
動作させたところ
実際にコマンドを呼び出した様子がこちらです。エディタでの編集中に、登録した「ViewMeshesCastShadow」
コマンドを実行しているところです。
影をキャストする設定になっている画面中央のActorが強調されています。
こちらは、エディタでの編集中に、登録した 「ViewMeshesUnwalkable」
コマンドを実行しているところです。
画面中央の一部のStatickMeshActorだけが Unwalkable
に設定されており、それが強調表示されています。
最後に
本記事では DrawDebugMesh
を使っての特定の条件のオブジェクトをで可視化するエディタ拡張についてご紹介しました。
上記のサンプルはかなり単純なものですが、少しのコード変更で以下のようにもできます。
- さらに条件文とコマンドを追加する
- コンソールコマンドの引数で動作を変更できるようにする
- SkeletalMeshにも適用する
- EditorUtilityWidget から細かいパラメータを渡して実行する
…などなど。
描画に関しては DrawDebugMesh以外を利用した方法もあると思います。エディタの機能拡張は色々なアプローチがありますので、作っているゲームと作業者の要求に合わせて実装方法を選択してみてください。
小ネタではございましたが、どなたかの参考になれば幸いです。ここまで読んでいただきありがとうございました!