Returning Game Object to Original Position Upon Release from Oculus Touch Controller

I have a machine setup with multiple child components. The machine sits on a pedestal that the player has the ability to rotate with a controller button (the machine then rotates with the pedestal). I am using the OVR Grab and OVR Grabbable sciprts (for the Oculus Rift touch controller) to allow the player to pull away components of the machine. I want to set it up so that when the player releases the object, it snaps back to it’s original position and rotation relative to the machine (preferably with some ease and maybe some spring). I’ve tried using joints and haven’t had any luck.

I’m very new to Unity, so pretend like your explaining it to a 5 year old :slight_smile:

One way would be to give each part a small script to save it’s initial local position and rotation. When you release a part, call a function or have the part listen for the “released” event or whatever (somehow tell the part it was dropped), and have the part move and rotate itself back to the initial local transform.

You could also take the same approach but give the parent machine the script, and have it keep track of all the parts.

I personally use DOTween for all my programmatic animations, because it’s dope af.

Yea I was thinking that I would probably have to setup some sort of script. Can you give me an idea of what that script might look like?

The part could be as simple as this:

using DG.Tweening; // this only works with the free DOTween asset
using UnityEngine;

public class Part : MonoBehaviour
{
    public float moveSpeed;
    public Ease moveEase;
    public float rotateSpeed;
    public Ease rotateEase;

    private Vector3 initialLocalPosition;
    private Vector3 initialLocalRotation;
  
    // Unity function called once when the game starts
    private void Start()
    {
        initialLocalPosition = transform.localPosition;
        initialLocalRotation = transform.localRotation.eulerAngles;
    }

    public void OnReleased()
    {
        // starts a tween with an ease, speed based as opposed to duration based
        transform.DOLocalMove(initialLocalPosition, moveSpeed)
            .SetSpeedBased()
            .SetEase(moveEase);

        transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
            .SetSpeedBased()
            .SetEase(rotateEase);
    }

    public void OnGrabbed()
    {
        // stops all tweens on this transform
        transform.DOKill();
    }
}

Then it’s just a matter of calling the OnGrabbed and OnReleased functions. I don’t know how the grab/release script works, but if you can get it to call those functions (or does it throw events or something?) then this can work.

This is the script I am using:

https://developer3.oculus.com/doc/1.11-unity/class_o_v_r_grabber.html#details

Can you tell what variables I would need from looking at that documentation? Or would you need the whole script?

Thanks so much for your help by the way…

To actually help you alter the scripts I would need to see the source. If you paid for that, you can’t share it here. You’ll be working with the “GrabEnd” and “GrabBegin” functions.

So assuming the script doesn’t already have events to hook into, you can add some.

So you can modify the provided Grabbable script like this:

using UnityEngine.Events; // add this

// the provided oculus script
public class Grabbable : MonoBehaviour
{
    // add these
    public UnityEvent OnGrabBegin;
    public UnityEvent OnGrabEnd;

    // pre-existing function
    void GrabBegin()
    {
        // add this line
        OnGrabBegin.Invoke();
    }

    // pre-existing function
    void GrabEnd()
    {
        // add this line
        OnGrabEnd.Invoke();
    }
}

Then your Part script can look like this:
Part.cs

using DG.Tweening; // this only works with the free DOTween asset
using UnityEngine;

public class Part : MonoBehaviour
{
    public float moveSpeed;
    public Ease moveEase;
    public float rotateSpeed;
    public Ease rotateEase;

    private Vector3 initialLocalPosition;
    private Vector3 initialLocalRotation;
    private OVRGrabbable grabbable;

    private void Awake()
    {
        // get the grabbable component on this object
        grabbable = GetComponent<OVRGrabbable>();
    }

    // Unity function called once when the game starts
    private void Start()
    {
        initialLocalPosition = transform.localPosition;
        initialLocalRotation = transform.localRotation.eulerAngles;
    }

    private void OnEnable()
    {
        // listen for grabs
        grabbable.OnGrabBegin.AddListener(OnGrabbed);
        grabbable.OnGrabEnd.AddListener(OnReleased);
    }

    private void OnDisable()
    {
        // stop listening for grabs
        grabbable.OnGrabBegin.RemoveListener(OnGrabbed);
        grabbable.OnGrabEnd.RemoveListener(OnReleased);
    }

    public void OnReleased()
    {
        // starts a tween with an ease, speed based as opposed to duration based
        transform.DOLocalMove(initialLocalPosition, moveSpeed)
            .SetSpeedBased()
            .SetEase(moveEase);

        transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
            .SetSpeedBased()
            .SetEase(rotateEase);
    }

    public void OnGrabbed()
    {
        // stops all tweens on this transform
        transform.DOKill();
    }
}

The script is part of the “Oculus Sample Framework” which is a free resource in the Unity 3D Store.

Luckily whoever wrote that was nice enough to make that class easily extensible.

You can create a new script called “OVRGrabbableExtended”, and paste this in:
OVRGrabbableExtended.cs

using UnityEngine.Events;

public class OVRGrabbableExtended : OVRGrabbable
{
    [HideInInspector] public UnityEvent OnGrabBegin;
    [HideInInspector] public UnityEvent OnGrabEnd;

    public override void GrabBegin(OVRGrabber hand, Collider grabPoint)
    {
        OnGrabBegin.Invoke();
        base.GrabBegin(hand, grabPoint);
    }

    public override void GrabEnd(Vector3 linearVelocity, Vector3 angularVelocity)
    {
        base.GrabEnd(linearVelocity, angularVelocity);
        OnGrabEnd.Invoke();
    }
}

It will look and behave identically to the original one, except this one exposes some public events to hook into.

So then the Part class would look like this (just changed the name of the grabbable class variable):
Part.cs

using DG.Tweening; // this only works with the free DOTween asset
using UnityEngine;

public class Part : MonoBehaviour {
    public float moveSpeed;
    public Ease moveEase;
    public float rotateSpeed;
    public Ease rotateEase;

    private Vector3 initialLocalPosition;
    private Vector3 initialLocalRotation;
    private OVRGrabbableExtended grabbable;

    private void Awake() {
        // get the grabbable component on this object
        grabbable = GetComponent<OVRGrabbable>();
    }

    // Unity function called once when the game starts
    private void Start() {
        initialLocalPosition = transform.localPosition;
        initialLocalRotation = transform.localRotation.eulerAngles;
    }

    private void OnEnable() {
        // listen for grabs
        grabbable.OnGrabBegin.AddListener(OnGrabbed);
        grabbable.OnGrabEnd.AddListener(OnReleased);
    }

    private void OnDisable() {
        // stop listening for grabs
        grabbable.OnGrabBegin.RemoveListener(OnGrabbed);
        grabbable.OnGrabEnd.RemoveListener(OnReleased);
    }

    public void OnReleased() {
        // starts a tween with an ease, speed based as opposed to duration based
        transform.DOLocalMove(initialLocalPosition, moveSpeed)
            .SetSpeedBased()
            .SetEase(moveEase);

        transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
            .SetSpeedBased()
            .SetEase(rotateEase);
    }

    public void OnGrabbed() {
        // stops all tweens on this transform
        transform.DOKill();
    }
}

I hope you will take the time to read through this and really understand what is going on so that you can start coding stuff like this for yourself moving forward. If you have any questions about any part of it, please ask.

Also please note that I didn’t test any of this, so hopefully it works, but if not I’ll help you fix it.

Honestly most of the time I don’t bother with their sample scripts. It’s usually better to do what you need using OVRInput directly.

Oculus are nice about their SDK though, that’s for sure. Pretty much all of the source is available, save for some external c++ libraries.

1 Like

I’m new to C#, so I’m slowly learning the language but it’s probably going to take a while. I think I’m at least starting to grasp some of the basics.

Unity is throwing an error on the OVRGrabbableExtended.cs script. It says “OVRGrabbableExtended.cs(8,53): error CS0246: The type or namespace name ‘Collider’ could not be found. Are you missing ‘UnityEngine’ using directive?”. Any ideas?

Add “using UnityEngine;” to the top.

Easy for you to say :slight_smile: Took me forever just to get things working with the pre-made scripts.

Ok that script seems to be working now. However, now I’m getting an error on the Part.cs script. “Part.cs(18,21): error CS0266: Cannot implicitly convert type ‘OVRGrabbable’ to ‘OVRGrabbableExtended’. An explicit conversion exists (are you missing a cast?)”

woops, in the Awake function, change OVRGrabbable to OVRGrabbableExtended.

Ok I think we are getting pretty close. I applied both scripts to my object that i want to grab, and I can pull it away from the machine. When I release it, the object moves to a position, but not the original one (it’s somewhere up in the air).

I also noticed that once I remove it and it goes back to its original place, it no longer rotates with the machine on the pedestal. I have the machine and pedestal as children of an empty game object. My rotation script is on the empty game object.

So are the individual parts children of the machine, and do they stay children throughout the grab/release?

My guess is that the part gets unparented, and then ‘local’ position becomes worldspace.

Yes, the parts are children of the machine in the hierarchy. Right now I am focusing on removing a lid which is attached to the case. I have the hierarchy setup as Machine - Case - Lid. I applied a RigidBody tag applied to the lid with gravity disabled.

Lets try saving the original parent transform, and restoring it when the object is dropped.

using DG.Tweening; // this only works with the free DOTween asset
using UnityEngine;

public class Part : MonoBehaviour
{
    public float moveSpeed;
    public Ease moveEase;
    public float rotateSpeed;
    public Ease rotateEase;

    private Vector3 initialLocalPosition;
    private Vector3 initialLocalRotation;
    private Transform initialParent;
    private OVRGrabbableExtended grabbable;

    private void Awake()
    {
        // get the grabbable component on this object
        grabbable = GetComponent<OVRGrabbableExtended>();
    }

    // Unity function called once when the game starts
    private void Start()
    {
        initialLocalPosition = transform.localPosition;
        initialLocalRotation = transform.localRotation.eulerAngles;
        initialParent = transform.parent;
    }

    private void OnEnable()
    {
        // listen for grabs
        grabbable.OnGrabBegin.AddListener(OnGrabbed);
        grabbable.OnGrabEnd.AddListener(OnReleased);
    }

    private void OnDisable()
    {
        // stop listening for grabs
        grabbable.OnGrabBegin.RemoveListener(OnGrabbed);
        grabbable.OnGrabEnd.RemoveListener(OnReleased);
    }

    private void OnReleased()
    {
        // restore parent
        transform.SetParent(initialParent);

        // starts a tween with an ease, speed based as opposed to duration based
        transform.DOLocalMove(initialLocalPosition, moveSpeed)
            .SetSpeedBased()
            .SetEase(moveEase);

        transform.DOLocalRotate(initialLocalRotation, rotateSpeed)
            .SetSpeedBased()
            .SetEase(rotateEase);
    }

    private void OnGrabbed()
    {
        // stops all tweens on this transform
        transform.DOKill();
    }
}
3 Likes

The lid still floats off into space once I release it. I tried setting up a simplified version of my scene using primitive 3D shapes (without any rotation), and it does the same thing with the scripts applied.

Take a look at the Lid’s position before grabbing. Once you grab it and release it, check what it’s new position is, and if it has a parent.