There are many VR grabbing tutorials out there, why should you read this one? Watch this:
Ok, for those who haven’t been trying to create this exact result for months hitting their heads against the wall for hours, I’ll explain.
Most tutorials just teach you to connect the grabbed object to the controller using parenting or a fixed joint. This is all well and good until you realize your VR controller/player is not bound by the physics of your perfect world. Once you start swinging your controller you will find this method isn’t good enough. Using this your held object will pass right through walls like they aren’t there. It’s a no brainer that this isn’t good if you want to create a realistic experience. So you say, “I’ll just make it not go through walls.” So you search online for how to do it and find NOTHING. That was me, but not you. You found this article. Let’s get to the concept.
How it works
So the basic concept behind the demo above is simple. When the held object Isn’t touching anything it tracks the controller’s position exactly as it should, but when it touches something it switches from a fixed to a configurable joint allowing the object to collide with the intersected object as if it was being held, which it technically is. So now that we have that, let’s implement it.
The Code
First, we want to create a new tag called grabbable, this is so we can tell if our object is grabbable or not later on.
using UnityEngine;
public class Interactable : MonoBehaviour
{
public int touchCount;
void start()
{
if (gameObject.tag != "Grabbable") {
Debug.LogError("Interactable's tag is not set to grabbable");
}
}
private void OnCollisionEnter(Collision collision)
{
touchCount++;
}
private void OnCollisionExit(Collision collision)
{
touchCount--;
}
}
First, we define the public variable touchCount to keep track of how many objects we are touching. Next, in our start function, we do a quick check to make sure our tag is set correctly, this is so we don’t spend hours trying to figure out what’s wrong just to facepalm ourselves when we realize how dumb we were. Then we have our OnEnter and OnExit functions update our touchCount integer. And that’s it for now on this side.
Next, we create a Script Named GripController to be attached to the controller. This is, as the name suggests where all the magic happens. We’ll break it down with comments in the code as it is quite a bit more complicated than the last one and I still want you to be able to copy and paste:
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;
//These make sure that we have the components that we need
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(ConfigurableJoint))]
[RequireComponent(typeof(FixedJoint))]
public class GripController : MonoBehaviour
{
public SteamVR_Input_Sources Hand;//these allow us to set our input source and action.
public SteamVR_Action_Boolean ToggleGripButton;
private GameObject ConnectedObject;//our current connected object
public List<GameObject> NearObjects =new List<GameObject>();//all objects that we could pick up
private void Update()
{
if (ConnectedObject != null)//if we are holding somthing
{
if (ConnectedObject.GetComponent<Interactable>().touchCount == 0)//if our object isn't touching anything
{
//first, we disconnect our object
GetComponent<ConfigurableJoint>().connectedBody = null;
GetComponent<FixedJoint>().connectedBody = null;
//now we step our object slightly towards the position of our controller, this is because the fixed joint just freezes the object in whatever position it currently is in relation to the controller so we need to move it to the position we want it to be in. We could just set position to the position of the controller and be done with it but I like the look of it swinging into position.
ConnectedObject.transform.position = Vector3.MoveTowards(ConnectedObject.transform.position, transform.position, .25f);
ConnectedObject.transform.rotation = Quaternion.RotateTowards(ConnectedObject.transform.rotation, transform.rotation, 10);
//reconnect the body to the fixed joint
GetComponent<FixedJoint>().connectedBody = ConnectedObject.GetComponent<Rigidbody>();
}
else if(ConnectedObject.GetComponent<Interactable>().touchCount > 0)//if it is touching something
{
//switch from fixed joint to configurable joint
GetComponent<FixedJoint>().connectedBody = null;
GetComponent<ConfigurableJoint>().connectedBody = ConnectedObject.GetComponent<Rigidbody>();
}
if (ToggleGripButton.GetStateDown(Hand))// Check if we want to drop the object
{
Release();
}
}
else//if we aren't holding something
{
if (ToggleGripButton.GetStateDown(Hand))//cheack if we want to pick somthing up
{
Grip();
}
}
}
private void Grip()
{
GameObject NewObject = ClosestGrabbable();
if(NewObject != null)
ConnectedObject = ClosestGrabbable();//find the Closest Grabbable and set it to the connected objectif it isn't null
}
private void Release()
{
//disconnect all joints and set the connected object to null
GetComponent<ConfigurableJoint>().connectedBody = null;
GetComponent<FixedJoint>().connectedBody = null;
ConnectedObject = null;
}
void OnTriggerEnter (Collider other)
{
//Add grabbable objects in range of our hand to a list
if (other.CompareTag("Grabbable"))
{
NearObjects.Add(other.gameObject);
}
Debug.Log(NearObjects);
}
void OnTriggerExit(Collider other)
{
//remove grabbable objects going out of range from our list
if (other.CompareTag("Grabbable"))
{
NearObjects.Remove(other.gameObject);
}
}
GameObject ClosestGrabbable()
{
//find the object in our list of grabbable that is closest and return it.
GameObject ClosestGameObj = null;
float Distance = float.MaxValue;
if (NearObjects != null)
{
foreach (GameObject GameObj in NearObjects)
{
if ((GameObj.transform.position - transform.position).sqrMagnitude < Distance)
{
ClosestGameObj = GameObj;
Distance = (GameObj.transform.position - transform.position).sqrMagnitude;
}
}
}
return ClosestGameObj;
}
}
Alright, weather you read all that or just copy/pasted that we are ready to move on. All we need to do now is set up our scene. First Drop a random primitive or object into your scene that you want to be intractable, next add a collider and set it so that it covers the part of the object you want to grab and set it as a trigger, then set its tag to grabbable and add the Interactible script. Now we can set up our hand. Select your controller component and add the GripController script, it will add all the necessary components. Set the rigidbody to kinematic. Now We can set up our configurable joint, here’s a screenshot of my values that work pretty well:
Now all that’s left is to set up the values of the Grip Controller script. Set your action source to whatever hand you attached your script to, and your grip action to a boolean action pared to the button you want to use to pick stuff up. If you don’t understand how to do this click the tutorial link below to find out.
And that should be it for this tutorial, the technique is simple but it took me forever for me to come up with this idea. I’ve heard that
In the next tutorial, I’ll teach you how to make it so you can throw your grabbed objects.