Life, Education, Death

プログラミング以外でも思ったことをつらつらと書きたい

UE5.2 Procedural Content GenerationのノードをBlueprintで書くチュートリアル

PCGグラフのノードをBPで追加することができるよう話題が以下の動画にありました。これとソースコードをベースにやり方を調べて手順をまとめています。

Unreal Engine 5.2 Feature Showcase | GDC 2023 - YouTube

環境について

  • 2023/05/07 時点
  • UE5.2 Preview
  • Procedual Content Generation Framework version 0.1

となっています。今後バージョンアップにより以下の手順で実装できない可能性があります。

はじめに

PCGはUE5.2に追加される機能です。Blueprintのような画面で実装したルールに従ってマップを生成する機能です。これはエディタ中に使えるもので実行時に影響を及ぼすものではありません。 マップと書きましたが、Static Mesh以外にも特定のActorをSpawnするノードがあったので、好きなものを出せるはずです。

www.youtube.com

まずはUE5.2がインストールされている状態で、プラグイン一覧から、"Procedual Content Generation Framework"をインストールしましょう。 インストール後、自由にPCGを使ったマップ生成が楽しめます。

PCGプラグイン

Densityが必ず固定の値になるオリジナルのノードを作成する

PCGの基本的な使い方は動画内で説明されているので割愛します。

上記のGCDセッションや他のサンプルでもDensity filterを使ってポイントをフィルタする操作をしているものがあったので、 何かをフィルタするのにDensityを使うのが良いと思ったのでDensityを操作するノードを作成します。

まず、適当なPCGグラフを新規作成し、以下のようなグラフにします。 Surface SamplerノードでDキーを押して、水色のマークがついている状態にし、デバッグ描画を行っています。 レベルには適当なLandscapeが置いてある状態です。

ランドスケープの作り方については公式のチュートリアルが参考になります。 docs.unrealengine.com

PCGグラフ初期状態

このとき、レベルはこのようになっていて、適当なDensityを持った状態です。

PCGグラフ初期状態のレベル

ここから具体的にBlueprintを使ってノードを作成していきましょう。継承するクラスをPCGBlueprintElementを選択して新規Blueprintを作成します。

PCGBlueprintElementクラスにはいくつか関数が実装されていますが、必要なものだけをオーバーライドします。

PointLoopBodyをオーバーライドします。この関数は、1つのポイントを入力に、1つのポイントを返すロジックを実装できます。ここで実際のDensity操作ロジックを記述します。 といっても、、、今回はDensityを1に固定するロジックを実装したいので、単純にDensity以外のノードを繋ぎなおしただけです。

PointLoopBodyの中身

そして、ExecuteWithContextをオーバーライドします。この関数は、実際に入力から出力結果を返すまでを担当する関数です。簡単なロジックを実装する場合にはお約束のコードになるかもしれません。

処理の流れとしては

  1. 入力が配列なのでループで分解する
  2. PCGSpatialDataにキャストしてPointDataを取得する準備(Normal To Densityのコードを読む限りこの型でよいはず)
  3. Loop On Pointsを呼び、オーバーライドしたPoint Loop Bodyが呼ばれるようにする
  4. 出力結果をローカル変数ReturnValueに格納する
  5. 全てのループが終わったら、ローカル変数ReturnValueを戻り値として返す

PCGグラフ ExecuteWithContextの中身

Loop On Pointsの呼び出しが重要なところなので、C++側のコードを除くと

void UPCGBlueprintElement::LoopOnPoints(FPCGContext& InContext, const UPCGPointData* InData, UPCGPointData*& OutData, UPCGPointData* OptionalOutData) const
{
~~~
    FPCGAsync::AsyncPointProcessing(&InContext, InPoints.Num(), OutPoints, [this, &InContext, InData, OutData, &InPoints](int32 Index, FPCGPoint& OutPoint)
    {
        return PointLoopBody(InContext, InData, InPoints[Index], OutPoint, OutData->Metadata);
    });
}

となっていて、何やら非同期(FPCGAsync::AsyncPointProcessing)で処理を実行していそうなコードが見られます。この機能を使わないで実装することも可能ですが、単純にやってしまうとパフォーマンスが低いノードになってしまうと思われます。

ここまで実装したら、先ほど作成した自作ノードをコンテンツブラウザからPCGグラフにドロップしてください。新しく追加したノードをSurface Samplerの後ろに追加してPCGグラフは完成です。動作を確認するために、Surface Samplerのデバッグフラグをオフにして、自作ノードのデバッグをオンにするために再びDキーで切り替えてください。

PCGグラフ完成

Densityを全て1にしたので、全てのポイントが白くなりました。

PCGグラフ完成後のレベル

単純なコードでしたが、ノードを追加することができました。実際には追加のパラメータを外部から渡したりしながら、Densityを計算するロジックを追加することになるのでもう少し複雑なものになるはずですが、実装のとっかかりになるのではないでしょうか。