How to open a VR door in Unity and SteamVR

Opening doors is a common task in games. In this article I will show you how you can implement a door mechanic in Unity for VR.  

This examples uses the SteamVR Interaction System from SteamVR 1.0. It is still widely used so I keep it here for reference for anyone looking for a specific solution.

In Shopkeeper Simulator VR, hand interaction is generally achieved through the SteamVR Interaction System. Let's take a look at the 3D scene in Unity to visualize what we are going to talk about:

Fridge door in closed statae

Fridge door in closed state


Fridge door in opened state

Fridge door in opened state

To move the fridge door independently from the fridge body, the door was modeled as a separate mesh in Blender. Here is the object hierarchy in Blender:

Fridge object hierarchy in Blender

Object hierarchy in Blender

After we exported the file as FBX, the imported hierarchy on the Unity side looked like this:

GameObject in Unity

Object hierarchy in Unity (irrelevant children obfuscated)


Handle explained

The "Handle" GameObject is the one which should react to the player's hand. Therefore, it was also separated from the door itself. The handle needs two components: The Interactable component which is part of the interaction system, a collider and the script "OpenDoor" which is explained further below.

GameObject

"Handle" GameObject in the inspector


Script "OpenDoor.cs"

using UnityEngine;
using Valve.VR.InteractionSystem;
 
 /*
  * This class is attached to a door handle. The door handle is child of a door.
  */
 public class OpenDoor : MonoBehaviour
 {
     private Vector3 force;
     private Vector3 cross;
     private bool holdingHandle;
     private float angle;
     private const float forceMultiplier = 150f;
 
     private void HandHoverUpdate(Hand hand)
     {
         if (hand.GetStandardInteractionButton())
         {
             holdingHandle = true;
 
             // Direction vector from the door's pivot point to the hand's current position
             Vector3 doorPivotToHand = hand.transform.position - transform.parent.position;
 
             // Ignore the y axis of the direction vector
             doorPivotToHand.y = 0;  
 
             // Direction vector from door handle to hand's current position
             force = hand.transform.position - transform.position;
 
             // Cross product between force and direction. 
             cross = Vector3.Cross(doorPivotToHand, force);
             angle = Vector3.Angle(doorPivotToHand, force);
         }
         else if (hand.GetStandardInteractionButtonUp())
         {
             holdingHandle = false;
         }
     }

     void Update()
     {
         if (holdingHandle)
         {
             // Apply cross product and calculated angle to
             GetComponentInParent<Rigidbody>().angularVelocity = cross * angle * forceMultiplier;
         }
     }
 
     private void OnHandHoverEnd()
     {
         // Set angular velocity to zero if the hand stops hovering
         GetComponentInParent<Rigidbody>().angularVelocity = Vector3.zero;
     }
 }

The two methods HandHoverUpdate and OnHandHoverEnd are specific to the SteamVR interaction system.  They are called by the interaction system every frame the hand hovers over an interactable object and when the hovering ends respectively.

Basically, we are using the cross product of the force vector (pulling force of the hand) and the vector that points from the door's hinge to the transform where the hand is holding the handle (called "doorPivotToHand" vector). Together with the angle between the force vector and the "doorPivotToHand" vector, the angularVelocity is calculated and applied in FixedUpdate(). Note that FixedUpdate should be used and not Update, since we are changing a RigidBody's angular  velocity.

Notice that in order to work, the door must have a Rigidbody, a collider and a Hinge Joint component to limit the door's angle. The HingeJoint's limits were set to 0 min and 90 max:

GameObject

The GameObject "FridgeBody" needs a rigidbody with enabled isKinematic property and gravity equal false because we don't want the body to be controlled by the physics system and influenced by gravity. It is mandatory for the HingeJoint to work though.

If you found this tutorial helpful, subscribe to my newsletter to be the first to hear about new tutorials. You also get a free demo of Shopkeeper Simulator VR!