Making Dynamic VR Interaction Using Grip Points

These tutorials have only been becoming more complicated. With the recent release of Boneworks I’ve been inspired to add some features to my interaction system that I’ve been thinking about adding for a while. Mainly Dynamic gripping of poles and rotation activated grip points. The problem with this was that I had to rework my interaction system in quite a few ways that I’ll try to mark in the code below. I’ve already done a tutorial on the basis of how my system works that you can check out here for a more in-depth explanation of the code.

Note: I will have all the code for this on the GitHub before long

The Changes

Getting started, I had to modify all of the interaction scripts except one to get this to work so let’s show those with comments about the changes:

GrabPoint

using UnityEngine;
public class GrabPoint : MonoBehaviour
{
    //The way that Rotation Restriction works is by comparing the rotation of the GrabPoint and controler and if the angle between them is less than the RotationLimit then it is grabbable.
    public bool RestrictByRotation;//Set's if you want to restrict the grip point by a rotation
    public float RotationLimit;//set's the angle that you want to restrict it to
    public bool HelperGrip;//Replaced Subgrip and reworked it so that it works like a nomal grip unless double gripped 
    public Vector3 Offset;//Added in offset varibles so that the Grabpoints don't have to be direct children of a game object with an interactible script
    public Quaternion RotationOffset;

    public bool Gripped;
    public Interactable ParentInteractable;
    private void Awake()//auto set ParentInteractible if we can and update the offset
    {
        
        if (!ParentInteractable && transform.parent.GetComponent<Interactable>())
        {
            ParentInteractable = transform.parent.GetComponent<Interactable>();
        }
        UpdateOffset();

    }
    public void UpdateOffset()
    {
        Offset = Quaternion.Inverse(ParentInteractable.transform.rotation) * (-ParentInteractable.transform.position + transform.position);
        RotationOffset = Quaternion.Inverse(ParentInteractable.transform.rotation) * transform.rotation;
    }

}

Grabber

using System.Collections.Generic;
using UnityEngine;

public class Grabber : MonoBehaviour
{
    public ConfigurableJoint StrongGrip;
    public ConfigurableJoint WeakGrip;
    public FixedJoint FixedJoint;


    public List<GameObject> NearObjects = new List<GameObject>();

    void OnTriggerEnter(Collider other)
    {
        if (other.GetComponent<GrabPoint>())
        {
            if (!other.GetComponent<GrabPoint>().Gripped)
            {
                NearObjects.Add(other.gameObject);
            }
            
        }
        //Debug.Log(NearObjects);
    }
    void OnTriggerExit(Collider other)
    {
        if (other.GetComponent<GrabPoint>())
        {
            NearObjects.Remove(other.gameObject);
        }
    }
    public GameObject ClosestGrabbable()
    {
        GameObject ClosestGameObj = null;
        float Distance = float.MaxValue;
        if (NearObjects != null)
        {
            foreach (GameObject GameObj in NearObjects)//updated this function to take into account Rotation Locked GrabPoints
            {
                if (!GameObj.GetComponent<GrabPoint>().RestrictByRotation || GameObj.GetComponent<GrabPoint>().RotationLimit > Quaternion.Angle(transform.rotation, GameObj.transform.rotation))//added the need for 
                {
                    if ((GameObj.transform.position - transform.position).sqrMagnitude < Distance)
                    {
                        ClosestGameObj = GameObj;
                        Distance = (GameObj.transform.position - transform.position).sqrMagnitude;
                    }
                }
            }
        }
        return ClosestGameObj;
    }
}

GripController

using UnityEngine;
using Valve.VR;

public class GripController : MonoBehaviour
{
    public SteamVR_Input_Sources Hand;
    public SteamVR_Action_Boolean ToggleGripButton;
    public SteamVR_Action_Pose position;
    public SteamVR_Behaviour_Skeleton HandSkeleton;
    public SteamVR_Behaviour_Skeleton PreviewSkeleton;
    public Grabber grabber;

    private GameObject ConnectedObject;
    private Transform OffsetObject;
    private bool DomanantGrip;
    private void Update()//there are a lot of changes in this one, the biggest is that I replaced all the GripPoint.localposition stuff with GripPoint.Offset.
    {

        if (ConnectedObject != null )
        {
            if (DomanantGrip || !ConnectedObject.GetComponent<Interactable>().SecondGripped)
            {


                if (ConnectedObject.GetComponent<Interactable>().touchCount == 0&& !ConnectedObject.GetComponent<Interactable>().SecondGripped)
                {
                    grabber.FixedJoint.connectedBody = null;
                    grabber.StrongGrip.connectedBody = null;

                    ConnectedObject.transform.position = Vector3.MoveTowards(ConnectedObject.transform.position, transform.position - ConnectedObject.transform.rotation * OffsetObject.GetComponent<GrabPoint>().Offset, .25f);
                    ConnectedObject.transform.rotation = Quaternion.RotateTowards(ConnectedObject.transform.rotation, transform.rotation*Quaternion.Inverse( OffsetObject.GetComponent<GrabPoint>().RotationOffset), 10);
                    grabber.FixedJoint.connectedBody = ConnectedObject.GetComponent<Rigidbody>();
                }
                else if (ConnectedObject.GetComponent<Interactable>().touchCount > 0|| ConnectedObject.GetComponent<Interactable>().SecondGripped)
                {

                    grabber.FixedJoint.connectedBody = null;
                    grabber.StrongGrip.connectedAnchor = OffsetObject.GetComponent<GrabPoint>().Offset;
                    grabber.StrongGrip.connectedBody = ConnectedObject.GetComponent<Rigidbody>();

                }else if(ConnectedObject.GetComponent<Interactable>().touchCount < 0)
                {
                    ConnectedObject.GetComponent<Interactable>().touchCount = 0;
                }
                
            }
            else
            {
                grabber.FixedJoint.connectedBody = null;
                grabber.StrongGrip.connectedBody = null;
                grabber.WeakGrip.connectedBody = ConnectedObject.GetComponent<Rigidbody>();
            }
            if (ToggleGripButton.GetStateUp(Hand))
            {
                Release();
            }
            if(PreviewSkeleton)
                PreviewSkeleton.transform.gameObject.SetActive(false);
        }
        else
        {
            if (grabber.ClosestGrabbable() && PreviewSkeleton)
            {
                PreviewSkeleton.transform.gameObject.SetActive(true);
                OffsetObject = grabber.ClosestGrabbable().transform;
                if (grabber.ClosestGrabbable().GetComponent<SteamVR_Skeleton_Poser>())
                {
                    if (!OffsetObject.GetComponent<GrabPoint>().Gripped)
                    {
                        PreviewSkeleton.transform.SetParent(OffsetObject, false);
                        PreviewSkeleton.BlendToPoser(OffsetObject.GetComponent<SteamVR_Skeleton_Poser>(), 0f);
                    }
                }
            }
            else
            {
                PreviewSkeleton.transform.gameObject.SetActive(false);
            }
            if (ToggleGripButton.GetStateDown(Hand))
            {
                Grip();
            }
        }
    }
    private void Grip()
    {
        GameObject NewObject = grabber.ClosestGrabbable();
        if (NewObject != null)
        {
            OffsetObject = grabber.ClosestGrabbable().transform;
            ConnectedObject = OffsetObject.GetComponent<GrabPoint>().ParentInteractable.gameObject;//find the Closest Grabbable and set it to the connected object
            ConnectedObject.GetComponent<Rigidbody>().useGravity = false;

            OffsetObject.GetComponent<GrabPoint>().Gripped = true;
            if (ConnectedObject.GetComponent<Interactable>().gripped)
            {
                ConnectedObject.GetComponent<Interactable>().SecondGripped = true;
                if (OffsetObject.GetComponent<GrabPoint>().HelperGrip)
                {
                    DomanantGrip = false;
                    grabber.WeakGrip.connectedBody = ConnectedObject.GetComponent<Rigidbody>();
                    grabber.WeakGrip.connectedAnchor = OffsetObject.GetComponent<GrabPoint>().Offset;
                }
                grabber.WeakGrip.connectedBody = ConnectedObject.GetComponent<Rigidbody>();
                grabber.WeakGrip.connectedAnchor = OffsetObject.GetComponent<GrabPoint>().Offset;
            }
            else
            {
                ConnectedObject.GetComponent<Interactable>().Hand = Hand;
                ConnectedObject.GetComponent<Interactable>().gripped = true;
                if (!OffsetObject.GetComponent<GrabPoint>().HelperGrip)
                {
                    DomanantGrip = true;
                    ConnectedObject.GetComponent<Interactable>().GrippedBy = transform.parent.gameObject;
                }
            }
            if (OffsetObject.GetComponent<SteamVR_Skeleton_Poser>()&&HandSkeleton)
            {
                HandSkeleton.transform.SetParent(OffsetObject, false);
                HandSkeleton.BlendToPoser(OffsetObject.GetComponent<SteamVR_Skeleton_Poser>(), 0f);
            }


        }
    }
    private void Release()
    {
        grabber.FixedJoint.connectedBody = null;
        grabber.StrongGrip.connectedBody = null;
        grabber.WeakGrip.connectedBody = null;
        ConnectedObject.GetComponent<Rigidbody>().velocity = position.GetVelocity(Hand) + transform.parent.GetComponent<Rigidbody>().velocity;
        ConnectedObject.GetComponent<Rigidbody>().angularVelocity = position.GetAngularVelocity(Hand) + transform.parent.GetComponent<Rigidbody>().angularVelocity;
        ConnectedObject.GetComponent<Rigidbody>().useGravity = true;
        if (!ConnectedObject.GetComponent<Interactable>().SecondGripped)
        {
            
            ConnectedObject.GetComponent<Interactable>().gripped = false;

            ConnectedObject.GetComponent<Interactable>().GrippedBy =null;

        }
        else
        {
            ConnectedObject.GetComponent<Interactable>().SecondGripped = false;
        }
        
        ConnectedObject = null;
        if (OffsetObject.GetComponent<SteamVR_Skeleton_Poser>() && HandSkeleton)
        {
            HandSkeleton.transform.SetParent(transform, false);
            HandSkeleton.BlendToSkeleton();
        }
        OffsetObject.GetComponent<GrabPoint>().Gripped = false;
        OffsetObject = null;
    }
    
}

Sorry about all of that copy and pasting but I needed to get that out of the way to get to the new code. Using the new features and reworks we can now rig objects up to Boneworks Interaction level (At least soon after I figure out Physics for the body tracking script) Though we need one more thing, dynamic pole gripping that lets us grip a pole at any point.

Pole Grip Code

First, we need to set up our GameObject, First, create an empty game object and put a cylinder an empty game object in it. Inside it, we want to put two more game objects with GrabHandle prefabs inside them. in the end, It should look something like this:

Yes, the Pole Grip is duplicated, that comes later.

In the root object add an Interactible script and a rigidbody, then assign the parent interactable variable on the Grab Handles to the root. Add a capsule collider as a trigger to the PoleGrip object and scale it so that it surrounds the cylinder.

Now we add a new script to the PoleGrip object:

using UnityEngine;

public class PoleGrip : MonoBehaviour
{
    public GrabPoint LeftHandGrip;//the Grab points
    public GrabPoint RightHandGrip;

    private Transform LeftHand;//The hands
    private Transform RightHand;
    

    // Update is called once per frame
    void Update()
    {
        if (LeftHand)//check to see if the hand is near
        {
            Vector3 newpose = Vector3.Project((LeftHand.position - transform.position)*100, transform.up)/100;//find the closest position
            if (newpose.magnitude < GetComponent<CapsuleCollider>().height / 2 && !LeftHandGrip.Gripped)//if it's not outside of the capsule collider and not gripped
            {
                LeftHandGrip.transform.parent.position = newpose + transform.position;
                LeftHandGrip.transform.parent.rotation = Quaternion.LookRotation(-((LeftHand.position) - LeftHandGrip.transform.parent.position), transform.up);//set rotation

                LeftHandGrip.UpdateOffset();//update the offset
                Debug.Log("set new pose");

            }
            else if (!LeftHandGrip.Gripped && newpose.magnitude > GetComponent<CapsuleCollider>().height / 2)//if it's outside of the collider
            {
                LeftHandGrip.transform.localPosition = new Vector3();//reset the position
            }
        }
        if (RightHand)
        {
            Vector3 newpose = Vector3.Project((RightHand.position - transform.position) * 100, transform.up)/100;
            if (newpose.magnitude < GetComponent<CapsuleCollider>().height / 2 && !RightHandGrip.Gripped)
            {
                RightHandGrip.transform.parent.position = newpose + transform.position;
                RightHandGrip.transform.parent.rotation = Quaternion.LookRotation(-((RightHand.position) - RightHandGrip.transform.parent.position), transform.up);

                RightHandGrip.UpdateOffset();
                Debug.Log("set new pose");

            }
            else if (!RightHandGrip.Gripped&& newpose.magnitude > GetComponent<CapsuleCollider>().height / 2)
            {
                RightHandGrip.transform.localPosition = new Vector3();
            }
        }

    }
    private void OnTriggerEnter(Collider other)//update our hand varibles depending on if they are near or not
    {
        if (other.GetComponent<Grabber>())
        {
            if (other.transform.parent.GetComponent<GripController>().Hand == Valve.VR.SteamVR_Input_Sources.LeftHand)
            {
                LeftHand = other.transform;
            }
            else if (other.transform.parent.GetComponent<GripController>().Hand == Valve.VR.SteamVR_Input_Sources.RightHand)
            {
                RightHand = other.transform;
            }
            Debug.Log("found a grabber!");
        }
    }
    private void OnTriggerExit(Collider other)
    {
        if (other.GetComponent<Grabber>())
        {
            if (other.transform.parent.GetComponent<GripController>().Hand == Valve.VR.SteamVR_Input_Sources.LeftHand)
            {
                LeftHand = null;
            }
            else if (other.transform.parent.GetComponent<GripController>().Hand == Valve.VR.SteamVR_Input_Sources.RightHand)
            {
                RightHand = null;
            }
        }
    }
}

What this script does is simple, It keeps track of the hands inside of the Capsule collider and moves the Grab Handles as Close to the hands as it can, then rotates them so that they face the same direction as the hand. Add it to the PoleGrip and set the GrabPoint variables appropriately.

Test it to make sure it’s working, you should be able to grip it in one rotation, it’s also a good time to set the SteamVR poses. If it works, Open the Grab Handle prefabs in the inspector and set Restrict By Rotation to true and set Rotation Limit to 90. Now we can duplicate the Pole Grip and rotate the duplicated Pole Grip by 180 degrees to allow us to grab it with two different hand rotations.

Hopefully, you liked this. I apologize for the ever-increasing reliance on earlier tutorials but I hope you understand that it lets me give you guys really complex stuff without having to explain it every time. Though with that said I plan to do a tutorial on just setting up an object to be grabbed with this system, SteamVR poses and all. I hope this helps you out, until the next one.

Liked it? Take a second to support WireWhiz on Patreon!
Become a patron at Patreon!