はじめに
前回の記事で、VRコントローラの可視化、及び、コンパスを表示するまで作りました。
[JavaScript] WebXRでVRコントローラを表示する:XRControllerModelFactoryと右手コンパスUIの実装
Three.jsとWebXRでXRControllerModelFactoryを使い、両手のVRコントローラを正しく表示する方法と、playerRigに追従する右手コンパスUIの実装手順を解説。VR向けUI設計として、ミニマップではなくコンパスを選ぶ理由も具体 …
https://humanxai.info/posts/javascript-threejs-webxr-controller-compass-ui/記事の中でも書いてますが、ヒントになったのはドラゴンレーダーで、今回は、ガチでこのコンパスをドラゴンレーダー化して見ようという内容です。
プレステのゲームなどで、街の中に入ると、ミニマップが表示されて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ステップだけ
- NPCとプレイヤーの差分ベクトルを取る
- 水平成分のみを使用する
- 距離をレーダー半径に正規化する
- 角度を
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という前提に合わせた進化だ。
💬 コメント