//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: The bow // //============================================================================= using UnityEngine; using System.Collections; using System.Collections.Generic; namespace Valve.VR.InteractionSystem { //------------------------------------------------------------------------- [RequireComponent( typeof( Interactable ) )] public class Longbow : MonoBehaviour { public enum Handedness { Left, Right }; public Handedness currentHandGuess = Handedness.Left; private float timeOfPossibleHandSwitch = 0f; private float timeBeforeConfirmingHandSwitch = 1.5f; private bool possibleHandSwitch = false; public Transform pivotTransform; public Transform handleTransform; private Hand hand; private ArrowHand arrowHand; public Transform nockTransform; public Transform nockRestTransform; public bool autoSpawnArrowHand = true; public ItemPackage arrowHandItemPackage; public GameObject arrowHandPrefab; public bool nocked; public bool pulled; private const float minPull = 0.05f; private const float maxPull = 0.5f; private float nockDistanceTravelled = 0f; private float hapticDistanceThreshold = 0.01f; private float lastTickDistance; private const float bowPullPulseStrengthLow = 100; private const float bowPullPulseStrengthHigh = 500; private Vector3 bowLeftVector; public float arrowMinVelocity = 3f; public float arrowMaxVelocity = 30f; private float arrowVelocity = 30f; private float minStrainTickTime = 0.1f; private float maxStrainTickTime = 0.5f; private float nextStrainTick = 0; private bool lerpBackToZeroRotation; private float lerpStartTime; private float lerpDuration = 0.15f; private Quaternion lerpStartRotation; private float nockLerpStartTime; private Quaternion nockLerpStartRotation; public float drawOffset = 0.06f; public LinearMapping bowDrawLinearMapping; private Vector3 lateUpdatePos; private Quaternion lateUpdateRot; public SoundBowClick drawSound; private float drawTension; public SoundPlayOneshot arrowSlideSound; public SoundPlayOneshot releaseSound; public SoundPlayOneshot nockSound; SteamVR_Events.Action newPosesAppliedAction; //------------------------------------------------- private void OnAttachedToHand( Hand attachedHand ) { hand = attachedHand; } //------------------------------------------------- private void HandAttachedUpdate( Hand hand ) { // Reset transform since we cheated it right after getting poses on previous frame //transform.localPosition = Vector3.zero; //transform.localRotation = Quaternion.identity; // Update handedness guess EvaluateHandedness(); if ( nocked ) { Vector3 nockToarrowHand = ( arrowHand.arrowNockTransform.parent.position - nockRestTransform.position ); // Vector from bow nock transform to arrowhand nock transform - used to align bow when drawing // Align bow // Time lerp value used for ramping into drawn bow orientation float lerp = Util.RemapNumberClamped( Time.time, nockLerpStartTime, ( nockLerpStartTime + lerpDuration ), 0f, 1f ); float pullLerp = Util.RemapNumberClamped( nockToarrowHand.magnitude, minPull, maxPull, 0f, 1f ); // Normalized current state of bow draw 0 - 1 Vector3 arrowNockTransformToHeadset = ( ( Player.instance.hmdTransform.position + ( Vector3.down * 0.05f ) ) - arrowHand.arrowNockTransform.parent.position ).normalized; Vector3 arrowHandPosition = ( arrowHand.arrowNockTransform.parent.position + ( ( arrowNockTransformToHeadset * drawOffset ) * pullLerp ) ); // Use this line to lerp arrowHand nock position //Vector3 arrowHandPosition = arrowHand.arrowNockTransform.position; // Use this line if we don't want to lerp arrowHand nock position Vector3 pivotToString = ( arrowHandPosition - pivotTransform.position ).normalized; Vector3 pivotToLowerHandle = ( handleTransform.position - pivotTransform.position ).normalized; bowLeftVector = -Vector3.Cross( pivotToLowerHandle, pivotToString ); pivotTransform.rotation = Quaternion.Lerp( nockLerpStartRotation, Quaternion.LookRotation( pivotToString, bowLeftVector ), lerp ); // Move nock position if ( Vector3.Dot( nockToarrowHand, -nockTransform.forward ) > 0 ) { float distanceToarrowHand = nockToarrowHand.magnitude * lerp; nockTransform.localPosition = new Vector3( 0f, 0f, Mathf.Clamp( -distanceToarrowHand, -maxPull, 0f ) ); nockDistanceTravelled = -nockTransform.localPosition.z; arrowVelocity = Util.RemapNumber( nockDistanceTravelled, minPull, maxPull, arrowMinVelocity, arrowMaxVelocity ); drawTension = Util.RemapNumberClamped( nockDistanceTravelled, 0, maxPull, 0f, 1f ); this.bowDrawLinearMapping.value = drawTension; // Send drawTension value to LinearMapping script, which drives the bow draw animation if ( nockDistanceTravelled > minPull ) { pulled = true; } else { pulled = false; } if ( ( nockDistanceTravelled > ( lastTickDistance + hapticDistanceThreshold ) ) || nockDistanceTravelled < ( lastTickDistance - hapticDistanceThreshold ) ) { ushort hapticStrength = (ushort)Util.RemapNumber( nockDistanceTravelled, 0, maxPull, bowPullPulseStrengthLow, bowPullPulseStrengthHigh ); hand.TriggerHapticPulse( hapticStrength ); hand.otherHand.TriggerHapticPulse( hapticStrength ); drawSound.PlayBowTensionClicks( drawTension ); lastTickDistance = nockDistanceTravelled; } if ( nockDistanceTravelled >= maxPull ) { if ( Time.time > nextStrainTick ) { hand.TriggerHapticPulse( 400 ); hand.otherHand.TriggerHapticPulse( 400 ); drawSound.PlayBowTensionClicks( drawTension ); nextStrainTick = Time.time + Random.Range( minStrainTickTime, maxStrainTickTime ); } } } else { nockTransform.localPosition = new Vector3( 0f, 0f, 0f ); this.bowDrawLinearMapping.value = 0f; } } else { if ( lerpBackToZeroRotation ) { float lerp = Util.RemapNumber( Time.time, lerpStartTime, lerpStartTime + lerpDuration, 0, 1 ); pivotTransform.localRotation = Quaternion.Lerp( lerpStartRotation, Quaternion.identity, lerp ); if ( lerp >= 1 ) { lerpBackToZeroRotation = false; } } } } //------------------------------------------------- public void ArrowReleased() { nocked = false; hand.HoverUnlock( GetComponent() ); hand.otherHand.HoverUnlock( arrowHand.GetComponent() ); if ( releaseSound != null ) { releaseSound.Play(); } this.StartCoroutine( this.ResetDrawAnim() ); } //------------------------------------------------- private IEnumerator ResetDrawAnim() { float startTime = Time.time; float startLerp = drawTension; while ( Time.time < ( startTime + 0.02f ) ) { float lerp = Util.RemapNumberClamped( Time.time, startTime, startTime + 0.02f, startLerp, 0f ); this.bowDrawLinearMapping.value = lerp; yield return null; } this.bowDrawLinearMapping.value = 0; yield break; } //------------------------------------------------- public float GetArrowVelocity() { return arrowVelocity; } //------------------------------------------------- public void StartRotationLerp() { lerpStartTime = Time.time; lerpBackToZeroRotation = true; lerpStartRotation = pivotTransform.localRotation; Util.ResetTransform( nockTransform ); } //------------------------------------------------- public void StartNock( ArrowHand currentArrowHand ) { arrowHand = currentArrowHand; hand.HoverLock( GetComponent() ); nocked = true; nockLerpStartTime = Time.time; nockLerpStartRotation = pivotTransform.rotation; // Sound of arrow sliding on nock as it's being pulled back arrowSlideSound.Play(); // Decide which hand we're drawing with and lerp to the correct side DoHandednessCheck(); } //------------------------------------------------- private void EvaluateHandedness() { var handType = hand.handType; if ( handType == SteamVR_Input_Sources.LeftHand )// Bow hand is further left than arrow hand. { // We were considering a switch, but the current controller orientation matches our currently assigned handedness, so no longer consider a switch if ( possibleHandSwitch && currentHandGuess == Handedness.Left ) { possibleHandSwitch = false; } // If we previously thought the bow was right-handed, and were not already considering switching, start considering a switch if ( !possibleHandSwitch && currentHandGuess == Handedness.Right ) { possibleHandSwitch = true; timeOfPossibleHandSwitch = Time.time; } // If we are considering a handedness switch, and it's been this way long enough, switch if ( possibleHandSwitch && Time.time > ( timeOfPossibleHandSwitch + timeBeforeConfirmingHandSwitch ) ) { currentHandGuess = Handedness.Left; possibleHandSwitch = false; } } else // Bow hand is further right than arrow hand { // We were considering a switch, but the current controller orientation matches our currently assigned handedness, so no longer consider a switch if ( possibleHandSwitch && currentHandGuess == Handedness.Right ) { possibleHandSwitch = false; } // If we previously thought the bow was right-handed, and were not already considering switching, start considering a switch if ( !possibleHandSwitch && currentHandGuess == Handedness.Left ) { possibleHandSwitch = true; timeOfPossibleHandSwitch = Time.time; } // If we are considering a handedness switch, and it's been this way long enough, switch if ( possibleHandSwitch && Time.time > ( timeOfPossibleHandSwitch + timeBeforeConfirmingHandSwitch ) ) { currentHandGuess = Handedness.Right; possibleHandSwitch = false; } } } //------------------------------------------------- private void DoHandednessCheck() { // Based on our current best guess about hand, switch bow orientation and arrow lerp direction if ( currentHandGuess == Handedness.Left ) { pivotTransform.localScale = new Vector3( 1f, 1f, 1f ); } else { pivotTransform.localScale = new Vector3( 1f, -1f, 1f ); } } //------------------------------------------------- public void ArrowInPosition() { DoHandednessCheck(); if ( nockSound != null ) { nockSound.Play(); } } //------------------------------------------------- public void ReleaseNock() { // ArrowHand tells us to do this when we release the buttons when bow is nocked but not drawn far enough nocked = false; hand.HoverUnlock( GetComponent() ); this.StartCoroutine( this.ResetDrawAnim() ); } //------------------------------------------------- private void ShutDown() { if ( hand != null && hand.otherHand.currentAttachedObject != null ) { if ( hand.otherHand.currentAttachedObject.GetComponent() != null ) { if ( hand.otherHand.currentAttachedObject.GetComponent().itemPackage == arrowHandItemPackage ) { hand.otherHand.DetachObject( hand.otherHand.currentAttachedObject ); } } } } //------------------------------------------------- private void OnHandFocusLost( Hand hand ) { gameObject.SetActive( false ); } //------------------------------------------------- private void OnHandFocusAcquired( Hand hand ) { gameObject.SetActive( true ); OnAttachedToHand( hand ); } //------------------------------------------------- private void OnDetachedFromHand( Hand hand ) { Destroy( gameObject ); } //------------------------------------------------- void OnDestroy() { ShutDown(); } } }