// Slingshot Jump|Locomotion|20121 namespace VRTK { using UnityEngine; /// /// Event Payload /// /// this object public delegate void SlingshotJumpEventHandler(object sender); /// /// Provides the ability for the SDK Camera Rig to be thrown around with a jumping motion by slingshotting based on the pull back of each valid controller. /// /// /// **Required Components:** /// * `VRTK_PlayerClimb` - A Player Climb script for dealing with the physical throwing of the play area as if throwing off an invisible climbed object. /// * `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_SlingshotJump` script on the same GameObject as the `VRTK_PlayerClimb` script. /// /// /// `VRTK/Examples/037_CameraRig_ClimbingFalling` shows how to set up a scene with slingshot jumping. This script just needs to be added to the PlayArea object and the requested forces and buttons set. /// [AddComponentMenu("VRTK/Scripts/Locomotion/VRTK_SlingshotJump")] public class VRTK_SlingshotJump : MonoBehaviour { [Header("SlingshotJump Settings")] [Tooltip("How close together the button releases have to be to initiate a jump.")] public float releaseWindowTime = 0.5f; [Tooltip("Multiplier that increases the jump strength.")] public float velocityMultiplier = 5.0f; [Tooltip("The maximum velocity a jump can be.")] public float velocityMax = 8.0f; [Tooltip("The button that will initiate the slingshot move.")] [SerializeField] protected VRTK_ControllerEvents.ButtonAlias activationButton = VRTK_ControllerEvents.ButtonAlias.GripPress; [Tooltip("The button that will cancel an already tensioned sling shot.")] [SerializeField] protected VRTK_ControllerEvents.ButtonAlias cancelButton = VRTK_ControllerEvents.ButtonAlias.Undefined; [Tooltip("The Body Physics script to deal with the physics and gravity of the play area. If the script is being applied onto an object that already has a VRTK_BodyPhysics component, this parameter can be left blank as it will be auto populated by the script at runtime.")] [SerializeField] protected VRTK_BodyPhysics bodyPhysics; [Tooltip("The Player Climb script to deal ability to throw the play area. If the script is being applied onto an object that already has a VRTK_PlayerClimb component, this parameter can be left blank as it will be auto populated by the script at runtime.")] [SerializeField] protected VRTK_PlayerClimb playerClimb; [Tooltip("The Teleporter script to deal play area teleporting. If the script is being applied onto an object that already has a VRTK_BasicTeleport component, this parameter can be left blank as it will be auto populated by the script at runtime.")] [SerializeField] protected VRTK_BasicTeleport teleporter; /// /// Emitted when a slingshot jump occurs /// public event SlingshotJumpEventHandler SlingshotJumped; protected Transform playArea; protected Vector3 leftStartAimPosition; protected Vector3 leftReleasePosition; protected bool leftIsAiming; protected Vector3 rightStartAimPosition; protected Vector3 rightReleasePosition; protected bool rightIsAiming; protected VRTK_ControllerEvents leftControllerEvents; protected VRTK_ControllerEvents rightControllerEvents; protected VRTK_InteractGrab leftControllerGrab; protected VRTK_InteractGrab rightControllerGrab; protected bool leftButtonReleased; protected bool rightButtonReleased; protected float countDownEndTime; /// /// The SetActivationButton method gets the button used to activate a slingshot jump. /// /// Returns the button used for slingshot activation. public virtual VRTK_ControllerEvents.ButtonAlias GetActivationButton() { return activationButton; } /// /// The SetActivationButton method sets the button used to activate a slingshot jump. /// /// The controller button to use to activate the jump. public virtual void SetActivationButton(VRTK_ControllerEvents.ButtonAlias button) { InitControllerListeners(false); activationButton = button; InitControllerListeners(true); } /// /// The GetCancelButton method gets the button used to cancel a slingshot jump. /// /// Returns the button used to cancel a slingshot jump. public virtual VRTK_ControllerEvents.ButtonAlias GetCancelButton() { return cancelButton; } /// /// The SetCancelButton method sets the button used to cancel a slingshot jump. /// /// The controller button to use to cancel the jump. public virtual void SetCancelButton(VRTK_ControllerEvents.ButtonAlias button) { InitControllerListeners(false); cancelButton = button; InitControllerListeners(true); } protected virtual void Awake() { bodyPhysics = (bodyPhysics != null ? bodyPhysics : FindObjectOfType()); playerClimb = (playerClimb != null ? playerClimb : FindObjectOfType()); VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this); } protected virtual void OnEnable() { InitListeners(true); playArea = VRTK_DeviceFinder.PlayAreaTransform(); } protected virtual void OnDisable() { UnAim(); InitListeners(false); } protected virtual void OnDestroy() { VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this); } protected virtual void LeftButtonPressed(object sender, ControllerInteractionEventArgs e) { // Check for new left aim if (!leftIsAiming && !IsClimbing()) { leftIsAiming = true; leftStartAimPosition = playArea.InverseTransformPoint(leftControllerEvents.gameObject.transform.position); } } protected virtual void RightButtonPressed(object sender, ControllerInteractionEventArgs e) { // Check for new right aim if (!rightIsAiming && !IsClimbing()) { rightIsAiming = true; rightStartAimPosition = playArea.InverseTransformPoint(rightControllerEvents.gameObject.transform.position); } } protected virtual void LeftButtonReleased(object sender, ControllerInteractionEventArgs e) { // Check for release states if (leftIsAiming) { leftReleasePosition = playArea.InverseTransformPoint(leftControllerEvents.gameObject.transform.position); if (!rightButtonReleased) { countDownEndTime = Time.time + releaseWindowTime; } leftButtonReleased = true; } CheckForReset(); CheckForJump(); } protected virtual void RightButtonReleased(object sender, ControllerInteractionEventArgs e) { // Check for release states if (rightIsAiming) { rightReleasePosition = playArea.InverseTransformPoint(rightControllerEvents.gameObject.transform.position); if (!leftButtonReleased) { countDownEndTime = Time.time + releaseWindowTime; } rightButtonReleased = true; } CheckForReset(); CheckForJump(); } protected virtual void CancelButtonPressed(object sender, ControllerInteractionEventArgs e) { UnAim(); } protected virtual void CheckForReset() { if ((leftButtonReleased || rightButtonReleased) && Time.time > countDownEndTime) { UnAim(); } } protected virtual void CheckForJump() { if (leftButtonReleased && rightButtonReleased && !bodyPhysics.IsFalling()) { Vector3 leftDir = leftStartAimPosition - leftReleasePosition; Vector3 rightDir = rightStartAimPosition - rightReleasePosition; Vector3 localJumpDir = leftDir + rightDir; Vector3 worldJumpDir = playArea.transform.TransformVector(localJumpDir); Vector3 jumpVector = worldJumpDir * velocityMultiplier; if (jumpVector.magnitude > velocityMax) { jumpVector = jumpVector.normalized * velocityMax; } bodyPhysics.ApplyBodyVelocity(jumpVector, true, true); UnAim(); OnSlingshotJumped(); } } protected void OnSlingshotJumped() { if (SlingshotJumped != null) { SlingshotJumped(this); } } protected void InitListeners(bool state) { InitTeleportListener(state); InitControllerListeners(state); } protected void InitTeleportListener(bool state) { teleporter = (teleporter != null ? teleporter : FindObjectOfType()); if (teleporter != null) { if (state == true) { teleporter.Teleporting += new TeleportEventHandler(OnTeleport); } else { teleporter.Teleporting -= new TeleportEventHandler(OnTeleport); } } } protected void InitControllerListeners(bool state) { InitControllerListener(state, VRTK_DeviceFinder.GetControllerLeftHand(), ref leftControllerEvents, ref leftControllerGrab, LeftButtonPressed, LeftButtonReleased); InitControllerListener(state, VRTK_DeviceFinder.GetControllerRightHand(), ref rightControllerEvents, ref rightControllerGrab, RightButtonPressed, RightButtonReleased); } protected void InitControllerListener(bool state, GameObject controller, ref VRTK_ControllerEvents events, ref VRTK_InteractGrab grab, ControllerInteractionEventHandler triggerPressed, ControllerInteractionEventHandler triggerReleased) { if (controller != null) { events = controller.GetComponentInChildren(); grab = controller.GetComponentInChildren(); if (events != null) { if (state == true) { events.SubscribeToButtonAliasEvent(activationButton, true, triggerPressed); events.SubscribeToButtonAliasEvent(activationButton, false, triggerReleased); if (cancelButton != VRTK_ControllerEvents.ButtonAlias.Undefined) { events.SubscribeToButtonAliasEvent(cancelButton, true, CancelButtonPressed); } } else { events.UnsubscribeToButtonAliasEvent(activationButton, true, triggerPressed); events.UnsubscribeToButtonAliasEvent(activationButton, false, triggerReleased); if (cancelButton != VRTK_ControllerEvents.ButtonAlias.Undefined) { events.UnsubscribeToButtonAliasEvent(cancelButton, true, CancelButtonPressed); } } } } } protected void OnTeleport(object sender, DestinationMarkerEventArgs e) { UnAim(); } protected void UnAim() { leftIsAiming = false; rightIsAiming = false; leftButtonReleased = false; rightButtonReleased = false; } protected bool IsClimbing() { return (playerClimb != null && playerClimb.IsClimbing()); } } }