【UE5】サンドボックスの経営シミュレーションゲームを作る-1
先日、アセットコレクションPCG Japanese Gardensのリリースが無事に完了したこともあり、かねてより温めていた「日本庭園シミュレーションゲーム」の制作へ本格的に着手しました。
動画版
やり方
ただ、今回の最終的なゴールは特定のゲームを1つ完成させることだけではありません。目指すのは、サンドボックス系の経営シミュレーションゲーム(海外でいういわゆる「Tycoon(タイクーン)ゲーム」)のベースとなる、汎用的なフレームワークの構築です。
今回はその第一歩として、レベル(マップ)上に来園者となるNPCを登場させ、わちゃわちゃと園内を回遊してもらうための「超基本的かつ軽量なNPCのライフサイクル」をBlueprintで実装していきます。
1. 今回実装するNPCのライフサイクル
「NPCを自律的に動かす」と聞くと、Behavior Tree(行動ツリー)やBlackboardといった複雑なAIシステムの構築をイメージする方が多いかもしれません。しかし、今回求めるのは高度な心理戦や複雑な思考ではなく、「たくさんのNPCが楽しそうに園内を歩き回る」というビジュアルです。
そこで今回は、思考ルーチンを徹底的にシンプルにするため、以下の5ステップのライフサイクルを設計しました。
💡 NPCの基本ライフサイクル
- スポーン: スポーン地点(ゲート外など)にNPCが生成される
- 入口通過: 園内のエントランス(チェックポイント)へ移動する
- オブジェクト見物: 園内の見たい物(プロップ)をいくつか巡回する
- 出口通過: 満足したら退園ルート(出口)へ向かう
- 消滅(破棄): スポーン地点や特定のエリアに戻り、Destroyされる
このように整理すると、やるべきタスクが非常にシンプルに見えてきます。今回はこのロジックの肝として、非常に手軽な AI Move To ノードをメインに据えて実装していきます。
2. レベルの準備とアクターの配置
まずは実験台となるシンプルなステージ(レベル)を作成します。配置する要素は以下の4つの Plane メッシュのみです。
- 園外の道路を想定した床面
- 園内(庭園内)を想定したメインの床面
- 入口通路用の床面
- 出口通路用の床面
次に、NPCが移動目標(経由地)として認識できるように、中身が空の「Blueprint Class (Actor)」を3種類新規作成し、それぞれをレベル上に配置しておきます。
BP_CheckPoint_Enter(入口用アクター)BP_CheckPoint_Prop(見物したいオブジェクト用アクター)BP_CheckPoint_Goal(出口用アクター)
3. NPCキャラクターの初期実装(ステップ①〜⑤の開通)
来園者となるキャラクター BP_Guest_Character を作成し、イベントグラフを開きます。まずはシンプルに「生成されたら入口へ向かい、オブジェクトを見て、出口から出て消滅する」という一本道の流れを作ります。
ステップ①&②:スポーンから入口へ移動
スポーン用の別アクター(BP_Guest_Spawn)から BP_Guest_Character(テンプレートのThird Person CharacterなどでOK)を生成したら、キャラクター側の BeginPlay からロジックを開始します。
レベル上にある BP_CheckPoint_Enter を検知するため、Get All Actors of Class ノードを使用します。今回はまずテストとして、取得した配列の先頭要素(インデックス0)を Get (Copy) で取得。そのアクターの座標を Get Actor Location で引き出し、AI Move To の Destination に接続します。Pawn ピンには Self を繋いでください。
ステップ③〜⑤:見物、そして退園・破棄へ
AI Move To ノードの右側には、移動が成功した際に実行される On Success ピンがあります。これがこの手法の最大のメリットです。移動完了のタイミングをイベントで綺麗に拾うことができます。
同じ要領で処理を数珠つなぎにしていきます。
- 入口への移動が
On Successしたら、次はGet All Actors of ClassでBP_CheckPoint_Propを取得し、その座標へAI Move To。 - 見物が完了して
On Successしたら、今度はBP_CheckPoint_Goal(出口)を取得してそこへAI Move To。 - 出口に到着したら、最後に
Destroy Actorノードを呼び出し、自分自身を破棄します。
ここで一度Playしてみましょう。NPCが生成され、入口を通り、プロップの前を経由して、出口に到達した瞬間にパッと消滅すれば、基本経路の開通は成功です!
4. 複数オブジェクトを巡回させる(非同期の罠と対策)
基本の動きができたので、次のステップとして「園内に見たい物が複数ある場合、それらを順番にすべて見物してから出口に向かう」という仕様に拡張してみます。レベル上の BP_CheckPoint_Prop を複数個に増やしておきます。
⚠️ 配列処理の落とし穴:For Each Loopが使えない理由
「配列を全部ループすればいい」と考えて、
Get All Actors of Classの後ろにFor Each Loopを配置し、ループ内でAI Move Toを呼び出したくなるところですが、実はこれは機能しません。
AI Move Toは処理が完了するまでに時間がかかる非同期ノード(潜行ノード)です。しかし、For Each Loopは1フレームの間にすべてのインデックスの処理を流れるように実行してしまうため、移動の完了を待たずにループが終了し、一瞬で最後の処理(出口へ向かう処理)へ突き抜けてしまいます。結果として、NPCは途中のオブジェクトを無視していきなり出口に向かってしまいます。
正しいアプローチ:手動カウンタによる再帰的な実行
非同期ノードの完了を待ってから次の要素へ進めるには、ループノードに頼らず、自分でインデックスを管理する仕組み(カウンタ)を実装する必要があります。
まずは BP_Guest_Character に以下の2つの変数を追加します。
PropList(Actor型の配列) :見物するターゲットのアクターを格納CurrentIndex(Integer型) :現在何番目のオブジェクトを目指しているかを記録(初期値 0)
【実装の手順】
- 入口に到着したタイミングで
Get All Actors of Classを行い、取得したアクターの配列をそのまま変数PropListに保存(セット)します。 PropListからCurrentIndex番目の要素をGetし、その座標に向けてAI Move Toを実行します。- 移動が完了して
AI Move ToのOn Successが呼ばれたら、IncrementノードでCurrentIndexを+1します。 - 次にブランチ(
Branch)を置き、条件式として 「CurrentIndex<PropListの長さ (Length)」 を評価します。 - 比較結果が
True(まだ未訪問のオブジェクトがある)であれば、再び手順2のAI Move Toの手前に処理を戻します(ループバック)。 - 比較結果が
False(すべて見終わった)であれば、巡回フェーズを抜けて「出口(Goal)へ向かう処理」へと遷移させます。
5. 動作確認とまとめ
この手動カウンタ方式に修正して再度Playしてみると、レベル上に配置した複数のオブジェクトを、NPCが順番に漏れなく巡回し、最後にしっかり出口へ向かって退園していく様子が確認できるはずです!
この仕組みさえ作ってしまえば、今後レベル上にオブジェクトがどれだけ増えても、コードを一切書き換えることなく自動的にすべてのプロップを回ってくれるようになります。
💡 今回の振り返り
Behavior Treeを律儀にセットアップしなくても、
AI Move Toと簡単な配列・インデックス管理だけで、シミュレーションゲームとして十分魅力的な群衆の回遊ロジックが実装できました。「こだわらなければめちゃくちゃ楽だし、これで十分わちゃわちゃ感を表現できる!」という気づきは、個人開発において非常に大きな武器になりますね。
とりあえず超基本的なNPCのライフサイクルは完成しましたので、今回はここまで。今後は、この動きをすっきりとした「移動ロジック」としてコンポーネント化したり、NPCごとの個性を出せるように拡張していきたいと思います。お読みいただきありがとうございました!
