1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
|
---
title: WebVRでの VRコントローラーの使用
slug: Web/API/WebVR_API/Using_VR_controllers_with_WebVR
tags:
- Experimental
- Gamepad API
- Guide
- VR
- Virtual Reality
- WebGL
- WebVR
- controllers
translation_of: Web/API/WebVR_API/Using_VR_controllers_with_WebVR
---
<div>{{APIRef("WebVR API")}}</div>
<p class="summary">多くのWebVRハードウェアは、ヘッドセットとコントローラーがセットになっています。WebVRアプリにおいては、ヘッドセットとコントローラーは<a href="/ja/docs/Web/API/Gamepad_API">Gamepad API</a>を通じて接続されます。中でも、<a href="/ja/docs/Web/API/Gamepad_API#Experimental_Gamepad_extensions">Gamepad Extensions API</a>は、コントローラーの状態(<a href="/ja/docs/Web/API/GamepadPose">controller pose</a>)、触覚アクチュエータ(<a href="/ja/docs/Web/API/GamepadHapticActuator">haptic actuators</a>)などの情報を取得します。この記事では、その基礎となる部分を解説いたします。</p>
<h2 id="The_WebVR_API">The WebVR API</h2>
<p><a href="/ja/docs/Web/API/WebVR_API">WebVR API</a> は初期段階ではあるが、開発者がウェブベースのバーチャルリアリティー経験を生み出すことのできるとても興味深いウェブの新しい機能です。コンピュータとつながっているVRヘッドセット(VRディスプレイ)へのアクセスを与えることで,ディスプレイをスタートしたり、ストップする操作ができます.動きのデータ(例:方向や位置)へアクセスして得られたデータは,各アニメーションループのフレームごとにディスプレイをアップデートするためなどに使用されます。</p>
<p>この記事を読む前提として、Web VR API の基礎についてすでに知っていることを想定しています。 — もしまだ<a href="/ja/docs/Web/API/WebVR_API/Using_the_WebVR_API">Using the WebVR API</a>にを読んでいない場合には、まずはそちらを読んでみましょう.その記事の中では,ブラウザ側がハードウェアの設定をサポートしたり,設定を要求したりすることについて詳しく説明しています。</p>
<h2 id="The_Gamepad_API">The Gamepad API</h2>
<p><a href="/ja/docs/Web/API/Gamepad_API">Gamepad API</a> はよくサポートされたAPIであり, これを使用することでPCにつながっているゲームパッドやコントローラーに開発者がアクセスすることができるようになります。また、ウェブアプリケーションをゲームパッドやコントローラーを通じて操作することもできるようになります。基本としてGamepad APIは、ゲームパッドオブジェクトとしてつながっているコントローラーに対してアクセスの許可を与えます。そしてどのボタンが押されているか、軸がどの方向に向いているかなどの情報を取得するよう要求します。</p>
<p>Gamepad APIの基本的な使い方については、<a href="/ja/docs/Web/API/Gamepad_API/Using_the_Gamepad_API">Using the Gamepad API</a>や<a href="/ja/docs/Games/Techniques/Controls_Gamepad_API">Implementing controls using the Gamepad API</a>の中で詳しく知ることができます。</p>
<p>しかしながら,この記事では主に、位置、方向、触覚アクチュエーター(バイブレーション)などの高度なコントローラー情報へのアクセスのような、Gamepad Extensions APIで与えられたいくつかの新しい特徴に注目します。このAPIはとても新しく,Firefox 55+ BetaやFirefox Nightly のブラウザでのみデフォルトでWebVR APIがサポートされています。</p>
<h2 id="コントローラーの種類">コントローラーの種類</h2>
<p>VRハードウェアに付随するコントローラーには、2つの種類があります。</p>
<ul>
<li>6軸に対して自由度を持つコントローラーは位置と方向のデータを取得することができる。具体的には、コントローラーがVRシーンや動きや回転のある物体を操作することができる。例えば、HTC VIVEのコントローラーがそれにあたる。</li>
<li>3軸に対して自由度を持つコントローラーは、位置データは取得できないが方向のデータを取得することができる.例えば Google Daydreamのコントローラーである。具体的には、3D空間で異なる物体をレーザーポインターのように指し示すことはできるが、3D空間を動き回ることはできない。</li>
</ul>
<h2 id="コントローラーへのアクセス方法">コントローラーへのアクセス方法</h2>
<p>ここではいくつかのコードを紹介します。まず、Gamepad APIを使用してVRコントローラーへの基本的なアクセス方法を見ていきましょう。いくつかのおかしなニュアンスを心に留めておきましょう、それは後から調べる価値があるものです。</p>
<p>シンプルな例を紹介します。-<a href="https://github.com/mdn/webvr-tests/blob/master/vr-controller-basic-info/index.html">vr-controller-basic-info</a> のソースコード(<a href="https://mdn.github.io/webvr-tests/vr-controller-basic-info/">see it running live here also</a>)を御覧ください。この例はVRディスプレイやコンピューターと接続したゲームコントローラーへ情報を出力するシンプルなものです。</p>
<h3 id="ディスプレイの情報を取得">ディスプレイの情報を取得</h3>
<p>最初のコードです。</p>
<pre class="brush: js notranslate">var initialRun = true;
if(navigator.getVRDisplays && navigator.getGamepads) {
info.textContent = 'WebVR API and Gamepad API supported.'
reportDisplays();
} else {
info.textContent = 'WebVR API and/or Gamepad API not supported by this browser.'
}</pre>
<p>ここでは、<code>initialRun</code> というトラッキングの変数を使います。これは、「このページを初めてロードした」ことを示します。この点については、あとで詳しく述べます。次に、{{domxref("Navigator.getVRDisplays()")}} と {{domxref("Navigator.getGamepads()")}}メソッドがあるかないかをチェックして、WebVR と Gamepad APIs がサポートされているかどうかを検知します。もし、サポートされていれば、検知するプロセスをOFFにするために、カスタム機能である <code>reportDisplays()</code> を実行します。 <code>reportDisplays()</code> は、以下のような構成になっています。</p>
<pre class="brush: js notranslate">function reportDisplays() {
navigator.getVRDisplays().then(function(displays) {
console.log(displays.length + ' displays');
for(var i = 0; i < displays.length; i++) {
var cap = displays[i].capabilities;
// cap is a VRDisplayCapabilities object
var listItem = document.createElement('li');
listItem.innerHTML = '<strong>Display ' + (i+1) + '</strong>'
+ '<br>VR Display ID: ' + displays[i].displayId
+ '<br>VR Display Name: ' + displays[i].displayName
+ '<br>Display can present content: ' + cap.canPresent
+ '<br>Display is separate from the computer\'s main display: ' + cap.hasExternalDisplay
+ '<br>Display can return position info: ' + cap.hasPosition
+ '<br>Display can return orientation info: ' + cap.hasOrientation
+ '<br>Display max layers: ' + cap.maxLayers;
list.appendChild(listItem);
}
setTimeout(reportGamepads, 1000);
// For VR, controllers will only be active after their corresponding headset is active
});
}</pre>
<p>This function first uses the promise-based {{domxref("Navigator.getVRDisplays()")}} method, which resolves with an array containing {{domxref("VRDisplay")}} objects representing the connected displays. Next, it prints out each display's {{domxref("VRDisplay.displayId")}} and {{domxref("VRDisplay.displayName")}} values, and a number of useful values contained in the display's associated {{domxref("VRCapabilities")}} object. The most useful of these are {{domxref("VRCapabilities.hasOrientation","hasOrientation")}} and {{domxref("VRCapabilities.hasPosition","hasPosition")}}, which allow you to detect whether the device can return orientation and position data and set up your app accordingly.</p>
<p>The last line contained in this function is a {{domxref("WindowOrWorkerGlobalScope.setTimeout()")}} call, which runs the <code>reportGamepads()</code> function after a 1 second delay. Why do we need to do this? First of all, VR controllers will only be ready after their associated VR headset is active, so we need to invoke this after <code>getVRDisplays()</code> has been called and returned the display information. Second, the Gamepad API is much older than the WebVR API, and not promise-based. As you'll see later, the <code>getGamepads()</code> method is synchronous, and just returns the <code>Gamepad</code> objects immediately — it doesn't wait for the controller to be ready to report information. Unless you wait for a little while, returned information may not be accurate (at least, this is what we found in our tests).</p>
<h3 id="ゲームコントローラーの情報を取得">ゲームコントローラーの情報を取得</h3>
<p><code>reportGamepads()</code> 関数は、このような構成になっています。</p>
<pre class="brush: js notranslate">function reportGamepads() {
var gamepads = navigator.getGamepads();
console.log(gamepads.length + ' controllers');
for(var i = 0; i < gamepads.length; ++i) {
var gp = gamepads[i];
var listItem = document.createElement('li');
listItem.classList = 'gamepad';
listItem.innerHTML = '<strong>Gamepad ' + gp.index + '</strong> (' + gp.id + ')'
+ '<br>Associated with VR Display ID: ' + gp.displayId
+ '<br>Gamepad associated with which hand: ' + gp.hand
+ '<br>Available haptic actuators: ' + gp.hapticActuators.length
+ '<br>Gamepad can return position info: ' + gp.pose.hasPosition
+ '<br>Gamepad can return orientation info: ' + gp.pose.hasOrientation;
list.appendChild(listItem);
}
initialRun = false;
}</pre>
<p>This works in a similar manner to <code>reportDisplays()</code> — we get an array of {{domxref("Gamepad")}} objects using the non-promise-based <code>getGamepads()</code> method, then cycle through each one and print out information on each:</p>
<ul>
<li>The {{domxref("Gamepad.displayId")}} property is the same as the <code>displayId</code> of the headet the controller is associated with, and therefore useful for tying controller and headset information together.</li>
<li>The {{domxref("Gamepad.index")}} property is unique numerical index that identifies each connected controller.</li>
<li>{{domxref("Gamepad.hand")}} returns which hand the controller is expected to be held in.</li>
<li>{{domxref("Gamepad.hapticActuators")}} returns an array of the haptic actuators available in the controller. Here we are returning its length so we can see how many each has available.</li>
<li>Finally, we return {{domxref("GamepadPose.hasPosition")}} and {{domxref("GamepadPose.hasOrientation")}} to show whether the controller can return position and orientation data. This works just the same as for the displays, except that in the case of gamepads these values are available on the pose object, not the capabilities object.</li>
</ul>
<p>Note that we also gave each list item containing controller information a class name of <code>gamepad</code>. We'll explain what this is for later.</p>
<p>The last thing to do here is set the <code>initialRun</code> variable to <code>false</code>, as the initial run is now over.</p>
<h3 id="コントローラーのイベント">コントローラーのイベント</h3>
<p>To finish off this section, we'll look at the gamepad-associated events. There are two we need concern ourselves with — {{event("gamepadconnected")}} and {{event("gamepaddisconnected")}} — and it is fairly obvious what they do.</p>
<p>At the end of our example we first include the <code>removeGamepads()</code> function:</p>
<pre class="brush: js notranslate">function removeGamepads() {
var gpLi = document.querySelectorAll('.gamepad');
for(var i = 0; i < gpLi.length; i++) {
list.removeChild(gpLi[i]);
}
reportGamepads();
}</pre>
<p>This function simply grabs references to all list items with a class name of <code>gamepad</code>, and removes them from the DOM. Then it re-runs <code>reportGamepads()</code> to populate the list with the updated list of connected controllers.</p>
<p><code>removeGamepads()</code> will be run each time a gamepad is connected or disconnected, via the following event handlers:</p>
<pre class="brush: js notranslate">window.addEventListener('gamepadconnected', function(e) {
info.textContent = 'Gamepad ' + e.gamepad.index + ' connected.';
if(!initialRun) {
setTimeout(removeGamepads, 1000);
}
});
window.addEventListener('gamepaddisconnected', function(e) {
info.textContent = 'Gamepad ' + e.gamepad.index + ' disconnected.';
setTimeout(removeGamepads, 1000);
});</pre>
<p>We have <code>setTimeout()</code> calls in place here — like we did with the initialization code at the top of the script — to make sure that the gamepads are ready to report their information when <code>reportGamepads()</code> is called in each case.</p>
<p>But there's one more thing to note — you'll see that inside the <code>gamepadconnected</code> handler, the timeout is only run if <code>initialRun</code> is <code>false</code>. This is because if your gamepads are connected when the document first loads, <code>gamepadconnected</code> is fired once for each gamepad, therefore <code>removeGamepads()</code>/<code>reportGamepads()</code> will be run several times. This could lead to inaccurate results, therefore we only want to run <code>removeGamepads()</code> inside the <code>gamepadconnected</code> handler after the initial run, not during it. This is what <code>initialRun</code> is for.</p>
<h2 id="実際のデモの紹介">実際のデモの紹介</h2>
<p>実際のWebVRのデモで使用されたGamepad APIを見てみましょう。このデモは<a href="https://github.com/mdn/webvr-tests/tree/master/raw-webgl-controller-example">raw-webgl-controller-example</a> (<a href="https://mdn.github.io/webvr-tests/raw-webgl-controller-example/">see it live here also</a>).で見ることができます。</p>
<p>私達の<a href="https://github.com/mdn/webvr-tests/tree/master/raw-webgl-example">raw-webgl-example</a> (詳しくは <a href="/ja/docs/Web/API/WebVR_API/Using_the_WebVR_API">Using the WebVR API</a> を御覧ください。)と同じ方法で、このデモにおいても回転する3D立方体をレンダリングしています。また、これをVRディスプレイへ投影することもできます。</p>
<p>唯一の違いとしては、VRディスプレイへ投影モードでは、VRコントローラーを使って立方体を動かすことができます。(オリジナルのデモ動画では、VRヘッドセットを動かすことで、立方体を動かすことができる。)</p>
<p>以下に、このバージョンでのコードの違いをより詳しく紹介します。<a href="https://github.com/mdn/webvr-tests/blob/master/raw-webgl-controller-example/webgl-demo.js">webgl-demo.js</a>.を御覧ください。</p>
<h3 id="コントローラーデータへのアクセス">コントローラーデータへのアクセス</h3>
<p><code>drawVRScene()</code> 関数についてのコードの一部です。</p>
<pre class="brush: js notranslate">var gamepads = navigator.getGamepads();
var gp = gamepads[0];
if(gp) {
var gpPose = gp.pose;
var curPos = gpPose.position;
var curOrient = gpPose.orientation;
if(poseStatsDisplayed) {
displayPoseStats(gpPose);
}
}</pre>
<p> {{domxref("Navigator.getGamepads")}}を利用して、コントローラーが接続されました。また <code>gp</code> 変数の中に最初に認識したコントローラーを保存します。 デモでは、コントローラーを一つしか使用しないので、その他のコントローラーは無視します。</p>
<p>The next thing we do is to get the {{domxref("GamepadPose")}} object for the controller stored in gpPose (by querying {{domxref("Gamepad.pose")}}), and also store the current gamepad position and orientation for this frame in variables so they are easuy to access later. We also display the post stats for this frame in the DOM using the <code>displayPoseStats()</code> function. All of this is only done if <code>gp</code> actually has a value (if a gamepad is connected), which stops the demo erroring if we don't have our gamepad connected.</p>
<p>Slightly later in the code, you can find this block:</p>
<pre class="brush: js notranslate">if(gp && gpPose.hasPosition) {
mvTranslate([
0.0 + (curPos[0] * 15) - (curOrient[1] * 15),
0.0 + (curPos[1] * 15) + (curOrient[0] * 15),
-15.0 + (curPos[2] * 25)
]);
} else if(gp) {
mvTranslate([
0.0 + (curOrient[1] * 15),
0.0 + (curOrient[0] * 15),
-15.0
]);
} else {
mvTranslate([
0.0,
0.0,
-15.0
]);
}</pre>
<p>Here we alter the position of the cube on the screen according to the {{domxref("GamepadPose.position","position")}} and {{domxref("GamepadPose.orientation","orientation")}} data received from the connected controller. These values (stored in <code>curPos</code> and <code>curOrient</code>) are {{domxref("Float32Array")}}s containing the X, Y, and Z values (here we are just using [0] which is X, and [1] which is Y).</p>
<p>If the <code>gp</code> variable has a <code>Gamepad</code> object inside it and it can return position values (<code>gpPose.hasPosition</code>), indicating a 6DoF controller, we modify the cube position using position and orientation values. If only the former is true, indicating a 3DoF controller, we modify the cube position using the orientation values only. If there is no gamepad connected, we don't modify the cube position at all.</p>
<h3 id="コントローラーの姿勢データの表示">コントローラーの姿勢データの表示</h3>
<p><code>displayPoseStats()</code> 関数では、{{domxref("GamepadPose")}} オブジェクトのうちの表示したいすべての情報を取得することができます。そして、そのようなデータを表示するためのデモの中に存在するUI パネルに表示します。</p>
<pre class="brush: js notranslate">function displayPoseStats(pose) {
var pos = pose.position;
var orient = pose.orientation;
var linVel = pose.linearVelocity;
var linAcc = pose.linearAcceleration;
var angVel = pose.angularVelocity;
var angAcc = pose.angularAcceleration;
if(pose.hasPosition) {
posStats.textContent = 'Position: x ' + pos[0].toFixed(3) + ', y ' + pos[1].toFixed(3) + ', z ' + pos[2].toFixed(3);
} else {
posStats.textContent = 'Position not reported';
}
if(pose.hasOrientation) {
orientStats.textContent = 'Orientation: x ' + orient[0].toFixed(3) + ', y ' + orient[1].toFixed(3) + ', z ' + orient[2].toFixed(3);
} else {
orientStats.textContent = 'Orientation not reported';
}
linVelStats.textContent = 'Linear velocity: x ' + linVel[0].toFixed(3) + ', y ' + linVel[1].toFixed(3) + ', z ' + linVel[2].toFixed(3);
angVelStats.textContent = 'Angular velocity: x ' + angVel[0].toFixed(3) + ', y ' + angVel[1].toFixed(3) + ', z ' + angVel[2].toFixed(3);
if(linAcc) {
linAccStats.textContent = 'Linear acceleration: x ' + linAcc[0].toFixed(3) + ', y ' + linAcc[1].toFixed(3) + ', z ' + linAcc[2].toFixed(3);
} else {
linAccStats.textContent = 'Linear acceleration not reported';
}
if(angAcc) {
angAccStats.textContent = 'Angular acceleration: x ' + angAcc[0].toFixed(3) + ', y ' + angAcc[1].toFixed(3) + ', z ' + angAcc[2].toFixed(3);
} else {
angAccStats.textContent = 'Angular acceleration not reported';
}
}</pre>
<h2 id="まとめ">まとめ</h2>
<p>この記事は,WebVRアプリの中でVRコントローラーを使うためには,どのようにGamepad Extensionsを用いればよいのかというとても基本的な概念を解説したものです.実際のWebVRアプリの中では,VRコントローラーのボタンに紐付けられたコントローラーにより,おそらくより複雑なコントロールシステムをもたせることになるでしょう. そして,ディスプレイとコントローラーの両方の情報(位置や方向)を同期的にディスプレイへフィードバックするという複雑な処理を行うことになります.しかし,この記事でやりたかったのは,Gamepad Extensionsの中の純粋なGamepad Extensions部分を切り分けるということです.</p>
<h2 id="関連項目">関連項目</h2>
<ul>
<li><a href="/ja/docs/Web/API/WebVR_API">WebVR API</a></li>
<li><a href="/ja/docs/Web/API/Gamepad_API">Gamepad API</a></li>
<li><a href="/ja/docs/Web/API/WebVR_API/Using_the_WebVR_API">Using the WebVR API</a></li>
<li><a href="/ja/docs/Games/Techniques/Controls_Gamepad_API">Implementing controls using the Gamepad API</a></li>
</ul>
|