エンジニアとしての市場価値を測りませんか?PR

企業からあなたに合ったオリジナルのスカウトを受け取って、市場価値を測りましょう

31
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SteamVR2.0のInteractionシステムが超便利だから使ってみよう

Last updated at Posted at 2019-07-15

#はじめに
こんにちは、ZeniZeniです。
みなさんSteamVRのバージョンは何を使っていますか?SteamVRはバージョンが2.0になってから大幅に仕様が変わり、それを忌避して今もver1.xを使っている人は多いんじゃないんでしょうか。(僕もそうでした)

**しかし!**SteamVR2.0にはめちゃくちゃ便利なコンポーネント群があって、SteamVRを入れるだけで基本的なVRゲームのシステムを実装できます。

この記事では、SteamVR2.0での主要な便利コンポーネントの使い方を紹介していきます。(めちゃくちゃ種類があるので、少しずつ紹介を増やしていきます。)
こちらの公式ドキュメントにも詳しいことが載っているので、ぜひ読んでみてください。

コントローラのinput周りの仕様(binding UI)については、これまでにたくさん記事が出ているので、そちらを参照してください。
フレームシンセシスさんのサイト がわかりやすくておすすめです。

覚えておくべき単語

それからSteamVRにはあまり馴染みのない単語があって少々混乱するので、それらをここで紹介します。

名前                          役割             
Skeleton 骸骨・骨組みという単語ですが、SteamVRでは手のモデル(5本指に限らない)をSkeletonと呼びます。またViveコントローラのモデルをControllerと呼び、ControllerとSkeletonを二つ合わせてHandと呼びます。
Hover SteamVR独自の当たり判定のようなものです。空中に停止する・漂うという単語ですが、インタラクション対象に触れている状態を意味します。名詞的にコリダーと同様の意味でも使われます。
Snap 音を鳴らす・かみつくという単語ですが、オブジェクトを手で掴む際の、オブジェクトを手に引き寄せる行為を意味します。snap the object to the center of the handみたいな感じで出てきます。

#で、どう便利なんですか?

まずはSteamVR2.0を導入して、フレームシンセシスさんのサイトに書いてある通りに設定をしましょう。
その後、Asset/SteamVR/InteractionSystem/Samples/Interactions_Example.sceneを開きましょう。
するとこのようなシーンが開きます。
bandicam 2019-07-15 16-25-04-511.jpg

このシーンを起動すると、テレポート移動しながら、Steamが用意してくれる多様なInteraction Systemを体験できます。

弓矢や感覚的に押せるボタンがあったり
bandicam 2019-07-15 16-30-08-802.jpg

回せる円盤やレバーがあったりします。
bandicam 2019-07-15 16-30-19-271.jpg

ラジコンもありました。
bandicam 2019-07-15 16-31-56-666.jpg

もうここだけでも1時間は遊べますね!

このシーンからわかる通り、SteamVRには基本的な「物をつかむ」、「物を投げる」、「テレポートする」以外の多様な機能が標準実装されています。

さらにこれらはVRでなくても遊べるようになっているのがすごいんです。
SteamVR2.0にはfallbackという機能が付いていて、下図の2D Debugを押すとキーボードとマウスでVRと同様のことができます。
チーム開発でVR環境のない人もデバッグができるのは素晴らしいですね。
bandicam 2019-07-15 22-20-13-160.jpg

主要なコンポーネント

基本的には、Playerコンポーネントを持つクラスが存在し、インタラクション対象にはInteractableコンポーネントを付け、コントローラにはHandコンポーネントを付けます。
SteamVRのInteraction Systemはこれらを参照して、様々な命令を行っています。
Player.prefabがCore/Prefabsにあるので、それを使うのが簡単です。これはCameraRigのより発展的な物で、InteractiveSystemを使うために必要な設定がされています。

bandicam 2019-07-16 00-40-35-737.jpg

bandicam 2019-07-15 16-59-52-250.jpg
bandicam 2019-07-15 17-00-06-926.jpg
インスペクターの項目が多すぎてうへーっってなりますが、項目をひとつずつ簡単に説明します。

Player.cs

シングルトン的な扱いになっているので、シーンに二つ以上置けません。
GetComponentしなくてもどこからでも呼び出せます。(便利!)

基本的には他のクラスはこのPlayerコンポーネントを利用して、HandやCameraを参照します。
特にこのインスペクターをいじるということは少ないと思います。

##Interactable.cs
インタラクションしたいオブジェクトには必ずこのコンポーネントを付けてください。
このコンポーネントの様々なメソッドを利用して、複雑なインタラクションシステムを構成していきます。

変数名                                                 型                役割 
Activate Action Set On Attach   SteamVR_ActionSet SteamVR Inputに登録したAction Setを有効にします。
自前で作成したAction Setで操りたい場合はここにそのAction Setを設定します。
Hide Hand On Attach bool trueだと物を掴んだりするとき(Hand.AttachObject()が呼ばれるとき)に、コントローラも手のモデルも消します。逆に物を離す時(Hand.DettachObject()が呼ばれるとき)には出現します。
Hide Skeleton On Attach bool Hide Hand On Attachと違い手だけが消えるようになります。
Hide Skeleton On Attach bool Hide Hand On Attachと違いコントローラだけが消えるようになります。
Hand Animation On Pickup int 物を掴む際の手のアニメーションを指定します。手のアニメーターコントローラはint型のanimationStateという変数でアニメーションを切り替えていて、指定した値のアニメーションが物を掴む際に実行されます(後述)。 ...のはずなんですけど0以外を指定するとエラーがめちゃめちゃ出る。というか手の形を指定する場合はSteamVR_Skeleton_Poserを使うので(後述)、これどう使うのかわからない。サンプルはみんな0だし。
Set Range Of Motion On Pickup SkeletalMotionRangeChange 手の形をコントローラを握ってる形に合わせるか合わせないかを決めます。どうでもいいならNoneにします。
Use Hand Object Attachment Point bool オブジェクトを掴む際の手の位置を、Hand.ObjectAttachmentPointのTransformを参照するかHand自体のTransformを参照するかを決めます。SteamVR_Skeleton_Poserで手の位置と形を指定しているとfalseになることに注意。
Attach Ease In bool 物を掴む際に、オブジェクトがゆっくり手に収まるか、即座に手に収まるかを決めます。
Snap Attach Ease In Time float Attach Ease In がtrueのとき、オブジェクトが手に収まるまでの時間を指定します。
Snap Attach Ease In Completed bool Attach Ease In がtrueになっていて、オブジェクトがゆっくり移動している間はfalse。移動が完了するとtrueになります。それ以外の時にtrueになっているかfalseになっているかは不定(確認用だからいじることはない)
Hand Follow Transform bool falseのとき、物を掴むと、掴んだ瞬間のオブジェクトと手の相対距離のまま、オブジェクトが手に追従します。trueならばUse Hand Object Attachment Pointで指定したTransformの位置に手が来ます。SteamVR_Skeleton_Poserで手の位置と形を指定しているとtrue、false関係なくSteamVR_Skeleton_Poserで指定した手と位置と形になります。
Highlight On Hover bool Handがオブジェクトに触れているときに、オブジェクトにハイライトを付けるかを決めます。
Hide Highlight GameObject[] ここに設定された子オブジェクトはHighlightされなくなります。

Hand.cs

コントローラにつけるコンポーネントですね。

変数名                                                 型                役割 
Other Hand Hand 反対の手を設定します。
Hand Type SteamVR_Input_Sources Inputシステムでのコントローラの入力を取るかを指定します。
Tracked Object SteamVR_Behaviour_Pose 基本的に自身が入ります。
Grab Pinch Action SteamVR_Action_Boolean 物を掴む際に、Inputシステムのどの入力で掴むかを決めます。
Grab Grip Action SteamVR_Action_Boolean 物を掴む際に、Inputシステムのどの入力で掴むかを決めます。
Haptic Action SteamVR_Action_Vibration コントローラを振動させるときのOutアクションを指定します。
UI Interact Action SteamVR_Action_Boolean UIを操作する際に、Inputシステムのどの入力で操作するかを決めます。
Use Hover Sphere bool インタラクションする際の手の効果範囲(緑色)を使用するかしないかを決めます。falseにするとGizmoが消えます。
Hover Sphere Transform Transform インタラクションする際の手の効果範囲(緑色)の原点の座標をTransformで指定します。
Hover Sphere Radius float インタラクションする際の手の効果範囲(緑色)を設定します。
Hover Layer Mask float インタラクションしたくないものの属するlayerのチェックを外すことで、それらのオブジェクトに干渉しなくなります。
Hover Update Interval float オブジェクトに触れているかいないかの更新頻度を設定します。
Use Controller Hover Component bool インタラクションする際の手の効果範囲(青色)を使用するかしないかを決めます。falseにするとGizmoが消えます。
Controller Hover Component string インタラクションする際の手の効果範囲(緑色)の原点の座標をstringで指定します。なにゆえstring?と思うかもしれませんが、これはviveコントローラのモデル(SteamVR_RenderModel.csのついたもの)の子オブジェクトの任意の部位の座標を指定するためです。他に下図のようなものがあります。bandicam 2019-07-16 01-50-44-177.jpg
Controller Hover Radius float インタラクションする際の手の効果範囲(青色)を設定します。
Use Finger Joint Hover bool インタラクションする際の手の効果範囲(黄色)を使用するかしないかを決めます。falseにするとGizmoが消えます。
Finger Joint Hover SteamVR_Skeleton_JointIndexEnum インタラクションする際の手の効果範囲(黄色)の原点の座標を手のモデルの部位で指定します。
Finger Joint Hover Radius float インタラクションする際の手の効果範囲(黄色)を設定します。
Object Attachment Point Transform 物を掴む際む際の手の位置を指定します。Interactableコンポーネントの設定によっては全く使わないです。
No Steam VR Fallback Camera Camera Fallback用の機能
No Steam VR Fallback Max Distance No Item float Fallback用の機能
No Steam VR Fallback Max Distance With Item float Fallback用の機能
Show Debug Text bool trueだとコントローラの横に下図のようなデバッグ用テキストが出ます(便利)bandicam 2019-07-16 02-07-47-183.jpg
Spew Debug Text bool trueだとShow Debug TextのtextがEditerのConsoleに出力されます。
Show Debug Interactables bool trueだとインタラクション対象まで緑色の線が引かれます。Debug.DrawLineで出した線なのでSceneビューじゃないと確認できません。

主要な変数

それから頻繁に使われる変数を紹介します。

AttachmentFlags

Hand.csが持つ列挙型の変数です。掴む際の諸々の挙動を指定します。現状8種類存在していてビットフィールドで管理されています。
そのため初期化時には

private Hand.AttachmentFlags attachmentFlags = Hand.defaultAttachmentFlags & ( ~Hand.AttachmentFlags.SnapOnAttach ) & (~Hand.AttachmentFlags.DetachOthers) & (~Hand.AttachmentFlags.VelocityMovement);

というように論理演算子が使われています。

名前                                              ビットフィールド                      役割             
Snap On Attach 00000001 これを指定しておかないと、Interactable.csのAttachEaseInとHandFollowTransformが常にfalseのような挙動をします。
Detach Others 00000010 物を掴む際に、既に掴んでいるオブジェクトがあればすべて離します。
Detach From OtherHand 00000100 物を掴む際に、反対の手で掴んでいるオブジェクトも離します。
Parent To Hand 00001000 物を掴む際に、オブジェクトを手の子オブジェクトにします。
VelocityMovement 00010000 掴んだオブジェクトが他のコリダーを絶対に貫通しないようになります。そのため、下のTuen On Kinematicと一緒に指定するとコリダーを貫通するようになり、指定する意味がなくなります。これを指定するオブジェクトは必ずRigidbodyを持つ必要があります。
Turn On Kinematic 00100000 これを指定しているオブジェクトは必ずRigidbodyを持つ必要があり、掴んだ際にRigidbody.IsKinemeticがtrueになります。
Turn On Gravity 01000000 これを指定しているオブジェクトは必ずRigidbodyを持つ必要があり、掴んだ際にRigidbody.UseGravityがtrueになります。
Allow Sidegrade 10000000 どこにも使われていないのでよくわかっていないです。コメントではThe object is able to switch from a pinch grab to a grip grab. Decreases likelyhood of a good throw but also decreases likelyhood of accidental dropと書いてあります。

Hand.defaultAttachmentFlagsでは下のようになっています。

        public const AttachmentFlags defaultAttachmentFlags = AttachmentFlags.ParentToHand |
                                                              AttachmentFlags.DetachOthers |
                                                              AttachmentFlags.DetachFromOtherHand |
                                                              AttachmentFlags.TurnOnKinematic |
                                                              AttachmentFlags.SnapOnAttach;

GrabTypes

物を掴む際にどのボタンで掴むかを決めるための列挙型の変数です。None, Trigger, Pinch, Grip, Scripted の5種類が存在します。
詳細は後述します。

#インタラクションする方法
インスペクターに出てる機能を理解したところで、さっそくインタラクションできるオブジェクトを作ってみましょう。

SteamVRのInteractionシステムは、Handコンポーネントが、Interactiveコンポーネントに自身の状態(オブジェクトに触れているかどうか等)を伝えることで、様々な処理を行っています。

基本的なイベント

まず基本的な関数を紹介します。これらは特定の条件下で自動的に呼ばれます。

void OnHandHoverBegin(Hand hand)

Hover(SteamVR独自の当たり判定)がオブジェクトに触れた瞬間に1度だけ呼ばれます。
OnColliderEnterみたいなものですね。

void OnHandHoverEnd(Hand hand)

Hoverがオブジェクトから離れた瞬間に1度だけ呼ばれます。

void OnHandHoverStay(Hand hand)

Hoverがオブジェクトに触れている間常に呼ばれます。

void OnAttachedToHand(Hand hand)

Hand.AttachObjectでこのオブジェクトがAttachされたときに1度だけ呼ばれます。

void OnDetachedFromHand(Hand hand)

Hand.DetachObjectでこのオブジェクトがDetachされたときに1度だけ呼ばれます。

void HandAttachedUpdate(Hand hand)

オブジェクトがアタッチされている間常に呼ばれます。

void OnHandFocusAcquired(Hand hand)

前提としてオブジェクトは複数アタッチでき、GameObjectのリストattachedobjectsに順にaddされるのですが、一番最後にアタッチされたオブジェクトはHand.currentAttachedObjectで取得できます。このcurrentAttachObjectがDetachされたとき、その一つ前にAttachされたオブジェクトで1度だけ呼ばれます。

void OnHandFocusLost(Hand hand)

新しくオブジェクトがAttachされるとき、その時点で一番新しくAttachされたオブジェクト(currentAttachObject)で1度だけ呼ばれます。

その他のよく使われる関数

そのほかに、頻繁に使われる便利な関数とその使い方を紹介します。

void Hand.HoverLock(Interactable interactable)

この関数を呼ぶと、引数に入れたInteractableを持つオブジェクトでしか上記の関数が呼ばれなくなります。
オブジェクトを掴んでいる間は他のオブジェクトに干渉したくないときに使います。

void Hand.HoverUnlock(Interactable interactable)

HoverLockでの処理をなかったことにし、他のオブジェクトでも上記の関数が呼ばれるようになります。
うっかり呼び忘れると何も反応しなくなります。

GrabTypes Hand.GetGrabStarting(GrabTypes explicitType = GrabTypes.None)

SteamVR2.0では物を掴む時の入力はトリガーかグリップと設定しています。
この関数は二種類の入力の始まり(ボタンを押す)を検知する関数です。
引数に何も渡さないと、トリガーが押されるとGrabTypes.Pinch、グリップが押されるとGrabTypes.Gripを1度だけ返します。どちらでもなければGrabTypes.Noneを返します。。
引数にGrabTypes.Pinchが渡されていれば、トリガーが押されるとGrabTypes.Pinchを1度だけ返し、それ以外の時はGrabTypes.Noneを返します。
引数にGrabTypes.Gripが渡されていれば、グリップが押されるとGrabTypes.Gripを1度だけ返し、それ以外の時はGrabTypes.Noneを返します。

GrabTypes Hand.GetGrabEnding(GrabTypes explicitType = GrabTypes.None)

GetGrabEndingは、GetGrabStartingの入力のおわり(ボタンを離す)版です。

##使い方

例として、SteamVR側がInteractableExample.csを作成しており、それを参考にするとよいと思います。

InteractableExample
//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Demonstrates how to create a simple interactable object
//
//=============================================================================

using UnityEngine;
using System.Collections;

namespace Valve.VR.InteractionSystem.Sample
{
	//-------------------------------------------------------------------------
	[RequireComponent( typeof( Interactable ) )]
	public class InteractableExample : MonoBehaviour
    {
        private TextMesh generalText;
        private TextMesh hoveringText;
        private Vector3 oldPosition;
		private Quaternion oldRotation;

		private float attachTime;

        
		private Hand.AttachmentFlags attachmentFlags = Hand.defaultAttachmentFlags & ( ~Hand.AttachmentFlags.SnapOnAttach ) & (~Hand.AttachmentFlags.DetachOthers) & (~Hand.AttachmentFlags.VelocityMovement);

        private Interactable interactable;

		//-------------------------------------------------
		void Awake()
		{
			var textMeshs = GetComponentsInChildren<TextMesh>();
            generalText = textMeshs[0];
            hoveringText = textMeshs[1];

            generalText.text = "No Hand Hovering";
            hoveringText.text = "Hovering: False";

            interactable = this.GetComponent<Interactable>();
		}

        //-------------------------------------------------
        // Called when a Hand starts hovering over this object
        //-------------------------------------------------
        private void OnHandHoverBegin( Hand hand )
		{
			generalText.text = "Hovering hand: " + hand.name;
		}

		//-------------------------------------------------
		// Called when a Hand stops hovering over this object
		//-------------------------------------------------
		private void OnHandHoverEnd( Hand hand )
		{
			generalText.text = "No Hand Hovering";
		}

		//-------------------------------------------------
		// Called every Update() while a Hand is hovering over this object
		//-------------------------------------------------
		private void HandHoverUpdate( Hand hand )
		{
            GrabTypes startingGrabType = hand.GetGrabStarting();//vive controllerの場合、トリガーかグリップボタンを押すとそれぞれ、Pinch、Gripになります。
            bool isGrabEnding = hand.IsGrabEnding(this.gameObject);//このオブジェクトをAttachした状態で、トリガーかグリップボタンを押すのをやめるとtrueになります。

            if (interactable.attachedToHand == null && startingGrabType != GrabTypes.None)
            {
                // Save our position/rotation so that we can restore it when we detach
                oldPosition = transform.position;
                oldRotation = transform.rotation;

                // Call this to continue receiving HandHoverUpdate messages,
                // and prevent the hand from hovering over anything else
                hand.HoverLock(interactable);

                // Attach this object to the hand
                hand.AttachObject(gameObject, startingGrabType, attachmentFlags);
            }
            else if (isGrabEnding)
            {
                // Detach this object from the hand
                hand.DetachObject(gameObject);

                // Call this to undo HoverLock
                hand.HoverUnlock(interactable);

                // Restore position/rotation
                transform.position = oldPosition;
                transform.rotation = oldRotation;
            }
		}

		//-------------------------------------------------
		// Called when this GameObject becomes attached to the hand
		//-------------------------------------------------
		private void OnAttachedToHand( Hand hand )
        {
            generalText.text = string.Format("Attached: {0}", hand.name);
            attachTime = Time.time;
		}
        
		//-------------------------------------------------
		// Called when this GameObject is detached from the hand
		//-------------------------------------------------
		private void OnDetachedFromHand( Hand hand )
		{
            generalText.text = string.Format("Detached: {0}", hand.name);
		}

		//-------------------------------------------------
		// Called every Update() while this GameObject is attached to the hand
		//-------------------------------------------------
		private void HandAttachedUpdate( Hand hand )
		{
            generalText.text = string.Format("Attached: {0} :: Time: {1:F2}", hand.name, (Time.time - attachTime));
		}

        private bool lastHovering = false;
        private void Update()
        {
            if (interactable.isHovering != lastHovering) //save on the .tostrings a bit
            {
                hoveringText.text = string.Format("Hovering: {0}", interactable.isHovering);
                lastHovering = interactable.isHovering;
            }
        }

		//-------------------------------------------------
		// Called when this attached GameObject becomes the primary attached object
		//-------------------------------------------------
		private void OnHandFocusAcquired( Hand hand )
		{
		}


		//-------------------------------------------------
		// Called when another attached GameObject becomes the primary attached object
		//-------------------------------------------------
		private void OnHandFocusLost( Hand hand )
		{
		}
	}
}

長くなってしまったので、ボタンや弓矢といったInteractionシステムは別の記事で紹介します。

#開発、執筆にあたり、下記のサイト様を参考、引用させていただきました。

  1. フレームシンセシス
  2. SteamVR Interaction System from The Lab
31
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Qiita Advent Calendar is held!

Qiita Advent Calendar is an article posting event where you post articles by filling a calendar 🎅

Some calendars come with gifts and some gifts are drawn from all calendars 👀

Please tie the article to your calendar and let's enjoy Christmas together!

31
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?