[JavaScript] WebXRでドラゴンレーダーを作る:コンパスUIを拡張してNPC位置を可視化する

はじめに

前回の記事で、VRコントローラの可視化、及び、コンパスを表示するまで作りました。

記事の中でも書いてますが、ヒントになったのはドラゴンレーダーで、今回は、ガチでこのコンパスをドラゴンレーダー化して見ようという内容です。

プレステのゲームなどで、街の中に入ると、ミニマップが表示されてNPCの位置がマップ上に表示されると思いますが、あれと同じように丁度、マップ上を動き回るキャラクターの実装が終わってるので、その位置を、レーダーの中に表示してみました。

1. 前回のおさらい

前回の記事では、WebXRでVR向けのUIを扱うための基礎として、 以下の実装を行った。

  • XRControllerModelFactory を使って VRコントローラを左右の手に表示
  • コントローラは scene 直下ではなく playerRig 配下に配置し、プレイヤー移動に正しく追従させた
  • 右手のコントローラに コンパスUI を配置し、 HUDとして常に視界内で確認できる構成にした
  • コンパスの回転は 視線(HMD)ではなく playerRig.rotation.y(プレイヤーの身体の向き)に同期させた

この構成により、

  • 視線を振り回してもUIが安定する
  • 移動方向を基準にした、VR向けに分かりやすいナビゲーション

を実現している。

詳細な実装については、以下の記事を参照してほしい。

[JavaScript] WebXRでVRコントローラを表示する:XRControllerModelFactoryと右手コンパスUIの実装
https://humanxai.info/posts/javascript-threejs-webxr-controller-compass-ui/

本記事では、この コンパスUIをベース に、 NPCの位置を可視化する「ドラゴンレーダー風UI」へと拡張していく。

PCミニマップとドラゴンレーダーの対応表

要素 PCミニマップ 今回のドラゴンレーダー
基準 ワールド座標 プレイヤー中心
視点 上からのカメラ カメラなし
計算 座標投影 座標差分+回転
描画 RenderTarget / viewport Geometryのみ
更新 毎フレーム描画 軽量ベクトル演算
VR適性 低い 高い

2. VRで「ミニマップ」を使わなかった理由

PC向けの3Dゲームでは、ミニマップは定番のUIだ。 Three.jsでも、複数カメラや RenderTarget を使えば比較的簡単に実装できる。

しかし、この方法を そのままVRに持ち込むと問題が多い。

RenderTargetはVRで重い

VRでは、描画自体が左右の目で2回行われる。 その状態でさらに RenderTarget を使ってサブカメラを描画すると、

  • フレームレートが不安定になる
  • 環境によっては黒画面やフリーズが起きる
  • エラーが出ずに破綻するケースもある

PCでは問題にならない処理が、 WebXRでは一気にリスクになる。

視線を奪うUIはVRに向かない

ミニマップは「見に行くUI」だ。 視線を画面の隅に移動させ、情報を読み取る前提になっている。

VRではこの動作が、

  • 首や視線の大きな移動を伴う
  • 没入感を壊しやすい
  • VR酔いの原因になりやすい

特に、常時表示のミニマップは VR体験と相性が悪い。

「上から見る」発想が破綻する

ミニマップは、基本的に

「世界を上から見下ろす」

という前提で作られている。

しかしVRでは、

  • プレイヤーは世界の中に「立っている」
  • 神視点に切り替わる感覚が強すぎる
  • 空間認識が一瞬でリセットされる

結果として、 分かりやすさより違和感が勝つ。


こうした理由から、本記事では ミニマップの代わりに コンパスUI を選んだ。

  • 情報量を最小限に絞れる
  • 視線を大きく動かさずに確認できる
  • プレイヤー基準のナビゲーションにできる

VR向けUIとして、 この判断はかなり重要だと感じている。

3. ドラゴンレーダー化の発想

今回の実装は、ゼロから新しいUIを考えたわけではない。

すでにゲーム側には、次の要素が揃っていた。

  • NPCはワールド内を移動している
  • プレイヤーとの 衝突判定 がある
  • 一定距離で 吹き出しメッセージ が表示される

つまり、ゲーム内部ではすでに

  • 「誰が」
  • 「どこにいるか」

という情報を、常に持っている状態だった。


不足していたのは、たった1点だけ。

プレイヤーが、遠くにいるNPCの存在に気づく手段

そこで必要になるのは、

  • 高精度な位置
  • 上から見た地形
  • 詳細な地図

ではない。

方向と距離が分かれば十分。


この条件を満たすUIとして思い浮かんだのが、 いわゆる ドラゴンレーダー 的な表現だった。

  • 自分を中心に
  • 周囲の対象を点で表示する
  • 対象が動けば、点も動く

実はこれ、やっていること自体は 3Dゲームのミニマップと同じ情報量しか扱っていない。

違うのは、

  • 表現方法
  • 基準座標
  • プレイヤーとの距離感

だけだ。


ミニマップが「世界を俯瞰するUI」だとすれば、 ドラゴンレーダーは「自分を中心に世界を感じるUI」。

VRでは、この違いが体験の質に直結する。

次のセクションでは、 このドラゴンレーダーを どうやって実装したか、 その中身を見ていく。

4. 実装の本質(ここが一番大事)

ドラゴンレーダーの実装は、見た目に反してとてもシンプルだ。 やっていることは、実は たった4ステップ しかない。

やっていることは4ステップだけ

  1. NPCとプレイヤーの差分ベクトルを取る
  2. 水平成分のみを使用する
  3. 距離をレーダー半径に正規化する
  4. 角度を atan2 で算出する

擬似コードにすると、ほぼこれだけになる。

dir = npcPos - playerPos
angle = atan2(...)
radius = distance / maxRange

1. NPCとプレイヤーの差分ベクトル

まず、NPCの位置とプレイヤーの位置は、どちらも ワールド座標 として取得できる。

const dir = npcPos.sub(playerPos);

この時点で dir は、

  • プレイヤーから見た
  • NPCの相対方向と距離

を含んだベクトルになる。


2. 水平成分のみを使用する

レーダーで必要なのは「上下」ではなく「周囲」。

そのため、Y成分は無視する。

dir.y = 0;

これにより、 高さの違いによるノイズを排除できる。


3. 距離をレーダー半径に正規化する

次に、NPCとの距離をレーダー内の半径に変換する。

const radius = (distance / maxRange) * COMPASS_RADIUS;
  • maxRange:探知可能な最大距離
  • COMPASS_RADIUS:UI上のレーダー半径

この処理によって、

  • 近いNPCは中心付近
  • 遠いNPCは外周付近

という直感的な表現ができる。


4. 角度を atan2 で算出する

最後に、NPCがどの方向にいるかを角度として求める。

const angle = Math.atan2(...);

atan2 を使うことで、

  • 前後左右すべての方向を正しく扱える
  • 角度の符号や象限を意識せずに済む

という利点がある。


カメラは一切使っていない

ここが、この実装の一番重要なポイントだ。

  • 上から見下ろすカメラ
  • RenderTarget
  • 複数カメラ構成

これらは一切使っていない。

必要なのは座標とベクトル計算だけ。

そのため、

  • 処理が軽い
  • WebXRの制約を踏まえられる
  • VRでも安定して動く

というメリットがある。

この点こそが、 このドラゴンレーダーが VR向き である理由だ。

5. コンパス回転とレーダー点の役割分離

ドラゴンレーダーを実装する中で、 一番時間を使ったのが 回転の扱い だった。

結論から言うと、設計は次のように分ける必要がある。

  • コンパス全体:プレイヤーの向きで回転
  • レーダー点:ワールド北基準で配置
  • 回転は親子関係(Scene Graph)に任せる

コンパス全体は「身体の向き」で回転させる

コンパスは HUD であり、 「今、自分がどちらを向いているか」を示すUIだ。

そのため、回転の基準は視線ではなく プレイヤーの身体の向きを使う。

compass.rotation.z = -playerRig.rotation.y;

これにより、

  • 視線を動かしてもコンパスは安定
  • 移動方向に対して一貫した表示

になる。


レーダー点は「ワールド北基準」で配置する

一方、NPCの位置は ワールド座標系で決まっている。

そのため、レーダー点の角度計算は、

  • プレイヤーからNPCへの差分
  • ワールド北(Z-)を基準

で行う。

ここで、 プレイヤーの回転を角度計算に混ぜてはいけない。


親子関係で回転を任せる

最終的な見た目の回転は、

  • 親(コンパス)を回す
  • 子(レーダー点)はローカル座標で配置

という Scene Graph の仕組みに任せる。

compass (回転)
 └─ dot(位置のみ)

こうすることで、

  • プレイヤーが回転すると
  • コンパス全体が回り
  • レーダー点も一緒に回る

という自然な挙動になる。


両方で回転させると必ず破綻する

実装途中で一度、

  • 角度計算にプレイヤーの向きを含める
  • さらにコンパス自体も回転させる

という状態になった。

結果は、

  • 東西が反転する
  • 向きを変えても点が動かない
  • 挙動が直感と合わない

という破綻だった。

回転を「計算」と「Scene Graph」の両方でやると破綻する

これは、実際に手を動かさないと気づきにくい落とし穴だ。


この役割分離を明確にしたことで、

  • 実装がシンプルになる
  • デバッグしやすくなる
  • 複数NPCへの拡張も容易になる

ドラゴンレーダーは、 回転の責務をどこに持たせるかがすべてだと言ってもいい。

6. 完成イメージ

NPCは、ワールド内を自由に歩き回っている。 プレイヤーの視界に入っていなくても、挙動は変わらない。

ふと右手を見ると、 そこにはコンパスUIがあり、 レーダー内に 黄色い点 が表示されている。

NPCが移動すると、

  • 点はレーダー内を滑るように動き
  • 距離が近づけば中心に寄り
  • 離れれば外周へと移動する

プレイヤーが向きを変えると、

  • コンパス全体が身体の向きに合わせて回転し
  • レーダー点も自然に追従する

視界にNPCが入る前から、 「どちらにいるか」「どれくらい離れているか」が分かる。

操作の邪魔をせず、 視線を大きく動かす必要もない。

完全にドラゴンレーダーだ。

ミニマップのように世界を俯瞰するのではなく、 自分を中心に周囲を“感じる”ためのUIになっている。

7. これはミニマップの代替ではなく「進化」

今回作ったドラゴンレーダーは、 ミニマップの単なる代替ではない。

扱っている情報量だけを見れば、

  • プレイヤーの位置
  • NPCの方向
  • NPCとの距離

これは、従来の3Dゲームのミニマップと同じだ。

しかし、表現の仕方と設計思想がまったく違う。


このレーダーUIは、

  • 情報量は最小限
  • 認知負荷が低い
  • 視線移動が少ない
  • VR酔いを引き起こしにくい

という特徴を持っている。

ミニマップのように 「見に行く」必要はなく、 「手元を見る」だけで把握できる。


さらに重要なのは、 この設計が WebXRの制約を前提にしている ことだ。

  • RenderTargetを使わない
  • 複数カメラを使わない
  • カメラ切り替えをしない

その結果、

  • 軽量で
  • 安定して
  • 環境依存のトラブルが少ない

UIになっている。


VRでは、 PCゲームのUIをそのまま持ち込むと破綻することが多い。

今回のドラゴンレーダーは、

ミニマップが持っていた「役割」だけを抜き出し、 VR向けに再構成したUI

と言える。

これは「代替」ではなく、 VRという前提に合わせた進化だ。