はじめに
前回の記事では、Three.jsで段差やスロープを自然に上がるPC用の移動処理を実装した。
[JavaScript] Three.jsで段差を登って降りられるキャラクター移動を実装する
Box3による単純な衝突判定を拡張し、colliderとtype設計を導入することで、three.js上で段差の上り下りが可能なキャラクター移動を実装する。
/posts/javascript-threejs-step-up-down-character-controller/今回はその処理を WebXR(VR)に持ってきたときに起きた破綻と、 それをどう直したかを書く。
結論だけ先に言うと、 移動ロジックは間違っていなかった。壊れていたのは「誰のY座標を信じるか」だった。
PC版がうまく動いていた理由
PC版では次の前提が成立していた。
- 移動主体:
player.box - 当たり判定:
player.box - 床スナップ:
player.box.position.y - カメラ:player.box を追従
この構造では、
- 段差判定
- スロープ処理
- 疑似重力
すべてが player.box を中心に一貫していた。
VRにした瞬間に壊れた理由
VRでは構造が変わる。
- 移動主体:
playerRig - カメラ:
playerRigの子(HMD) - 当たり判定:
player.box(追従用)
ここで PC版の snapToGround をそのまま使うと、
- 床に張り付くのは
player.box - 実際に動くのは
playerRig
Y座標の主権が分裂する。
これが、
- 段差で引っかかる
- スロープで浮く
- 視点がズレる
原因だった。
snapToGroundVR が必要だった理由
VRで床に張り付くべきなのは playerRig。
やっていることはPC版と同じだが、 修正対象だけを変える必要があった。
export function snapToGroundVR(groundMeshes) {
const footRay = new THREE.Raycaster(
config.playerRig.position.clone().add(new THREE.Vector3(0, 0.1, 0)),
new THREE.Vector3(0, -1, 0),
0,
2
);
const hits = footRay.intersectObjects(groundMeshes, false);
if (hits.length > 0) {
config.playerRig.position.y = hits[0].point.y;
} else {
config.playerRig.position.y -= 0.1;
}
}
ロジックは同じ。 誰のYを信用するかだけを切り替えた。
VRでも段差・スロープは「同じ考え方」
VRだからといって、
- 特別なスロープ判定
- VR専用の段差ロジック
は不要だった。
PC版と同じく、
- 前方で当たる
- 少しYを上げると通れる
- 毎フレーム snapToGround される
これでスロープは自然に成立する。
カメラを「制御しない」という設計
最後に効いたのがこれ。
if (!config.renderer.xr.isPresenting) {
updateCamera();
}
VR中は カメラを一切制御しない。
- 視点の高さ → 人間
- 視点の回転 → 首
- 見る方向 → 意志
コードは世界だけを決める。
この一行で、
- 視点の上下が自然になり
- 段差を上がった感覚が一致し
- VR特有の違和感が消えた
おわりに
VRで段差やスロープが壊れる原因は、 「VRは特殊だから」ではなかった。
移動ロジックはPCと同じでいい。 違うのは、どのオブジェクトが“存在の基準”か。
そこを揃えた瞬間、 VRでも地面は当たり前のように足元にあった。
💬 コメント