Unity & Vive: Tutorial

  • Tutorial

I decided on vacation to study development for Vive in Unity3D. Googled a couple of examples and began to try, but for some reason it did not work. Having begun to understand in more detail and found out that Valve rolled out recently updating of a plug-in for Unity3D - the new, strongly remade version. A couple of principal innovations appeared in it that made the old tutorials irrelevant. I decided to write a new one



We need Unity> = 5.4.0 and a new SteamVR Plugin ( GitHub )


There are three useful pdfs in the plugin itself.


\ Assets \ SteamVR \ SteamVR Unity Plugin.pdf
\ Assets \ SteamVR \ SteamVR Unity Plugin - Input System.pdf
\ Assets \ SteamVR \ InteractionSystem \ InteractionSystem.pdf


And two examples:


\ Assets \ SteamVR \ Simple Sample.unity
\ Assets \ SteamVR \ InteractionSystem \ Samples \ Interactions_Example.unity


The plugin supports imitation mode - it turns on if the helmet is not turned on



Well, now step by step;


  1. Create a new project on the 3D template with the SteamVR Plugin plugin


  2. We agree with the settings



  3. Now the key point - you need to configure management. Select the menu item Window \ SteamVR Imput



  4. Unity will ask about the missing actions.json and offer to copy the sample file (it lies in \ Assets \ SteamVR \ Input \ ExampleJSON) - I advise you to agree.



    By json-files it can be seen that the plugin is designed not only for Vive, but also for Oculus and Windows MR, as well as new knuckle controllers. There are major changes to this.


  5. In the window that opens, just click "Save and generate"



  6. Now you need to add the Player (Player from \ Assets \ SteamVR \ InteractionSystem \ Core \ Prefabs) to the Stage and delete the Main Camera



    that the imitation mode would work - it must be enabled in the Player properties



    It is also useful to copy the \ Assets \ SteamVR \ InteractionSystem \ Core \ Icons folder to \ Assets and rename it to Gizmos


  7. You can click Play - but only a fake hand in the simulation will not work, you need to copy the script from here and hang it on Player-> NoSteamVRFallbackObjects-> FallbackHand



Script code for a fake hand
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
publicclassVrSimulatorHandFixer156 : SteamVR_Behaviour_Pose
{
    Valve.VR.InteractionSystem.Hand _hand;
    protectedoverridevoidStart()
    {
        base.Start();
        _hand = this.gameObject.GetComponent<Valve.VR.InteractionSystem.Hand>();
        _hand.handType = SteamVR_Input_Sources.RightHand;
            GameObject broHand = GameObject.Instantiate(_hand.gameObject);
            Destroy(broHand.GetComponent<VrSimulatorHandFixer156>());
            broHand.SetActive(false);
        _hand.otherHand = broHand.GetComponent<Valve.VR.InteractionSystem.Hand>();
        _hand.otherHand.handType = SteamVR_Input_Sources.LeftHand;
        var spoofMouse = new SpoofMouseAction();
        _hand.grabGripAction = spoofMouse;
        spoofMouse.InitializeDictionariesExposed(_hand.handType);
        this.poseAction = new Poser_SteamVR_Action_Pose();
    }
    protectedoverridevoidOnEnable()
    {
    }
    protectedoverridevoidUpdate()
    {
        _hand.grabGripAction.UpdateValue(SteamVR_Input_Sources.RightHand);
    }
    protectedoverridevoidOnDisable()
    {
    }
    protectedoverridevoidCheckDeviceIndex()
    {
    }
    //----------------------------------------------------------------------------------------------classSpoofMouseAction : SteamVR_Action_Boolean
    {
        publicSpoofMouseAction()
        {
        }
        publicvoidInitializeDictionariesExposed(SteamVR_Input_Sources source)
        {
            try
            {
                InitializeDictionaries(source);
            }
            catch(System.Exception e)
            {
            }
        }
        protectedoverridevoidInitializeDictionaries(SteamVR_Input_Sources source)
        {
            base.InitializeDictionaries(source);
            onStateDown.Add(source, null);
            onStateUp.Add(source, null);
            actionData.Add(source, new InputDigitalActionData_t());
            lastActionData.Add(source, new InputDigitalActionData_t());
        }
        publicoverridevoidUpdateValue(SteamVR_Input_Sources inputSource)
        {
            lastActionData[inputSource] = actionData[inputSource];
            tempActionData.bState = Input.GetMouseButton(0) || Input.GetMouseButtonDown(0);
            tempActionData.bChanged = Input.GetMouseButtonDown(0) || Input.GetMouseButtonUp(0);
            tempActionData.bActive = true;
            //tempActionData.fUpdateTime//tempActionData.act
            actionData[inputSource] = tempActionData;
            changed[inputSource] = tempActionData.bChanged;
            active[inputSource] = tempActionData.bActive;
            activeOrigin[inputSource] = tempActionData.activeOrigin;
            updateTime[inputSource] = Time.time;// tempActionData.fUpdateTime;if (changed[inputSource])
                lastChanged[inputSource] = Time.time;
            if (onStateDown[inputSource] != null && GetStateDown(inputSource))
                onStateDown[inputSource].Invoke(this);
            if (onStateUp[inputSource] != null && GetStateUp(inputSource))
                onStateUp[inputSource].Invoke(this);
            if (onChange[inputSource] != null && GetChanged(inputSource))
                onChange[inputSource].Invoke(this);
            if (onUpdate[inputSource] != null)
                onUpdate[inputSource].Invoke(this);
            if (onActiveChange[inputSource] != null && lastActionData[inputSource].bActive != active[inputSource])
                onActiveChange[inputSource].Invoke(this, active[inputSource]);
        }
    }
        classPoser_SteamVR_Action_Pose : SteamVR_Action_Pose
    {
        publicoverrideboolGetActive(SteamVR_Input_Sources inputSource)
        {
            returnfalse;
        }
    }   
}

In VR mode with controllers on, they will be visible



  1. We added VR, but we can't even move. In the plugin there is a teleportation implementation.
    To enable it, you need to add to the Teleporting scene from \ Assets \ SteamVR \ InteractionSystem \ Teleport \ Prefabs
    and place TeleportPoint from there as well.



    You can also teleport in imitation with the T key.


  2. You can create a teleportation surface - Create a surface (plane) and hang the TeleportArea.cs script from \ Assets \ SteamVR \ InteractionSystem \ Teleport \ Scripts on it



  3. Let's try interaction with objects - create a Cube and hang Interactable.cs script from \ Assets \ SteamVR \ InteractionSystem \ Core \ Scripts on
    it, it is now highlighted, but nothing happens to it



  4. We need to register the interaction - create a new script for Cube



Code for interaction
usingSystem.Collections;
usingSystem.Collections.Generic;
using UnityEngine;
using Valve.VR.InteractionSystem;
publicclass NewBehaviourScript : MonoBehaviour {
    private Hand.AttachmentFlags attachmentFlags = Hand.defaultAttachmentFlags & (~Hand.AttachmentFlags.SnapOnAttach) & (~Hand.AttachmentFlags.DetachOthers) & (~Hand.AttachmentFlags.VelocityMovement);
    private Interactable interactable;
    // Use this for initialization
    voidStart () {
        interactable = this.GetComponent<Interactable>();
    }
    private void HandHoverUpdate(Hand hand)
    {
        GrabTypes startingGrabType = hand.GetGrabStarting();
        bool isGrabEnding = hand.IsGrabEnding(this.gameObject);
        if (startingGrabType != GrabTypes.None)
        {
            // Call this tocontinue receiving HandHoverUpdate messages,
            // and prevent the hand from hovering over anything else
            hand.HoverLock(interactable);
            // Attach this objectto the hand
            hand.AttachObject(gameObject, startingGrabType, attachmentFlags);
        }
        elseif (isGrabEnding)
        {
            // Detach this objectfrom the hand
            hand.DetachObject(gameObject);
            // Call this to undo HoverLock
            hand.HoverUnlock(interactable);
        }
    }
}


More details about the interaction can be found in the examples for the plugin, in particular in \ Assets \ SteamVR \ InteractionSystem \ Samples \ Scripts \ InteractableExample.cs


And we will further try to do what is not in the examples - add new actions.


  1. Open the script Player.cs and add fields


        [SteamVR_DefaultAction("PlayerMove", "default")]
        public SteamVR_Action_Vector2 a_move;
        [SteamVR_DefaultAction("PlayerRotate", "default")]
        public SteamVR_Action_Vector2 a_rotate;
        [SteamVR_DefaultAction("MenuClick", "default")]
        public SteamVR_Action_Boolean a_menu;

    Valid types for return values ​​can be found in \ Assets \ SteamVR \ Input



And the Update method:


privatevoidUpdate()
        {
            bool st = a_menu.GetStateDown(SteamVR_Input_Sources.Any);
            if (st)
            {
                this.transform.position = new Vector3(0, 0, 0);
            }
            else
            {
                Camera camera = this.GetComponentInChildren<Camera>();
                Quaternion cr = Quaternion.Euler(0, 0, 0);
                if (camera != null)
                {
                    Vector2 r = a_rotate.GetAxis(SteamVR_Input_Sources.RightHand);
                    Quaternion qp = this.transform.rotation;
                    qp.eulerAngles += new Vector3(0, r.x, 0);
                    this.transform.rotation = qp;
                    cr = camera.transform.rotation;
                }
                Vector2 m = a_move.GetAxis(SteamVR_Input_Sources.LeftHand);
                m = Quaternion.Euler(0, 0, -cr.eulerAngles.y) * m;
                this.transform.position += new Vector3(m.x / 10, 0, m.y / 10);
            }
        }

Full code Player.cs
//======= Copyright (c) Valve Corporation, All rights reserved. ===============//// Purpose: Player interface used to query HMD transforms and VR hands////=============================================================================using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespaceValve.VR.InteractionSystem
{
    //-------------------------------------------------------------------------// Singleton representing the local VR player/user, with methods for getting// the player's hands, head, tracking origin, and guesses for various properties.//-------------------------------------------------------------------------publicclassPlayer : MonoBehaviour
    {
        [Tooltip( "Virtual transform corresponding to the meatspace tracking origin. Devices are tracked relative to this." )]
        public Transform trackingOriginTransform;
        [Tooltip( "List of possible transforms for the head/HMD, including the no-SteamVR fallback camera." )]
        public Transform[] hmdTransforms;
        [Tooltip( "List of possible Hands, including no-SteamVR fallback Hands." )]
        public Hand[] hands;
        [Tooltip( "Reference to the physics collider that follows the player's HMD position." )]
        public Collider headCollider;
        [Tooltip( "These objects are enabled when SteamVR is available" )]
        public GameObject rigSteamVR;
        [Tooltip( "These objects are enabled when SteamVR is not available, or when the user toggles out of VR" )]
        public GameObject rig2DFallback;
        [Tooltip( "The audio listener for this player" )]
        public Transform audioListener;
        publicbool allowToggleTo2D = true;
        [SteamVR_DefaultAction("PlayerMove", "default")]
        public SteamVR_Action_Vector2 a_move;
        [SteamVR_DefaultAction("PlayerRotate", "default")]
        public SteamVR_Action_Vector2 a_rotate;
        [SteamVR_DefaultAction("MenuClick", "default")]
        public SteamVR_Action_Boolean a_menu;
        //-------------------------------------------------// Singleton instance of the Player. Only one can exist at a time.//-------------------------------------------------privatestatic Player _instance;
        publicstatic Player instance
        {
            get
            {
                if ( _instance == null )
                {
                    _instance = FindObjectOfType<Player>();
                }
                return _instance;
            }
        }
        //-------------------------------------------------// Get the number of active Hands.//-------------------------------------------------publicint handCount
        {
            get
            {
                int count = 0;
                for ( int i = 0; i < hands.Length; i++ )
                {
                    if ( hands[i].gameObject.activeInHierarchy )
                    {
                        count++;
                    }
                }
                return count;
            }
        }
        //-------------------------------------------------// Get the i-th active Hand.//// i - Zero-based index of the active Hand to get//-------------------------------------------------public Hand GetHand(int i )
        {
            for ( int j = 0; j < hands.Length; j++ )
            {
                if ( !hands[j].gameObject.activeInHierarchy )
                {
                    continue;
                }
                if ( i > 0 )
                {
                    i--;
                    continue;
                }
                return hands[j];
            }
            returnnull;
        }
        //-------------------------------------------------public Hand leftHand
        {
            get
            {
                for ( int j = 0; j < hands.Length; j++ )
                {
                    if ( !hands[j].gameObject.activeInHierarchy )
                    {
                        continue;
                    }
                    if ( hands[j].handType != SteamVR_Input_Sources.LeftHand)
                    {
                        continue;
                    }
                    return hands[j];
                }
                returnnull;
            }
        }
        //-------------------------------------------------public Hand rightHand
        {
            get
            {
                for ( int j = 0; j < hands.Length; j++ )
                {
                    if ( !hands[j].gameObject.activeInHierarchy )
                    {
                        continue;
                    }
                    if ( hands[j].handType != SteamVR_Input_Sources.RightHand)
                    {
                        continue;
                    }
                    return hands[j];
                }
                returnnull;
            }
        }
        //-------------------------------------------------// Get Player scale. Assumes it is scaled equally on all axes.//-------------------------------------------------publicfloat scale
        {
            get
            {
                return transform.lossyScale.x;
            }
        }
        //-------------------------------------------------// Get the HMD transform. This might return the fallback camera transform if SteamVR is unavailable or disabled.//-------------------------------------------------public Transform hmdTransform
        {
            get
            {
                if (hmdTransforms != null)
                {
                    for (int i = 0; i < hmdTransforms.Length; i++)
                    {
                        if (hmdTransforms[i].gameObject.activeInHierarchy)
                            return hmdTransforms[i];
                    }
                }
                returnnull;
            }
        }
        //-------------------------------------------------// Height of the eyes above the ground - useful for estimating player height.//-------------------------------------------------publicfloat eyeHeight
        {
            get
            {
                Transform hmd = hmdTransform;
                if ( hmd )
                {
                    Vector3 eyeOffset = Vector3.Project( hmd.position - trackingOriginTransform.position, trackingOriginTransform.up );
                    return eyeOffset.magnitude / trackingOriginTransform.lossyScale.x;
                }
                return0.0f;
            }
        }
        //-------------------------------------------------// Guess for the world-space position of the player's feet, directly beneath the HMD.//-------------------------------------------------public Vector3 feetPositionGuess
        {
            get
            {
                Transform hmd = hmdTransform;
                if ( hmd )
                {
                    return trackingOriginTransform.position + Vector3.ProjectOnPlane( hmd.position - trackingOriginTransform.position, trackingOriginTransform.up );
                }
                return trackingOriginTransform.position;
            }
        }
        //-------------------------------------------------// Guess for the world-space direction of the player's hips/torso. This is effectively just the gaze direction projected onto the floor plane.//-------------------------------------------------public Vector3 bodyDirectionGuess
        {
            get
            {
                Transform hmd = hmdTransform;
                if ( hmd )
                {
                    Vector3 direction = Vector3.ProjectOnPlane( hmd.forward, trackingOriginTransform.up );
                    if ( Vector3.Dot( hmd.up, trackingOriginTransform.up ) < 0.0f )
                    {
                        // The HMD is upside-down. Either// -The player is bending over backwards// -The player is bent over looking through their legs
                        direction = -direction;
                    }
                    return direction;
                }
                return trackingOriginTransform.forward;
            }
        }
        //-------------------------------------------------voidAwake()
        {
            SteamVR.Initialize(true); //force openvrif ( trackingOriginTransform == null )
            {
                trackingOriginTransform = this.transform;
            }
        }
        //-------------------------------------------------private IEnumerator Start()
        {
            _instance = this;
            while (SteamVR_Behaviour.instance.forcingInitialization)
                yieldreturnnull;
            if ( SteamVR.instance != null )
            {
                ActivateRig( rigSteamVR );
            }
            else
            {
#if !HIDE_DEBUG_UI
                ActivateRig( rig2DFallback );
#endif
            }
        }
        privatevoidUpdate()
        {
            bool st = a_menu.GetStateDown(SteamVR_Input_Sources.Any);
            if (st)
            {
                this.transform.position = new Vector3(0, 0, 0);
            }
            else
            {
                Camera camera = this.GetComponentInChildren<Camera>();
                Quaternion cr = Quaternion.Euler(0, 0, 0);
                if (camera != null)
                {
                    Vector2 r = a_rotate.GetAxis(SteamVR_Input_Sources.RightHand);
                    Quaternion qp = this.transform.rotation;
                    qp.eulerAngles += new Vector3(0, r.x, 0);
                    this.transform.rotation = qp;
                    cr = camera.transform.rotation;
                }
                Vector2 m = a_move.GetAxis(SteamVR_Input_Sources.LeftHand);
                m = Quaternion.Euler(0, 0, -cr.eulerAngles.y) * m;
                this.transform.position += new Vector3(m.x / 10, 0, m.y / 10);
            }
        }
        //-------------------------------------------------voidOnDrawGizmos()
        {
            if ( this != instance )
            {
                return;
            }
            //NOTE: These gizmo icons don't work in the plugin since the icons need to exist in a specific "Gizmos"//      folder in your Asset tree. These icons are included under Core/Icons. Moving them into a//      "Gizmos" folder should make them work again.
            Gizmos.color = Color.white;
            Gizmos.DrawIcon( feetPositionGuess, "vr_interaction_system_feet.png" );
            Gizmos.color = Color.cyan;
            Gizmos.DrawLine( feetPositionGuess, feetPositionGuess + trackingOriginTransform.up * eyeHeight );
            // Body direction arrow
            Gizmos.color = Color.blue;
            Vector3 bodyDirection = bodyDirectionGuess;
            Vector3 bodyDirectionTangent = Vector3.Cross( trackingOriginTransform.up, bodyDirection );
            Vector3 startForward = feetPositionGuess + trackingOriginTransform.up * eyeHeight * 0.75f;
            Vector3 endForward = startForward + bodyDirection * 0.33f;
            Gizmos.DrawLine( startForward, endForward );
            Gizmos.DrawLine( endForward, endForward - 0.033f * ( bodyDirection + bodyDirectionTangent ) );
            Gizmos.DrawLine( endForward, endForward - 0.033f * ( bodyDirection - bodyDirectionTangent ) );
            Gizmos.color = Color.red;
            int count = handCount;
            for ( int i = 0; i < count; i++ )
            {
                Hand hand = GetHand( i );
                if ( hand.handType == SteamVR_Input_Sources.LeftHand)
                {
                    Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_left_hand.png" );
                }
                elseif ( hand.handType == SteamVR_Input_Sources.RightHand)
                {
                    Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_right_hand.png" );
                }
                else
                {
                    /*
                    Hand.HandType guessHandType = hand.currentHandType;
                    if ( guessHandType == Hand.HandType.Left )
                    {
                        Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_left_hand_question.png" );
                    }
                    else if ( guessHandType == Hand.HandType.Right )
                    {
                        Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_right_hand_question.png" );
                    }
                    else
                    {
                        Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_unknown_hand.png" );
                    }
                    */
                }
            }
        }
        //-------------------------------------------------publicvoidDraw2DDebug()
        {
            if ( !allowToggleTo2D )
                return;
            if ( !SteamVR.active )
                return;
            int width = 100;
            int height = 25;
            int left = Screen.width / 2 - width / 2;
            int top = Screen.height - height - 10;
            string text = ( rigSteamVR.activeSelf ) ? "2D Debug" : "VR";
            if ( GUI.Button( new Rect( left, top, width, height ), text ) )
            {
                if ( rigSteamVR.activeSelf )
                {
                    ActivateRig( rig2DFallback );
                }
                else
                {
                    ActivateRig( rigSteamVR );
                }
            }
        }
        //-------------------------------------------------privatevoidActivateRig( GameObject rig )
        {
            rigSteamVR.SetActive( rig == rigSteamVR );
            rig2DFallback.SetActive( rig == rig2DFallback );
            if ( audioListener )
            {
                audioListener.transform.parent = hmdTransform;
                audioListener.transform.localPosition = Vector3.zero;
                audioListener.transform.localRotation = Quaternion.identity;
            }
        }
        //-------------------------------------------------publicvoidPlayerShotSelf()
        {
            //Do something appropriate here
        }
    }
}

  1. Launch Window \ SteamVR Imput, Create our actions in the default set and save them, now select the item "Open binding UI" (SteamVR must be running and at least one controller is enabled)



  2. In the browser, the Controller Binding tab will open - in it you need to configure the connection of our actions with the controllers: we will hang PlayerMove on the left TRACKPAD (do not forget to disable Mirror Mode), PlayerRotate on the right TRACKPAD, and MenuClick on the Menu keys



  3. Close the Controller Binding and save the changes.


  4. In the properties of the player, we associate new actions



  5. Run Play




Project


Conclusion


It should be noted a few points. Some actions in SteamVR Imput can make Unity think for a long time, in principle, you can make these changes yourself in the code, and instead of using Controller Binding, you can directly edit json files, but there is a big risk of error that will be difficult to catch.


For a deeper study of the plugin - it is useful to study the examples in detail, and of course - read the documentation.


Also popular now: