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
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.
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.
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();
}
}
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.
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?
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?)”
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.
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();
}
}
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.