※本記事はVOYAGE GROUP VR室ブログの中からVRに関連する技術記事を寄稿いただいております。


最近糖質制限を始めたVR室の@daybysayです。


明日はPSVRの発売日!弊社ももちろん購入しているので、到着が楽しみです!


さて、今回は前回の続きとして、HTC Vive向けのアプリにインタラクションの機能を追加したいと思います。


やはりVRコンテンツは、プレイヤーの身体を用いたインタラクションでVRの世界に影響を与える体験が楽しいですよね。


ということで今回はコントローラを用いたインタラクションの実装についてご紹介します。


ちなみに今回開発したアプリは下記にあげてあります。


github.com


目次



Viveコントローラについて”>Viveコントローラについて


コントローラのインタフェースは次の画像の通りです。



基本的にはこれらのインタフェースと、コントローラ本体の位置、角度などを掛け合わせてインタラクションを構築することになります。


Viveコントローラのイベントを利用する”>Viveコントローラのイベントを利用する


Viveコントローラのイベントを使うには、SteamVRプラグイン内に用意されているSteamVR_TrackedControllerクラスを利用すると簡単です。


今回は下記のようなクラスを用意しました。

<span class="synStatement">using</span>UnityEngine;
<span class="synStatement">using</span>System.Collections;

<span class="synType">public</span><span class="synType">class</span>MY_TrackedController : MonoBehaviour {
SteamVR_TrackedController trackedController;

<span class="synType">void</span>Start () {
trackedController = gameObject.GetComponent&lt;SteamVR_TrackedController&gt;();

<span class="synStatement">if</span>(trackedController == <span class="synConstant">null</span>) {
trackedController = gameObject.AddComponent&lt;SteamVR_TrackedController&gt;();
}

trackedController.MenuButtonClicked += <span class="synStatement">new</span>ClickedEventHandler (DoMenuButtonClicked);
trackedController.MenuButtonUnclicked += <span class="synStatement">new</span>ClickedEventHandler (DoMenuButtonUnClicked);
trackedController.TriggerClicked += <span class="synStatement">new</span>ClickedEventHandler (DoTriggerClicked);
trackedController.TriggerUnclicked += <span class="synStatement">new</span>ClickedEventHandler (DoTriggerUnclicked);
trackedController.SteamClicked += <span class="synStatement">new</span>ClickedEventHandler (DoSteamClicked);
trackedController.PadClicked += <span class="synStatement">new</span>ClickedEventHandler (DoPadClicked);
trackedController.PadUnclicked += <span class="synStatement">new</span>ClickedEventHandler (DoPadClicked);
trackedController.PadTouched += <span class="synStatement">new</span>ClickedEventHandler (DoPadTouched);
trackedController.PadUntouched += <span class="synStatement">new</span>ClickedEventHandler (DoPadTouched);
trackedController.Gripped += <span class="synStatement">new</span>ClickedEventHandler (DoGripped);
trackedController.Ungripped += <span class="synStatement">new</span>ClickedEventHandler (DoUngripped);
}

<span class="synType">public</span><span class="synType">void</span>DoMenuButtonClicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoMenuButtonClicked"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoMenuButtonUnClicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoMenuButtonUnClicked"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoTriggerClicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoTriggerClicked"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoTriggerUnclicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoTriggerUnclicked"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoSteamClicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoSteamClicked"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoPadClicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoPadClicked"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoPadUnclicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoPadUnclicked"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoPadTouched(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoPadTouched"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoPadUntouched(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoPadUntouched"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoGripped(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoGripped"</span>);
}

<span class="synType">public</span><span class="synType">void</span>DoUngripped(<span class="synType">object</span>sender, ClickedEventArgs e) {
Debug.Log (<span class="synConstant">"DoUngripped"</span>);
}
}

コントローラのトリガーを引いた時と離した時にログが吐き出されるだけの非常にシンプルな実装です。SteamVR_TrackedControllerにシステムボタンのイベントは実装されていないようですが、openvr_api.csを見る限りはイベントの取得はできそうに見えます。


次に、このクラスをSteamVR_TrackedControllerクラスと共にViveのコントローラにセットします。



これで準備完了です。動かしてみましょう。



見づらくて恐縮ですが、ボタンを押すとログが流れています。


これでコントローラにイベントを介したインタラクションを実装できるようになりました。


SteamVR_TrackedControllerクラスの解説


余談ですがSteamVR_TrackedControllerクラス事態の解説です。


コントローラのイベントの取得方法はSteamVRプラグインに用意されているSteamVR_TrackedController.cs 内のUpdate関数を見ると、イベントの呼び出しがどうなっているかがわかりやすいです。


例えば下記の実装を見てみましょう

<span class="synComment">// Update is called once per frame</span>
<span class="synType">void</span>Update()
{
var system = OpenVR.System;
<span class="synStatement">if</span>(system != <span class="synConstant">null</span>&amp;&amp;system.GetControllerState(controllerIndex, <span class="synStatement">ref</span>controllerState))
{
<span class="synType">ulong</span>trigger = controllerState.ulButtonPressed &amp;(1UL &lt;&lt;((<span class="synType">int</span>)EVRButtonId.k_EButton_SteamVR_Trigger));
<span class="synStatement">if</span>(trigger &gt;<span class="synConstant">0L</span>&amp;&amp;!triggerPressed)
{
triggerPressed = <span class="synConstant">true</span>;
ClickedEventArgs e;
e.controllerIndex = controllerIndex;
e.flags = (<span class="synType">uint</span>)controllerState.ulButtonPressed;
e.padX = controllerState.rAxis0.x;
e.padY = controllerState.rAxis0.y;
OnTriggerClicked(e);

}

4行目はCVRSystemクラスのGetControllerStateメソッドに対して、コントローラに振られたIndexとコントローラの状態を受け取る変数(controllerState)を渡しており、最終的にcontrollerStateにコントローラの状態を表すUnsigned Longの値が代入されます。


そのcontrollerStateの値をビットフラグとして扱っており、各ビットを見ることで現在のコントローラの状態がどうなっているかを判定できるようになっています。


トリガーイベントの場合はEVRButtonIdのk_EButton_SteamVR_Triggerの値、つまり33ビット分シフトされたUnsigned Long型の1を用いて論理積をとっています。

つまり、後ろから34桁目のビットが1である場合、トリガーが押下されている状態を表しています。


ここで返ってくる値にすべてのボタンのステートが含まれているので、ボタンの同時押しなどをハンドリングしたい場合は、このOpenVR APIを直接使うことでシンプルに実装が可能になります。


物をつかむスクリプトの実装


それでは、コントローラを使って物をつかむ処理を実装していきましょう。


ここでは下記を行います。



  • つかむオブジェクトを見つける

  • オブジェクトをつかむ


つかむオブジェクトを見つける


今回はコントローラに接触しているオブジェクトをつかむ対象とします。


接触しているオブジェクトを見つけるには、コントローラとの衝突判定を用いて実現したいと思います。


先ほどのMY_TrackedControllerクラスに次の処理を追加しました。接触したことがわかりやすいよう接触したオブジェクトを削除する実装ににしました。

void OnTriggerEnter(Collider other) {
Destroy(other.gameObject);
}

OnTriggerEnter関数はColliderを持つオブジェクト同士であり、かつ最低片方にRigidBodyがセットされている場合に呼び出されます。otherには接触したオブジェクトがわたってくるので、コントローラが触れた対象を取得できます。


次に衝突対象のオブジェクトを配置しましょう。今回はCubeを利用します。



Cubeを置き、RigidBodyを設定しました。


次はコントローラ側の設定を変えます。デフォルトではColliderが存在しないのでとりあえずつけます。


BoxColliderを0.1サイズで用意しました。このときIs Triggerにチェックを入れるのを忘れないでください。



いったんこれで動かしてみましょう。



ちゃんと消えますね。これで接触判定と接触した対象のオブジェクトを取得できるようになりました。


オブジェクトをつかむ


次に、削除の処理をつかむ(相対位置を固定する)処理に変えていきます。


ここではコントローラとオブジェクトの相対位置を固定するのにFixed Jointを利用します。


スクリプトは以下のように実装しました。

<span class="synStatement">using</span>UnityEngine;
<span class="synStatement">using</span>System.Collections;

<span class="synType">public</span><span class="synType">class</span>MY_TrackedController : MonoBehaviour {
SteamVR_TrackedController trackedController;
GameObject grababbleObject;
FixedJoint joint;

<span class="synType">void</span>Start () {
trackedController = gameObject.GetComponent&lt;SteamVR_TrackedController&gt;();

<span class="synStatement">if</span>(trackedController == <span class="synConstant">null</span>) {
trackedController = gameObject.AddComponent&lt;SteamVR_TrackedController&gt;();
}

trackedController.TriggerClicked += <span class="synStatement">new</span>ClickedEventHandler (DoTriggerClicked);
trackedController.TriggerUnclicked += <span class="synStatement">new</span>ClickedEventHandler (DoTriggerUnclicked);

joint = gameObject.GetComponent&lt;FixedJoint&gt;();
}

<span class="synType">public</span><span class="synType">void</span>DoTriggerClicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
grab ();
}

<span class="synType">public</span><span class="synType">void</span>DoTriggerUnclicked(<span class="synType">object</span>sender, ClickedEventArgs e) {
release ();
}

<span class="synType">void</span>grab() {
<span class="synStatement">if</span>(grababbleObject == <span class="synConstant">null</span>|| joint.connectedBody != <span class="synConstant">null</span>) {
<span class="synStatement">return</span>;
}

joint.connectedBody = grababbleObject.GetComponent&lt;Rigidbody&gt;();
}

<span class="synType">void</span>release() {
<span class="synStatement">if</span>(joint.connectedBody == <span class="synConstant">null</span>) {
<span class="synStatement">return</span>;
}

joint.connectedBody = <span class="synConstant">null</span>;
}

<span class="synType">void</span>OnTriggerEnter(Collider other) {
grababbleObject = other.gameObject;
}

<span class="synType">void</span>OnTriggerExit(Collider other) {
grababbleObject = <span class="synConstant">null</span>;
}
}

OnTriggerEnter関数で接触しているオブジェクトを取得、メンバ変数に保持し、DoTriggerClickedで保持されたオブジェクトとコントローラの相対位置を固定することでつかむ処理を実装しています。


次にコントローラにFixedJointを追加します。



FixedJoint追加の際にRigidBodyコンポーネントが自動で追加されますが、このRigidBodyのUse Gravityはチェックを外し、Is Kinematicはチェックを入れてください。これをやっておかないと固定したオブジェクトの挙動が変な感じになります。


それではこれで動かしてみましょう。


オブジェクトをつかんで離すところまで実装できました!


物を投げてみる


実は上の実装では、つかんだ物オブジェクトは自由落下するだけで物を投げることができません。


オブジェクトを投げるためには、飛んで行くための力をかけてあげる必要があります。


オブジェクトを離した瞬間に、コントローラにかかっている速度と角速度をオブジェクトにかけてあげましょう。


MY_TrackedControllerクラスのrelease関数を下記のように修正します。

<span class="synType">void</span>release() {
<span class="synStatement">if</span>(joint.connectedBody == <span class="synConstant">null</span>) {
<span class="synStatement">return</span>;
}

Rigidbody rigidBody = joint.connectedBody;
joint.connectedBody = <span class="synConstant">null</span>;

var device = SteamVR_Controller.Input((<span class="synType">int</span>)trackedController.GetComponent&lt;SteamVR_TrackedObject&gt;().index);
rigidBody.velocity = device.velocity;
rigidBody.angularVelocity = device.angularVelocity;
rigidBody.maxAngularVelocity = rigidBody.angularVelocity.magnitude;
}

動かしてみます!



いい感じに飛んでいきましたね。これでつかんだものを投げられるようになりました!


まとめ



  • SteamVR_TrackedControlerを使うと簡単に実現できる

  • 物をつかむにはFixed Jointを使う

  • つかんだものを投げるにはコントローラにかかっている速度と角速度をオブジェクトにかける


今回はコントローラの使い方についてご紹介させていただきました。


次回は何を書くか未定です!


※元記事:HTC Vive向けにアプリケーションを開発する〜コントローラでインタラクション編〜 –VOYAGE GROUP VR室ブログ


©VOYAGE GROUP, inc. All Rights Reserved


Copyright ©2016 VR Inside All Rights Reserved.

情報提供元: VR Inside
記事名:「 HTC Vive向けにアプリケーションを開発する〜コントローラでインタラクション編〜