// Player Climb|Locomotion|20120 namespace VRTK { using GrabAttachMechanics; using UnityEngine; /// /// Event Payload /// /// The reference to the controller doing the interaction. /// The GameObject of the interactable object that is being interacted with by the controller. public struct PlayerClimbEventArgs { public VRTK_ControllerReference controllerReference; public GameObject target; } /// /// Event Payload /// /// this object /// public delegate void PlayerClimbEventHandler(object sender, PlayerClimbEventArgs e); /// /// Provides the ability for the SDK Camera Rig to be moved around based on whether an Interact Grab is interacting with a Climbable Interactable Object to simulate climbing. /// /// /// **Required Components:** /// * `VRTK_BodyPhysics` - A Body Physics script to deal with the effects of physics and gravity on the play area. /// /// **Optional Components:** /// * `VRTK_BasicTeleport` - A Teleporter script to use when snapping the play area to the nearest floor when releasing from grab. /// * `VRTK_HeadsetCollision` - A Headset Collision script to determine when the headset is colliding with geometry to know when to reset to a valid location. /// * `VRTK_PositionRewind` - A Position Rewind script to utilise when resetting to a valid location upon ungrabbing whilst colliding with geometry. /// /// **Script Usage:** /// * Place the `VRTK_PlayerClimb` script on any active scene GameObject. /// /// **Script Dependencies:** /// * The controller Script Alias GameObject requires the Interact Touch and Interact Grab scripts to allow for touching and grabbing of Interactable Objects. /// * An Interactable Object in the scene that has the Climbable Grab Attach Mechanic. /// /// /// `VRTK/Examples/037_CameraRig_ClimbingFalling` shows how to set up a scene with player climbing. There are many different examples showing how the same system can be used in unique ways. /// [AddComponentMenu("VRTK/Scripts/Locomotion/VRTK_PlayerClimb")] public class VRTK_PlayerClimb : MonoBehaviour { [Header("Climb Settings")] [Tooltip("Will scale movement up and down based on the player transform's scale.")] public bool usePlayerScale = true; [Header("Custom Settings")] [Tooltip("The Body Physics script to use for dealing with climbing and falling. If this is left blank then the script will need to be applied to the same GameObject.")] public VRTK_BodyPhysics bodyPhysics; [Tooltip("The Teleport script to use when snapping to nearest floor on release. If this is left blank then a Teleport script will need to be applied to the same GameObject.")] public VRTK_BasicTeleport teleporter; [Tooltip("The Headset Collision script to use for determining if the user is climbing inside a collidable object. If this is left blank then the script will need to be applied to the same GameObject.")] public VRTK_HeadsetCollision headsetCollision; [Tooltip("The Position Rewind script to use for dealing resetting invalid positions. If this is left blank then the script will need to be applied to the same GameObject.")] public VRTK_PositionRewind positionRewind; /// /// Emitted when player climbing has started. /// public event PlayerClimbEventHandler PlayerClimbStarted; /// /// Emitted when player climbing has ended. /// public event PlayerClimbEventHandler PlayerClimbEnded; protected Transform playArea; protected Vector3 startControllerScaledLocalPosition; protected Vector3 startGrabPointLocalPosition; protected Vector3 startPlayAreaWorldOffset; protected GameObject grabbingController; protected GameObject climbingObject; protected Quaternion climbingObjectLastRotation; protected bool isClimbing; protected bool useGrabbedObjectRotation; /// /// The IsClimbing method will return if climbing is currently taking place or not. /// /// Returns `true` if climbing is currently taking place. public virtual bool IsClimbing() { return isClimbing; } protected virtual void Awake() { bodyPhysics = (bodyPhysics != null ? bodyPhysics : FindObjectOfType()); if (bodyPhysics == null) { VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_SCENE, "VRTK_PlayerClimb", "VRTK_BodyPhysics")); } teleporter = (teleporter != null ? teleporter : FindObjectOfType()); headsetCollision = (headsetCollision != null ? headsetCollision : FindObjectOfType()); positionRewind = (positionRewind != null ? positionRewind : FindObjectOfType()); VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this); } protected virtual void OnEnable() { playArea = VRTK_DeviceFinder.PlayAreaTransform(); InitListeners(true); } protected virtual void OnDisable() { Ungrab(false, null, climbingObject); InitListeners(false); } protected virtual void OnDestroy() { VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this); } protected virtual void Update() { if (isClimbing) { Vector3 controllerLocalOffset = GetScaledLocalPosition(grabbingController.transform) - startControllerScaledLocalPosition; Vector3 grabPointWorldPosition = climbingObject.transform.TransformPoint(startGrabPointLocalPosition); playArea.position = grabPointWorldPosition + startPlayAreaWorldOffset - controllerLocalOffset; if (useGrabbedObjectRotation) { Vector3 lastRotationVec = climbingObjectLastRotation * Vector3.forward; Vector3 currentObectRotationVec = climbingObject.transform.rotation * Vector3.forward; Vector3 axis = Vector3.Cross(lastRotationVec, currentObectRotationVec); float angle = Vector3.Angle(lastRotationVec, currentObectRotationVec); playArea.RotateAround(grabPointWorldPosition, axis, angle); climbingObjectLastRotation = climbingObject.transform.rotation; } if (positionRewind != null && !IsHeadsetColliding()) { positionRewind.SetLastGoodPosition(); } } } protected virtual void OnPlayerClimbStarted(PlayerClimbEventArgs e) { if (PlayerClimbStarted != null) { PlayerClimbStarted(this, e); } } protected virtual void OnPlayerClimbEnded(PlayerClimbEventArgs e) { if (PlayerClimbEnded != null) { PlayerClimbEnded(this, e); } } protected virtual PlayerClimbEventArgs SetPlayerClimbEvent(VRTK_ControllerReference controllerReference, GameObject target) { PlayerClimbEventArgs e; e.controllerReference = controllerReference; e.target = target; return e; } protected virtual void InitListeners(bool state) { InitControllerListeners(VRTK_DeviceFinder.GetControllerLeftHand(), state); InitControllerListeners(VRTK_DeviceFinder.GetControllerRightHand(), state); InitTeleportListener(state); } protected virtual void InitTeleportListener(bool state) { if (teleporter != null) { if (state) { teleporter.Teleporting += new TeleportEventHandler(OnTeleport); } else { teleporter.Teleporting -= new TeleportEventHandler(OnTeleport); } } } protected virtual void OnTeleport(object sender, DestinationMarkerEventArgs e) { if (isClimbing) { Ungrab(false, e.controllerReference, e.target.gameObject); } } protected virtual Vector3 GetScaledLocalPosition(Transform objTransform) { if (usePlayerScale) { return (playArea.localRotation * Vector3.Scale(objTransform.localPosition, playArea.localScale)); } return (playArea.localRotation * objTransform.localPosition); } protected virtual void OnGrabObject(object sender, ObjectInteractEventArgs e) { if (IsClimbableObject(e.target)) { GameObject controller = ((VRTK_InteractGrab)sender).gameObject; GameObject actualController = VRTK_DeviceFinder.GetActualController(controller); Grab(actualController, e.controllerReference, e.target); } } protected virtual void OnUngrabObject(object sender, ObjectInteractEventArgs e) { GameObject controller = ((VRTK_InteractGrab)sender).gameObject; GameObject actualController = VRTK_DeviceFinder.GetActualController(controller); if (e.target != null && IsClimbableObject(e.target) && IsActiveClimbingController(actualController)) { Ungrab(true, e.controllerReference, e.target); } } protected virtual void Grab(GameObject currentGrabbingController, VRTK_ControllerReference controllerReference, GameObject target) { if (bodyPhysics == null) { return; } bodyPhysics.ResetFalling(); bodyPhysics.TogglePreventSnapToFloor(true); bodyPhysics.enableBodyCollisions = false; bodyPhysics.ToggleOnGround(false); isClimbing = true; climbingObject = target; grabbingController = currentGrabbingController; startControllerScaledLocalPosition = GetScaledLocalPosition(grabbingController.transform); startGrabPointLocalPosition = climbingObject.transform.InverseTransformPoint(grabbingController.transform.position); startPlayAreaWorldOffset = playArea.transform.position - grabbingController.transform.position; climbingObjectLastRotation = climbingObject.transform.rotation; useGrabbedObjectRotation = climbingObject.GetComponent().useObjectRotation; OnPlayerClimbStarted(SetPlayerClimbEvent(controllerReference, climbingObject)); } protected virtual void Ungrab(bool carryMomentum, VRTK_ControllerReference controllerReference, GameObject target) { if (bodyPhysics == null) { return; } isClimbing = false; if (positionRewind != null && IsHeadsetColliding()) { positionRewind.RewindPosition(); } if (IsBodyColliding() && !IsHeadsetColliding()) { bodyPhysics.ForceSnapToFloor(); } bodyPhysics.enableBodyCollisions = true; if (carryMomentum) { Vector3 velocity = Vector3.zero; if (VRTK_ControllerReference.IsValid(controllerReference)) { velocity = -VRTK_DeviceFinder.GetControllerVelocity(controllerReference); if (usePlayerScale) { velocity = playArea.TransformVector(velocity); } else { velocity = playArea.TransformDirection(velocity); } } bodyPhysics.ApplyBodyVelocity(velocity, true, true); } grabbingController = null; climbingObject = null; OnPlayerClimbEnded(SetPlayerClimbEvent(controllerReference, target)); } protected virtual bool IsActiveClimbingController(GameObject controller) { return (controller == grabbingController); } protected virtual bool IsClimbableObject(GameObject obj) { VRTK_InteractableObject interactObject = obj.GetComponent(); return (interactObject != null && interactObject.grabAttachMechanicScript && interactObject.grabAttachMechanicScript.IsClimbable()); } protected virtual void InitControllerListeners(GameObject controller, bool state) { if (controller != null) { VRTK_InteractGrab grabScript = controller.GetComponentInChildren(); if (grabScript != null) { if (state) { grabScript.ControllerGrabInteractableObject += new ObjectInteractEventHandler(OnGrabObject); grabScript.ControllerUngrabInteractableObject += new ObjectInteractEventHandler(OnUngrabObject); } else { grabScript.ControllerGrabInteractableObject -= new ObjectInteractEventHandler(OnGrabObject); grabScript.ControllerUngrabInteractableObject -= new ObjectInteractEventHandler(OnUngrabObject); } } } } protected virtual bool IsBodyColliding() { return (bodyPhysics != null && bodyPhysics.GetCurrentCollidingObject() != null); } protected virtual bool IsHeadsetColliding() { return (headsetCollision != null && headsetCollision.IsColliding()); } } }