【ITニュース解説】Horizon World Tutorial - Maze Runner - Part 5 - NPC runners
2025年09月06日に「Dev.to」が公開したITニュース「Horizon World Tutorial - Maze Runner - Part 5 - NPC runners」について初心者にもわかりやすく解説しています。
ITニュース概要
Horizon World迷路ゲームにNPCを追加する方法を解説する。ランダムに動くNPCと、ゴールへ最短で進むNPCの2種を実装。キャラクター設定、迷路経路の生成(ランダム探索、DFS)、NPCの動きを制御するプログラム構築までを学ぶ。ゲームの挑戦性と面白さが増す。
ITニュース解説
この解説では、オンライン仮想空間プラットフォーム「Horizon World」で開発中の迷路ゲームに、自動で動くキャラクター、つまり「NPC(ノンプレイヤーキャラクター)」を追加する方法を説明する。これにより、ゲームはただプレイヤーが迷路を走るだけでなく、他のキャラクターとの競争や協力の要素が加わり、より面白く、挑戦的な体験になる。具体的には、迷路をランダムに探索するNPCと、最短経路でゴールを目指すNPCの二種類を作成し、それぞれの動きを制御するプログラミングの手順を学ぶ。
まず、ゲームの世界にNPCを配置する。Horizon Worldのデスクトップエディタで既存の迷路ゲームプロジェクトを開き、あらかじめ用意されている「NPCギズモ」という部品をシーンにドラッグ&ドロップする。これはゲーム内でキャラクターとして表示される基盤である。配置後、NPCの表示名と、プログラムから識別するためのオブジェクト名を設定する。次に、NPCの外見をカスタマイズするため、Webブラウザ上で動作する「アバターエディター」を使用する。ここでアバターの見た目を編集し保存すると、デスクトップエディタ上のNPCも新しい外見に更新される。 NPCが迷路を走り終えた後や、ゲームラウンドがリセットされた際に元の位置に戻れるように、「スポーンポイントギズモ」を追加する。これはNPCの初期位置やテレポート先となる場所である。NPCと同じ位置に配置し、方向を合わせ、NPCの名前に合わせた名前(例:RexSpawnPoint)にリネームしておく。
NPCの準備が整ったら、その動きをプログラムで制御する段階に入る。新しいスクリプトファイル「RandomNPCRunner.ts」を作成し、このスクリプトを先ほど配置したNPCオブジェクトにアタッチする。これにより、スクリプトがNPCの動作を直接制御できるようになる。 スクリプトには、ゲームの状態を管理する「GameState」や、ゲーム内の出来事を通知する「Events」といった共通のユーティリティをインポートする。さらに、NPCのAI的な振る舞いをプログラムで制御するために、「AvatarAIAgent」という特別なクラスをインポートする必要がある。このクラスを利用することで、NPCが移動したり、回転したりする機能にアクセスできるようになる。AvatarAIAgentを使用するためには、プロジェクト設定でこのパッケージを有効にする作業が必要である。 スクリプト内では、NPCのカスタマイズ可能なプロパティも定義する。これには、NPCの移動速度の最小値と最大値、迷路内でのスポーン地点、ロビーでのスポーン地点、そしてNPCが迷路の通路の中央をまっすぐ走らずに少しずれて移動するためのオフセットが含まれる。これらのプロパティはエディタ上で調整可能である。
NPCが迷路の中を適切に動くためには、迷路の構造に関する情報が必要である。具体的には、どのセルが通路で、どのセルが壁であるか、そしてスタート地点とゴール地点はどこかといった情報である。この情報をNPCに伝えるために、「mazeCarved」という新しいイベントを定義し、迷路が生成された直後にこのイベントをブロードキャストするよう「Maze.ts」スクリプトを更新する。イベントがブロードキャストされる際、迷路のデータから壁の情報は除外し、通路の座標とタイプ(スタート、エンド、通常の通路)のみを簡潔な形式で送る。これにより、NPCは壁にぶつからずに移動すべき経路を知ることができる。
次に、ゲームの状態変化に応じてNPCがどう行動するかを定義する。「preStart」関数内で「gameStateChanged」イベントのリスナーを設定し、ゲームの状態が「Starting(開始中)」「Playing(プレイ中)」「Ending(終了中)」「Finished(終了済み)」のいずれかに変わるたびに「handleGameStateChanged」関数が呼び出されるようにする。「Starting」状態では、NPCの「finished」フラグをfalseに設定し、「Playing」状態ではNPCを迷路内にテレポートさせて移動を開始させる。「Ending」状態ではfinishedフラグをtrueにし、これによってNPCはそれ以上の移動を中断する。「Finished」状態では、NPCをロビーに戻し、迷路に関するデータをクリアする。
NPCを迷路内に移動させる「moveNPCToMatch」関数では、NPCのプレイヤーオブジェクトを取得し、設定されたゲーム内スポーンポイントにテレポートさせる。テレポートが完了するまでの時間を考慮し、1秒後に実際の迷路探索を開始する「setNPCPath」関数を呼び出す。
ランダムに迷路を探索するNPCの最も重要な部分は、「buildMazePath」関数で迷路の経路を生成するロジックである。この関数では、現在のNPCの位置、探索された経路、移動可能な方向のリスト、すでに訪問したセルを記録するセット、そしてデッドエンドから戻るためのインデックスを管理する。スタート地点から始まり、ゴール地点に到達するまで、以下のプロセスを繰り返す。まず、移動可能な方向をランダムにシャッフルし、未訪問で壁ではない有効なセルを探索する。有効なセルが見つかれば、そのセルを経路に追加し、訪問済みとしてマークし、現在の位置を更新する。もし有効なセルが見つからずデッドエンドに到達した場合、lastFoundIndexを使って経路を遡り、新たな有効な方向が見つかるまでバックトラックを続ける。このプロセスにより、NPCは迷路の終点までランダムな経路を生成する。
生成された経路は、そのままではNPCの動きがぎこちなくなる可能性があるため、「setNPCPath」関数内で経路を最適化する。具体的には、同じ方向に連続して移動するステップをフィルタリングして取り除き、よりスムーズな動きを実現する。次に、NPCを実際に動かすためにPromiseチェーンという、複雑な処理を順序立てて実行する仕組みを利用する。パス内の各ステップに対し、まずNPCを次の移動方向に回転させ、次に定義された最小速度と最大速度の間でランダムに選ばれた速度でその位置まで移動させる。この際、NPCが迷路の通路の中央を走るのではなく、少しオフセット(ずらして)移動するように、位置情報を調整する。また、ゲームが終了状態になった場合は、このPromiseチェーンを中断し、NPCがそれ以上の動きをしないようにする。 ゲームラウンドが終了すると、NPCはロビーに戻る必要がある。「moveNPCToLobby」関数では、NPCをロビーのスポーンポイントにテレポートさせる。ここでも、テレポート完了後の時間を考慮し、1秒後にNPCをロビー内で適切な位置に移動させ、正しい方向に回転させることで、NPCがロビーに戻った後も正しく振る舞うようにする。
以上の設定とプログラミングにより、ランダムに迷路を走るNPCが完成する。ゲームをテストプレイすると、NPCが迷路内にテレポートし、ランダムな速度で迷路を探索し、ゴールに到達すると停止し、ラウンド終了後にロビーに戻る様子が確認できるはずだ。
次に、より戦略的な動きをする「ダイレクトNPCランナー」を実装する。このNPCは常にゴールへの最短経路を目指すが、ランダムNPCよりも遅い速度で移動するように設定することで、ゲームのバランスを保つ。
このNPCはランダムNPCと多くの共通点を持つため、既存の「RandomNPCRunner.ts」スクリプトを複製し、「DirectNPCRunner.ts」にリネームすることから始める。新しいNPCオブジェクトをシーンに追加し、新しいスポーンポイントを作成したら、このダイレクトNPCに「DirectNPCRunner.ts」スクリプトをアタッチする。
「DirectNPCRunner.ts」スクリプトを開き、クラス名を「DirectNPCRunner」に変更し、minSpeedとmaxSpeedプロパティをランダムNPCよりも遅い値に調整する(例:最小速度2、最大速度4)。
ダイレクトNPCの核となる変更は、「buildMazePath」関数である。ランダム探索とは異なり、ここでは「深さ優先探索(DFS:Depth First Search)」というアルゴリズムを使用して、迷路のスタート地点からゴール地点までの最短経路を特定する。DFSを実装するために、「MazeNode」というインターフェースを定義し、迷路内の各セルが持つべき情報(X座標、Z座標、前のノードへの参照、そしてどの方向に進んだか)を定義する。
「buildMazePath」関数内では、キュー(ここではスタックのように使用する)と、すでに訪問したセルを追跡するための配列、そしてゴールに到達したノードを格納する変数を初期化する。DFSの探索ループでは、キューから現在のノードを取り出し、それがゴールノードであれば探索を終了する。そうでなければ、現在のノードから上下左右の隣接セルを探索し、それが壁でなく、かつ未訪問であれば、キューに新しいノードとして追加し、訪問済みとしてマークする。この新しいノードには、現在のノードへの参照(prev)を含めることで、後で経路を再構築できるようにする。
ゴールノードが見つかったら、prev参照を逆順にたどっていくことで、ゴールからスタートまでの経路を再構築する。この逆順の経路をパス配列に追加し、最後に配列を反転させることで、スタートからゴールまでの正しい最短経路が得られる。
スクリプトを保存し、再コンパイルが完了したらゲームをプレイモードで開始する。これで、ランダムに動くNPCと、ゴールへ一直線に向かうが速度が遅いダイレクトNPCの二種類が同時に迷路を走る様子が確認できるだろう。 このように、ゲームに2種類のNPCランナーを導入することで、プレイヤーにとって多様な挑戦が生まれる。このチュートリアルシリーズはここで完結するが、これらの基盤を元に、さらに多くの機能や面白さを追加していくことが可能である。