Chapter 09

A-FrameでVRコントローラーを使う

かじるプログラミング
かじるプログラミング
2021.10.03に更新

A-FrameでVRコントローラーを使う

A-Frameを使うことで、VRデバイス用コントローラも簡単に制御する事ができます。

ただし、VRデバイスを利用してテストをする場合は"https"環境でのWebサーバーが必要になります。(こちらは自前で用意する必要があります)
Glitchで開発を進めている方は、そのまま進めることが出来ます。

HTMLを編集する

最初に、HTMLを編集します。
VRコントローラーの状態を表示する"a-text"を"a-camera"タグの内部に配置しておきます。
コントローラーからの反応は、このテキストに表示することにします。

index.html
<!-- カメラ正面にテキストを表示する -->
<a-camera id="my_camera">
	<a-cursor></a-cursor>
	<a-text id="my_text" value="***" position="0 -0.1 -2" scale="0.4 0.4 0.4" align="center" color="#ffffff"></a-text>
</a-camera>

そして、両手に持った左右のコントローラーとして、"a-entity"タグを2つ用意します。
"laser-controls"には、左右どちらかのコントローラーを指定し、
"raycaster"には、検知対象のクラス名を記述します。(後ほど解説します)

アトリビュート名 記述例 意味
laser-controls hand: left 左手用コントローラ
laser-controls hand: right 右手用コントローラ
raycaster objects: .collidable; far: 5 衝突判定をしたい対象のクラス名; 線の長さ
index.html
<!-- 左右のコントローラー -->
<a-entity laser-controls="hand: left" raycaster="objects: .collidable; far: 5" vr-controller></a-entity>
<a-entity laser-controls="hand: right" raycaster="objects: .collidable; far: 5" vr-controller></a-entity>

JavaScriptを編集する

"AFRAME.registerComponent()"関数を利用し、コントローラーをコンポーネントをとして設定します。
第一引数には先ほど用意したコントローラーに付けた"vr-controller"を指定します。
第二引数の"init"の部分には各種初期化処理や、コントローラーの反応に応じたイベントを記述していきます。
"document.getElementById()"で、カメラ正面に表示するテキストエリアを取得しています。

main.js
AFRAME.registerComponent("vr-controller", {
	dependencies: ["raycaster"],// Important
	init: function(){
		const text = document.getElementById("my_text");// 表示用テキスト
		text.setAttribute("value", "Controller is ready!!");
		// この部分にイベントを記述します
	}
});

グリップの処理

コントローラについているグリップ(親指がで押すボタンですね)のイベント処理です。
"gripdown"で押した時、"gripup"で離した時のイベントを取得する事ができます。

main.js
this.el.addEventListener("gripdown", function(e) {
	text.setAttribute("value", "GripDown!!");
});
	this.el.addEventListener("gripup", function(e) {
	text.setAttribute("value", "GripUp!!");
});

トリガーの処理

コントローラについているトリガー(人差し指で押すボタンですね)のイベント処理です。
"triggerdown"で押した時、"triggerup"で離した時のイベントを取得する事ができます。

main.js
this.el.addEventListener("triggerdown", function(e) {
	text.setAttribute("value", "TriggerDown!!");
});
this.el.addEventListener("triggerup", function(e) {
	text.setAttribute("value", "TriggerUp!!");
});

物体検知

"raycaster"を利用すると、コントローラが差す方向に伸びた線とそれが触れた物体を検知する事ができます。

コントローラのタグのアトリビュート"raycaster"に対し、"objects: .collidable; far: 5"と記述していた箇所がありました。
こうする事で、VR空間の物体に付けられたクラス名".collidable"が検知対象になります。

次に、検知対象のオブジェクトをVR空間に配置します。
"class"アトリビュートに、"collidable"というクラス名を付けている事に注目してください。
"raycaster"は、このクラス名に対して物体検知をします。(便利ですね!!)

index.html
<a-box class="collidable" position="-1.5 0.5 -2.5" rotation="0 20 0" color="#4CC3D9"></a-box>
<a-sphere class="collidable" position="0 0.5 -3" radius="0.5" color="#EF2D5E"></a-sphere>
<a-cylinder class="collidable" position="1.5 0.5 -2.5" radius="0.5" height="1" color="#FFC65D"></a-cylinder>

そして、JavaScript側で物体検知のイベント処理を実装します。(こちらも簡単です!!)
"raycaster-intersection"イベントは検知時、
"raycaster-intersection-cleared"イベントでは検知後(raycasterが外れた時)のイベントを取得する事ができます。

main.js
this.el.addEventListener("raycaster-intersection", function(e) {
	text.setAttribute("value", "Intersection:captured");
});
this.el.addEventListener("raycaster-intersection-cleared", function(e) {
	text.setAttribute("value", "Intersection:cleared");
});

全体のコード

最後に全体のコードを載せておきますね。

index.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8"/>
	<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
	<script src="https://unpkg.com/aframe-environment-component/dist/aframe-environment-component.min.js"></script>
	<script src="https://unpkg.com/three@0.131.3/examples/js/loaders/TDSLoader.js"></script>
	<script src="./main.js"></script>
</head>
<body>
	<a-scene stats loading-screen="dotsColor: gray; backgroundColor: lightgray">
		<!-- Controller -->
		<a-entity laser-controls="hand: left" raycaster="objects: .collidable; far: 5" vr-controller></a-entity>
		<a-entity laser-controls="hand: right" raycaster="objects: .collidable; far: 5" vr-controller></a-entity>
		<!-- Camera -->
		<a-camera id="my_camera">
			<a-cursor></a-cursor>
			<a-text id="my_text" value="***" position="0 -0.1 -2" scale="0.4 0.4 0.4" align="center" color="#ffffff"></a-text>
		</a-camera>
		<!-- Plane, Shapes -->
		<a-plane position="0 0.1 0" rotation="-90 0 0" width="2" height="2" color="#7BC8A4"></a-plane>
		<a-box class="collidable" position="-1.5 0.5 -2.5" rotation="0 20 0" color="#4CC3D9"></a-box>
		<a-sphere class="collidable" position="0 0.5 -3" radius="0.5" color="#EF2D5E"></a-sphere>
		<a-cylinder class="collidable" position="1.5 0.5 -2.5" radius="0.5" height="1" color="#FFC65D"></a-cylinder>
	</a-scene>
</body>
</html>
main.js
// Controller
AFRAME.registerComponent("vr-controller", {
	dependencies: ["raycaster"],// Important
	init: function(){
		console.log("vr-controller");
		const text = document.getElementById("my_text");
		text.setAttribute("value", "Controller is ready!?");
	
		this.el.addEventListener("gripdown", function(e) {
			text.setAttribute("value", "GripDown!!");
		});
		this.el.addEventListener("gripup", function(e) {
			text.setAttribute("value", "GripUp!!");
		});
		
		this.el.addEventListener("triggerdown", function(e) {
			text.setAttribute("value", "TriggerDown!!");
		});
		this.el.addEventListener("triggerup", function(e) {
			text.setAttribute("value", "TriggerUp!!");
		});
		
		this.el.addEventListener("raycaster-intersection", function(e) {
			text.setAttribute("value", "Intersection:captured");
		});
		
		this.el.addEventListener("raycaster-intersection-cleared", function(e) {
			text.setAttribute("value", "Intersection:cleared");
		});
	}
});

次回は、モデリングデータの読み込みと表示についてのお話です。