SteamVR 2.0 how to implement distance grab?
I am trying to create my own scripts called DistanceGrabber for my hands and DistanceGrabbable for the objects I want to be able to grab from a distance. what I made kind of works but the laser pointer script is making me have to release the grip button first before executing the onDistanceGrab function in DistanceGrabbable script, so I had to "double click" rapidly and hold the grip the second time to be able to hold the object in the hand or else it will just fall to the ground.
and also
how do you disable the line when the laser is not pointing at a grabbable object?
My setup:
Unity 2019.2.6f1
SteamVR 2.0
Oculus Rift
DistanceGrabber script:
public class DistanceGrabber : MonoBehaviour
{
private SteamVR_LaserPointer laserPointer;
private Hand hand;
void Awake()
{
laserPointer = GetComponent<SteamVR_LaserPointer>();
hand = GetComponent<Hand>();
laserPointer.PointerIn += PointerInside;
laserPointer.PointerOut += PointerOutside;
laserPointer.PointerClick += PointerClick;
//interactable = GetComponent<Interactable>();
}
public void PointerClick(object sender, PointerEventArgs e)
{
if (e.target.GetComponent<DistanceGrabbable>() != null)
{
e.target.GetComponent<DistanceGrabbable>().onDistanceGrab(hand);
}
}
public void PointerInside(object sender, PointerEventArgs e)
{
if (e.target.GetComponent<Interactable>() != null)
{
hand.ShowGrabHint();
hand.IsStillHovering(e.target.GetComponent<Interactable>());
laserPointer.active = true;
}
}
public void PointerOutside(object sender, PointerEventArgs e)
{
if (e.target.GetComponent<Interactable>() == null)
{
hand.HideGrabHint();
laserPointer.active = false;
}
}
DistanceGrabbable script:
public class DistanceGrabbable : MonoBehaviour
{
protected Rigidbody rb;
protected bool originalKinematicState;
protected Transform originalParent;
private void Awake()
{
rb = GetComponent<Rigidbody>();
originalParent = transform.parent;
originalKinematicState = rb.isKinematic;
}
public void onDistanceGrab(Hand hand)
{
rb.isKinematic = true;
transform.SetParent(hand.gameObject.transform);
hand.AttachObject(gameObject, GrabTypes.Trigger);
}
private void OnDetachedFromHand(Hand hand)
{
rb.isKinematic = false;
transform.SetParent(originalParent);
}
}
Answer by rgbeans · Apr 05, 2020 at 12:43 PM
@HelloPeopIe I have a solution to your problem. My code doesn't use a visible laser pointer, but instead, the objects I am pointing at have a child object with a SteamVR hover highlight material, as shown in the screenshot below.
but once you have that cube as a child of your base object, here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR.InteractionSystem;
using Valve.VR;
public class DistanceGrab : MonoBehaviour
{
public Transform pointer;//the transform the laser starts at
public LayerMask thingsWeCanGrab;//things we can grab
Hand hand;//our hand
bool isAttached = false;//do we have something in our hand?
GameObject attachedObject = null;//what do we have in our hand
Blank blank;//blank script
// Start is called before the first frame update
void Start()
{
hand = GetComponent<Hand>();//get our hand
}
// Update is called once per frame
void Update()
{
//raycast and check if our hand is empty
RaycastHit hit;
if (Physics.Raycast(pointer.position, pointer.forward, out hit, 10f, thingsWeCanGrab) && hand.currentAttachedObject == null)
{
Interactable interactable = hit.collider.gameObject.GetComponent<Interactable>();
SteamVR_Input_Sources source = hand.handType;
//are we pressing grip and trigger?
if (hand.grabGripAction[source].state == true && hand.grabPinchAction[source].state == true)
{
//does the interactable component exist?
if (interactable != null)
{
//move the object to your hand
interactable.transform.LookAt(transform);
interactable.gameObject.GetComponent<Rigidbody>().AddRelativeForce(Vector3.forward * 500, ForceMode.Force);
attachedObject = interactable.gameObject;
isAttached = true;
//attaching to hand is in the late update function
}
}
blank = hit.collider.gameObject.GetComponentInChildren<Blank>();
blank.gameObject.GetComponent<MeshRenderer>().enabled = true;
}
else
{
blank.gameObject.GetComponent<MeshRenderer>().enabled = false;
}
}
private void LateUpdate()
{
//did we get an object to our hand during this update?
if (isAttached)
{
//attach the object
hand.AttachObject(attachedObject, GrabTypes.Grip);
attachedObject = null;
isAttached = false;
}
}
}
this
Blank blank;
How in the he.... this you get from somewhere, because by default i cant use that in these namespaces..
Never $$anonymous$$d, a blank script, mean a blank script.. haha..
This is great, it works perfect for S$$anonymous$$mVR 2.0 distance grab.. I raise my glass to You sir ..
@rgbeans Where do you put this script? Trying it on the hand, the object being grabbed or on an empty game object all failed and continually threw errors about a Null Reference Exception at line 50 and 27?
Neverminded I found another system using a sphere raycast that works. It has a few issues to work out but its not throwing errors. http://egorfesenko.com/vrdistancegrab
Answer by jjn7 · Apr 05, 2020 at 11:29 PM
@HelloPeopIe Hey man your code helped me earlier when I tried to implement the same thing. However after testing your code I found that the laser pointer had a lot of issues and I eventually came up with the same conclusion as rgbeans that a Raycast hit would work better.
However I'm assuming you have included the SteamVR_LaserPointer.cs as a laser to guide the user. Unfortunately turning the script on or off is a hassle and doesn't really work: https://answers.unity.com/questions/1455089/vive-laser-pointer-turn-off.html https://answers.unity.com/questions/1629859/how-can-i-disable-the-steamvr-laser-how-can-i-disa.html
The solution around this would be to change the thickness of the laser when the raycast hits the specific object. For example:
if (Physics.Raycast() hits the object) or public void PointerInside(object sender, PointerEventArgs e)
{
laserPointer.thickness = 0.002f;
}
else
{
laserPointer.thickness = 0;
}
If you need anymore help hmu.