A-Frameの勉強として、VR迷路を作ってみました。
ソースコードはgithubで公開しています。
作るもの
A-Frameを使ったVR迷路
"スマートフォン(iOS,Android)+Cardboard"だけで操作できるようlook-controls
で視点を変えることで、camera(カメラ)の向いている方向に移動させます。
迷路の配置
碁盤目状の9×9マスに壁と通路を作るイメージで、<a-plane>
の上に<a-box>
を並べているだけです。
class="collidable"
は、当たり判定のために付与しているクラスです。詳しくは、後で説明します。
<a-entity id="maze" position="-4 1 -10">
<a-box class="collidable" position="0 0 0" width="1" height="2" depth="1" color="#4CC3D9"></a-box>
<a-box class="collidable" position="0 0 1" width="1" height="2" depth="1" color="#4CC3D9"></a-box>
<a-box class="collidable" position="0 0 2" width="1" height="2" depth="1" color="#4CC3D9"></a-box>
<a-box class="collidable" position="0 0 3" width="1" height="2" depth="1" color="#4CC3D9"></a-box>
...
<a-box class="collidable" position="8 0 7" width="1" height="2" depth="1" color="#4CC3D9"></a-box>
<a-box class="collidable" position="8 0 8" width="1" height="2" depth="1" color="#4CC3D9"></a-box>
</a-entity>
<a-plane rotation="-90 0 0" width="100" height="100" color="#7BC8A4" static></a-plane>
プレイヤーの移動
camera(カメラ)の向きからベクトルを計算し、移動速度(speed)を掛けることで移動量とします。
上下は考慮しないので、y軸の回転角から三角関数(あまり自信はない)で求めます。
var speed = 0.01;
var isIntersect = false;
function movePlayer() {
var camera = document.getElementById('camera');
if (camera && !isIntersect) {
var position = camera.getAttribute('position');
var rotation = camera.getAttribute('rotation');
position.x += -Math.cos((rotation.y - 90) * Math.PI / 180) * speed;
position.z += Math.sin((rotation.y - 90) * Math.PI / 180) * speed;
camera.setAttribute('position', position);
}
}
A-FrameはHTMLで記述されているため、getAttribute
、setAttribute
で移動・回転ができます。
isIntersect
は壁との当たり判定の結果を格納する変数です。こちらも後で説明します。
movePlayer()
は、くり返し実行する必要があるので、renderメソッドを作成してループさせます。
var t = 0;
function render() {
t += 0.01;
requestAnimationFrame(render);
movePlayer();
}
render();
プレイヤーと壁の当たり判定
camera(カメラ)にraycasterを追加して、視線(=進行方向)と交差するオブジェクトの有無を判定します。
<a-entity id="camera" camera look-controls raycaster="objects: .collidable; far: 0.5;" collider-check rotation="0 0 0" position="0 0.3 0"></a-entity>
まず、raycaster
のプロパティについて説明します。
-
objects: .collidable;
当たり判定の対象を指定するプロパティです。
class="collidable"
が付与されたオブジェクトを対象にしており、迷路の壁にあたる<a-box>
は当たり判定の対象、床にあたる<a-plane>
は対象外となります。 -
far: 0.5;
当たり判定を行う距離です。
デフォルト値が"Infinity"となっていて、変更しないと視線に壁があるだけで衝突と判定されてしまいます。
次に、当たり判定用のコンポーネントとして、collider-check
を設定します。
AFRAME.registerComponent('collider-check', {
dependencies: ['raycaster'],
init: function () {
this.el.addEventListener('raycaster-intersection', function () {
isIntersect = true;
});
this.el.addEventListener('raycaster-intersection-cleared', function () {
isIntersect = false;
});
}
});
raycaster
のプロパティで設定した距離内に交差オブジェクトがある場合、raycaster-intersection
イベントが発生します。この間、isIntersect
がtrue
になるようにします。
同様に、交差オブジェクトがない場合、raycaster-intersection-cleared
イベントが発生しますので、isIntersect
はfalse
にします。
先程、プレイヤーの移動を行う条件式に!isIntersect
を入れていたので、"壁に近付くと止まる"、"前方に壁がなければ前進する"という挙動になります。
これで、コーディングは完了です。
ビルド&デプロイ
gulpを使ってビルドしました。
記事の内容から逸れるので、詳細は割愛します。
$ gulp default
動作確認のためにWebサーバを用意するのも面倒なので、Amazon S3に必要なファイルだけアップしました。
- dist/app.min.js
- index.html
迷路に入ってみる
スマートフォン実機(iPhone SE)で、index.htmlにアクセスします。
向きを変えることで、ちゃんと迷路を抜けられました。
ただ、アドレスバーが邪魔ですね。
気を取り直して、VRモードに切り替えます。
視差効果で左右の表示が異なっているのがわかります。
最後に、以前に自作したCardboard初号機にセットして見てみます。
立体的に見えた!
...けど、没入感はあまりないかな。
まとめ
A-Frameを使ってVR体験できる迷路を作りました。
ほとんどJavaScriptを書かずに実装できたので、three.jsと比べて、スッキリと見やすく記述できそうです。
移動や当たり判定は、ゲームやナビゲーション等にも応用できそうですし、何かの役に立てば幸いです。
余力があれば、スタート・ゴール時の処理を追加してタイム計測したり、ステージを切り替えられるようにすると、ミニゲームっぽくなるかなと思います。