A Unity3d 5.3 script to add SteamVR functionality such as object interaction/grabbing/throwing, laser pointer and teleport movement with blink (Requires Unity SteamVR plugin)

5 min read Original article ↗
//============================================================================= // // Purpose: Add player ability on SteamVR CameraRig to: // // * Trackpad press down: project laser pointer from Touch Controller // * Trackpad release: teleport player with blink to laser point destination // * Trigger click: grab any object that has a custom "Grabbable" tag applied // * Trigger release: release the current grabbed object with relative force // * Application Menu: reset the position of last grabbed object to controller // // Tutorial on usage at: https://youtu.be/6uYaK_T46z0 // //============================================================================= using UnityEngine; using System.Collections; public class SteamVR_FirstPersonController : MonoBehaviour { public enum AxisType { XAxis, ZAxis } public Color pointerColor; public float pointerThickness = 0.002f; public AxisType pointerFacingAxis = AxisType.ZAxis; public float pointerLength = 100f; public bool showPointerTip = true; public bool teleportWithPointer = true; public float blinkTransitionSpeed = 0.6f; public bool highlightGrabbableObject = true; public Color grabObjectHightlightColor; private SteamVR_TrackedObject trackedController; private SteamVR_Controller.Device device; private GameObject pointerHolder; private GameObject pointer; private GameObject pointerTip; private Vector3 pointerTipScale = new Vector3(0.05f, 0.05f, 0.05f); private float pointerContactDistance = 0f; private Transform pointerContactTarget = null; private Rigidbody controllerAttachPoint; private FixedJoint controllerAttachJoint; private GameObject canGrabObject; private Color[] canGrabObjectOriginalColors; private GameObject previousGrabbedObject; private Transform HeadsetCameraRig; private float HeadsetCameraRigInitialYPosition; private Vector3 TeleportLocation; void Awake() { trackedController = GetComponent<SteamVR_TrackedObject>(); } void Start() { InitController(); InitPointer(); InitHeadsetReferencePoint(); } void InitController() { controllerAttachPoint = transform.GetChild(0).Find("tip").GetChild(0).GetComponent<Rigidbody>(); BoxCollider collider = this.gameObject.AddComponent<BoxCollider>(); collider.size = new Vector3(0.1f, 0.1f, 0.2f); collider.isTrigger = true; } void InitPointer() { Material newMaterial = new Material(Shader.Find("Unlit/Color")); newMaterial.SetColor("_Color", pointerColor); pointerHolder = new GameObject(); pointerHolder.transform.parent = this.transform; pointerHolder.transform.localPosition = Vector3.zero; pointer = GameObject.CreatePrimitive(PrimitiveType.Cube); pointer.transform.parent = pointerHolder.transform; pointer.GetComponent<MeshRenderer>().material = newMaterial; pointer.GetComponent<BoxCollider>().isTrigger = true; pointer.AddComponent<Rigidbody>().isKinematic = true; pointer.layer = 2; pointerTip = GameObject.CreatePrimitive(PrimitiveType.Sphere); pointerTip.transform.parent = pointerHolder.transform; pointerTip.GetComponent<MeshRenderer>().material = newMaterial; pointerTip.transform.localScale = pointerTipScale; pointerTip.GetComponent<SphereCollider>().isTrigger = true; pointerTip.AddComponent<Rigidbody>().isKinematic = true; pointerTip.layer = 2; SetPointerTransform(pointerLength, pointerThickness); TogglePointer(false); } void InitHeadsetReferencePoint() { Transform eyeCamera = GameObject.FindObjectOfType<SteamVR_Camera>().GetComponent<Transform>(); // The referece point for the camera is two levels up from the SteamVR_Camera HeadsetCameraRig = eyeCamera.parent.parent; HeadsetCameraRigInitialYPosition = HeadsetCameraRig.transform.position.y; } void SetPointerTransform(float setLength, float setThicknes) { //if the additional decimal isn't added then the beam position glitches float beamPosition = setLength / (2 + 0.00001f); if (pointerFacingAxis == AxisType.XAxis) { pointer.transform.localScale = new Vector3(setLength, setThicknes, setThicknes); pointer.transform.localPosition = new Vector3(beamPosition, 0f, 0f); pointerTip.transform.localPosition = new Vector3(setLength - (pointerTip.transform.localScale.x / 2), 0f, 0f); } else { pointer.transform.localScale = new Vector3(setThicknes, setThicknes, setLength); pointer.transform.localPosition = new Vector3(0f, 0f, beamPosition); pointerTip.transform.localPosition = new Vector3(0f, 0f, setLength - (pointerTip.transform.localScale.z / 2)); } TeleportLocation = pointerTip.transform.position; } float GetPointerBeamLength(bool hasRayHit, RaycastHit collidedWith) { float actualLength = pointerLength; //reset if beam not hitting or hitting new target if (!hasRayHit || (pointerContactTarget && pointerContactTarget != collidedWith.transform)) { pointerContactDistance = 0f; pointerContactTarget = null; } //check if beam has hit a new target if (hasRayHit) { if (collidedWith.distance <= 0) { } pointerContactDistance = collidedWith.distance; pointerContactTarget = collidedWith.transform; } //adjust beam length if something is blocking it if (hasRayHit && pointerContactDistance < pointerLength) { actualLength = pointerContactDistance; } return actualLength; ; } void TogglePointer(bool state) { pointer.gameObject.SetActive(state); bool tipState = (showPointerTip ? state : false); pointerTip.gameObject.SetActive(tipState); } void Teleport() { SteamVR_Fade.Start(Color.black, 0); SteamVR_Fade.Start(Color.clear, blinkTransitionSpeed); HeadsetCameraRig.position = new Vector3(TeleportLocation.x, HeadsetCameraRigInitialYPosition, TeleportLocation.z); } void UpdatePointer() { if (device.GetPressDown(SteamVR_Controller.ButtonMask.Touchpad)) { TogglePointer(true); } if (device.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad)) { if (pointerContactTarget != null && teleportWithPointer) { Teleport(); } TogglePointer(false); } if (pointer.gameObject.activeSelf) { Ray pointerRaycast = new Ray(transform.position, transform.forward); RaycastHit pointerCollidedWith; bool rayHit = Physics.Raycast(pointerRaycast, out pointerCollidedWith); float pointerBeamLength = GetPointerBeamLength(rayHit, pointerCollidedWith); SetPointerTransform(pointerBeamLength, pointerThickness); } } void SnapCanGrabObjectToController(GameObject obj) { obj.transform.position = controllerAttachPoint.transform.position; controllerAttachJoint = obj.AddComponent<FixedJoint>(); controllerAttachJoint.connectedBody = controllerAttachPoint; ToggleGrabbableObjectHighlight(false); } Rigidbody ReleaseGrabbedObjectFromController() { var jointGameObject = controllerAttachJoint.gameObject; var rigidbody = jointGameObject.GetComponent<Rigidbody>(); Object.DestroyImmediate(controllerAttachJoint); controllerAttachJoint = null; return rigidbody; } void ThrowReleasedObject(Rigidbody rb) { var origin = trackedController.origin ? trackedController.origin : trackedController.transform.parent; if (origin != null) { rb.velocity = origin.TransformVector(device.velocity); rb.angularVelocity = origin.TransformVector(device.angularVelocity); } else { rb.velocity = device.velocity; rb.angularVelocity = device.angularVelocity; } rb.maxAngularVelocity = rb.angularVelocity.magnitude; } void RecallPreviousGrabbedObject() { if (previousGrabbedObject != null && device.GetTouchDown(SteamVR_Controller.ButtonMask.ApplicationMenu)) { previousGrabbedObject.transform.position = controllerAttachPoint.transform.position; previousGrabbedObject.transform.rotation = controllerAttachPoint.transform.rotation; var rb = previousGrabbedObject.GetComponent<Rigidbody>(); rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; rb.maxAngularVelocity = 0f; } } void UpdateGrabbableObjects() { if (canGrabObject != null) { if (controllerAttachJoint == null && device.GetTouchDown(SteamVR_Controller.ButtonMask.Trigger)) { previousGrabbedObject = canGrabObject; SnapCanGrabObjectToController(canGrabObject); } else if (controllerAttachJoint != null && device.GetTouchUp(SteamVR_Controller.ButtonMask.Trigger)) { Rigidbody releasedObjectRigidBody = ReleaseGrabbedObjectFromController(); ThrowReleasedObject(releasedObjectRigidBody); } } } Renderer[] GetObjectRendererArray(GameObject obj) { return (obj.GetComponents<Renderer>().Length > 0 ? obj.GetComponents<Renderer>() : obj.GetComponentsInChildren<Renderer>()); } Color[] BuildObjectColorArray(GameObject obj, Color defaultColor) { Renderer[] rendererArray = GetObjectRendererArray(obj); int length = rendererArray.Length; Color[] colors = new Color[length]; for (int i = 0; i < length; i++) { colors[i] = defaultColor; } return colors; } Color[] StoreObjectOriginalColors(GameObject obj) { Renderer[] rendererArray = GetObjectRendererArray(obj); int length = rendererArray.Length; Color[] colors = new Color[length]; for (int i = 0; i < length; i++) { var renderer = rendererArray[i]; colors[i] = renderer.material.color; } return colors; } void ChangeObjectColor(GameObject obj, Color[] colors) { Renderer[] rendererArray = GetObjectRendererArray(obj); int i = 0; foreach (Renderer renderer in rendererArray) { renderer.material.color = colors[i]; i++; } } void ToggleGrabbableObjectHighlight(bool highlightObject) { if (highlightGrabbableObject && canGrabObject != null) { if (highlightObject) { var colorArray = BuildObjectColorArray(canGrabObject, grabObjectHightlightColor); ChangeObjectColor(canGrabObject, colorArray); } else { ChangeObjectColor(canGrabObject, canGrabObjectOriginalColors); } } } void FixedUpdate() { device = SteamVR_Controller.Input((int)trackedController.index); RecallPreviousGrabbedObject(); UpdateGrabbableObjects(); } void Update() { UpdatePointer(); } void OnTriggerEnter(Collider collider) { if (collider.tag == "Grabbable") { if (canGrabObject == null) { canGrabObjectOriginalColors = StoreObjectOriginalColors(collider.gameObject); } canGrabObject = collider.gameObject; ToggleGrabbableObjectHighlight(true); } } void OnTriggerExit(Collider collider) { if (collider.tag == "Grabbable") { ToggleGrabbableObjectHighlight(false); canGrabObject = null; } } }