前回に引き続き、A-Frameで簡単なVRアプリを実装してみます。
今回は、aframe-physics-systemによる物理演算のサンプルです。
動作デモ
先に何ができるかイメージした方がよさそうなので、デモ動画を載せておきます。
前回同様にgdgd妖精sのMMDを使用したので、サーバ公開はなしで動画のみです。
だいたいできた。#aframe #gdgd妖精s pic.twitter.com/usQQRBtdm5
— jyuko (@jyuko49) 2017年11月26日
model: シルシル、コロコロ、ピクピク
copyright: (c) gdgd妖精s (ぐだぐだフェアリーーーズ), 2代目gdgd妖精s
カメラの向いている方向にボールを飛ばし、的にぶつけて落としています。また、衝突を検知してパーティクルによるエフェクトの実装も試しました。
aframe-physics-systemの利用
A-Frameで物理演算を行うには、aframe-physics-systemを使うのが簡単です。
aframe-physics-systemを単体で利用してもよいですが、aframe-extrasにインクルードされているため、他の機能を利用することも考えて、aframe-extrasを使った方が便利です。
HTML内で"aframe-extras.min.js"をリンクすれば物理演算を含む拡張機能が使えるようになります。
<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v3.12.4/dist/aframe-extras.min.js"></script>
今回はサンプルなのでCDNからリンクしました。もちろんローカルコピーでもOKです。
規模が大きくなったらnpmで管理した方がいいかなと思いますが、まあそのうち。
シーンの設定
物理演算を有効にするには、<a-scene>
タグにphysics
属性を付けます。
<a-scene physics="debug: false;">
プロパティでは重力、反発係数、摩擦係数など指定できます。
※シーン全体の設定であることに注意
debugプロパティをtrueにすると、physicsの当たり判定(collider)がワイヤーフレームで表示されます。
static-body、dynamic-bodyの指定
物理演算を行うentityに、static-body
、dynamic-body
のいずれかの属性を付与します。
どちらも付与していないentityは他の物体に関与せず、衝突せずに通り抜けます。
static-body
を付与すると位置は固定され、衝突した物体に影響を与えます。床や壁などに指定します。
<a-entity id="board" geometry="primitive: box; depth: 10; height: 0.5; width: 40" material="color: #deb887" static-body position="0 0 -20"> </a-entity>
dynamic-body
を付与すると、重力や外部からの力を受けて反射・摩擦を行います。ボールを飛ばす場合や動かせる障害物などに指定します。
dynamic-body
の場合は、プロパティでcolliderの形状(shape)や物体の質量(mass)が指定できます。
<a-entity id="コロコロ" mmd-model="model: コロコロ/コロコロ.pmx;" dynamic-body="shape: box; mass: 10" position="0 5 -20" scale="0.5 0.5 0.5"> </a-entity>
shape
のデフォルト値はauto
になっており、基本的なオブジェクトであれば適切な形状を設定してくれます。
ただし、各種3Dフォーマットからロードした複雑な形状のオブジェクトの場合、auto
だとcolliderが設定されないことがあり、box
、cylinder
、sphere
のいずれかが無難です。
この辺りは、シーンの設定でdebugを有効にしながら設定するとわかりやすいです。
ボールを投げる
シューティングゲームやボールを投げてモンスターを捕まえるゲーム用に、ボールを投げる処理を実装してみます。
dynamic-body
属性を付与された要素は、自動的にvelocity
属性を持ちます。このvelocity
属性に飛ばしたい方向の速度ベクトルを設定してあげれば、後は物理演算ライブラリがよしなにしてくれます。
処理はJavaScriptになりますが、HTML要素の操作になるのでgetAttribute()
、setAttribute()
、appendChild()
など、Web制作で見慣れたコードになります。
サンプルコードにコメントを入れたので参考にしてください。
var _shot = function () { // カメラの位置と向きを取得する var camera = document.getElementById('camera'); var rotation = camera.getAttribute('rotation'); var position = camera.getAttribute('position'); // カメラ前方方向に速さを掛けて、速度ベクトルを求める var velocity = new THREE.Vector3(); var speed = 50; velocity.x = Math.sin(rotation.y * Math.PI / 180) * Math.cos(rotation.x * Math.PI / 180) * -speed; velocity.y = Math.sin(rotation.x * Math.PI / 180) * speed; velocity.z = Math.cos(rotation.y * Math.PI / 180) * Math.cos(rotation.x * Math.PI / 180) * -speed; // <a-entity>を新規作成して速度を与える var ball = document.createElement('a-entity'); ball.setAttribute('id', 'ball'); ball.setAttribute('velocity', velocity); ball.setAttribute('position', position); ball.setAttribute('geometry', 'primitive: sphere; radius: 0.5'); ball.setAttribute('dynamic-body', 'mass: 1'); // <a-scene>に追加する var scene = document.querySelector('a-scene'); scene.appendChild(ball); }; // keydownイベントでボールを発射する処理を呼び出す document.addEventListener('keydown', _shot);
dynamic-body
が付与されていれば、同じ要領でボール以外も飛ばせます。
シン・コロチャン襲来#aframe #gdgd妖精s pic.twitter.com/kN066hiJTm
— jyuko (@jyuko49) 2017年11月26日
衝突時に処理を行う
物体が他の物体と衝突した際に、対象オブジェクトを消したり、スコアを加算したり、アニメーションを起こしたりと何らかの処理を行いたいケースは多いです。
static-body
またはdynamic-body
を設定したentityにcollide
イベントを登録することで、他の物体と衝突した際の処理を記述できます。
サンプルとして、パーティクルを使ったエフェクトを実装してみました。
var _init = function () { // collideイベントを登録したい要素を取得する var targetElement = document.getElementById('コロコロ'); // collideイベントを登録する targetElement.addEventListener('collide', function (e) { // ボール(id="ball")と衝突した場合のみ処理を行う if (e.detail.body.el.id === 'ball'){ // 衝突したボールと同じ位置にパーティクルを発生させる var effect = document.createElement('a-entity'); var position = e.detail.body.el.getAttribute('position'); effect.setAttribute('position', position); effect.setAttribute('particle-system', 'color: #EF0000,#44CC00; particleCount: 500; duration: 0.2;'); // パーティクルを<a-scene>に追加する var scene = document.querySelector('a-scene'); scene.appendChild(effect); }; }); }; // DOMのloadが終わってから処理する document.addEventListener('DOMContentLoaded', _init);
サンプルでは衝突したオブジェクトの情報をe.detail.body.el
から取得しています。同様に、衝突されたオブジェクトや衝突地点の情報も取得できます。
※詳細はこちらを参照
エフェクトには、aframe-particle-systemを使っています。
こちらは、aframe-extrasに含まれないので別途リンクしてください。(下記はCDNの例)
<script src="https://unpkg.com/aframe-particle-system-component@1.0.x/dist/aframe-particle-system-component.min.js"></script>
ソースコード(サンプル)
冒頭の動画のうち、物理演算を使った処理は粗方説明したので、ソースコード全文を載せておきます。
MMDモデルの取得のみコピペでは動かないので、手持ちのモデルデータに置き換えて使ってください。
A-Frameだと100行に満たないHTMLで色々なサンプルが作れちゃうので、Gistとの相性がよいですね。
まとめ
aframe-physics-systemを使った物理演算を行ってみました。
Unityなどのゲームエンジンに比べると機能がシンプルで制約もありますが、基本的な処理は実装できます。
A-Frameの特徴に従い、HTML要素やイベントリスナの操作になるので、Webフロントエンドの基礎レベルの知識があれば実装できてしまう点が一番のメリットかなと思います。