//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: The object attached to the player's hand that spawns and fires the // arrow // //============================================================================= using UnityEngine; using System.Collections; using System.Collections.Generic; namespace Valve.VR.InteractionSystem { //------------------------------------------------------------------------- public class ArrowHand : MonoBehaviour { private Hand hand; private Longbow bow; private GameObject currentArrow; public GameObject arrowPrefab; public Transform arrowNockTransform; public float nockDistance = 0.1f; public float lerpCompleteDistance = 0.08f; public float rotationLerpThreshold = 0.15f; public float positionLerpThreshold = 0.15f; private bool allowArrowSpawn = true; private bool nocked; private GrabTypes nockedWithType = GrabTypes.None; private bool inNockRange = false; private bool arrowLerpComplete = false; public SoundPlayOneshot arrowSpawnSound; private AllowTeleportWhileAttachedToHand allowTeleport = null; public int maxArrowCount = 10; private List arrowList; //------------------------------------------------- void Awake() { allowTeleport = GetComponent(); //allowTeleport.teleportAllowed = true; allowTeleport.overrideHoverLock = false; arrowList = new List(); } //------------------------------------------------- private void OnAttachedToHand( Hand attachedHand ) { hand = attachedHand; FindBow(); } //------------------------------------------------- private GameObject InstantiateArrow() { GameObject arrow = Instantiate( arrowPrefab, arrowNockTransform.position, arrowNockTransform.rotation ) as GameObject; arrow.name = "Bow Arrow"; arrow.transform.parent = arrowNockTransform; Util.ResetTransform( arrow.transform ); arrowList.Add( arrow ); while ( arrowList.Count > maxArrowCount ) { GameObject oldArrow = arrowList[0]; arrowList.RemoveAt( 0 ); if ( oldArrow ) { Destroy( oldArrow ); } } return arrow; } //------------------------------------------------- private void HandAttachedUpdate( Hand hand ) { if ( bow == null ) { FindBow(); } if ( bow == null ) { return; } if ( allowArrowSpawn && ( currentArrow == null ) ) // If we're allowed to have an active arrow in hand but don't yet, spawn one { currentArrow = InstantiateArrow(); arrowSpawnSound.Play(); } float distanceToNockPosition = Vector3.Distance( transform.parent.position, bow.nockTransform.position ); // If there's an arrow spawned in the hand and it's not nocked yet if ( !nocked ) { // If we're close enough to nock position that we want to start arrow rotation lerp, do so if ( distanceToNockPosition < rotationLerpThreshold ) { float lerp = Util.RemapNumber( distanceToNockPosition, rotationLerpThreshold, lerpCompleteDistance, 0, 1 ); arrowNockTransform.rotation = Quaternion.Lerp( arrowNockTransform.parent.rotation, bow.nockRestTransform.rotation, lerp ); } else // Not close enough for rotation lerp, reset rotation { arrowNockTransform.localRotation = Quaternion.identity; } // If we're close enough to the nock position that we want to start arrow position lerp, do so if ( distanceToNockPosition < positionLerpThreshold ) { float posLerp = Util.RemapNumber( distanceToNockPosition, positionLerpThreshold, lerpCompleteDistance, 0, 1 ); posLerp = Mathf.Clamp( posLerp, 0f, 1f ); arrowNockTransform.position = Vector3.Lerp( arrowNockTransform.parent.position, bow.nockRestTransform.position, posLerp ); } else // Not close enough for position lerp, reset position { arrowNockTransform.position = arrowNockTransform.parent.position; } // Give a haptic tick when lerp is visually complete if ( distanceToNockPosition < lerpCompleteDistance ) { if ( !arrowLerpComplete ) { arrowLerpComplete = true; hand.TriggerHapticPulse( 500 ); } } else { if ( arrowLerpComplete ) { arrowLerpComplete = false; } } // Allow nocking the arrow when controller is close enough if ( distanceToNockPosition < nockDistance ) { if ( !inNockRange ) { inNockRange = true; bow.ArrowInPosition(); } } else { if ( inNockRange ) { inNockRange = false; } } GrabTypes bestGrab = hand.GetBestGrabbingType(GrabTypes.Pinch, true); // If arrow is close enough to the nock position and we're pressing the trigger, and we're not nocked yet, Nock if ( ( distanceToNockPosition < nockDistance ) && bestGrab != GrabTypes.None && !nocked ) { if ( currentArrow == null ) { currentArrow = InstantiateArrow(); } nocked = true; nockedWithType = bestGrab; bow.StartNock( this ); hand.HoverLock( GetComponent() ); allowTeleport.teleportAllowed = false; currentArrow.transform.parent = bow.nockTransform; Util.ResetTransform( currentArrow.transform ); Util.ResetTransform( arrowNockTransform ); } } // If arrow is nocked, and we release the trigger if ( nocked && hand.IsGrabbingWithType(nockedWithType) == false ) { if ( bow.pulled ) // If bow is pulled back far enough, fire arrow, otherwise reset arrow in arrowhand { FireArrow(); } else { arrowNockTransform.rotation = currentArrow.transform.rotation; currentArrow.transform.parent = arrowNockTransform; Util.ResetTransform( currentArrow.transform ); nocked = false; nockedWithType = GrabTypes.None; bow.ReleaseNock(); hand.HoverUnlock( GetComponent() ); allowTeleport.teleportAllowed = true; } bow.StartRotationLerp(); // Arrow is releasing from the bow, tell the bow to lerp back to controller rotation } } //------------------------------------------------- private void OnDetachedFromHand( Hand hand ) { Destroy( gameObject ); } //------------------------------------------------- private void FireArrow() { currentArrow.transform.parent = null; Arrow arrow = currentArrow.GetComponent(); arrow.StartRelease(); arrow.shaftRB.isKinematic = false; arrow.shaftRB.useGravity = true; arrow.shaftRB.transform.GetComponent().enabled = true; arrow.arrowHeadRB.isKinematic = false; arrow.arrowHeadRB.useGravity = true; arrow.arrowHeadRB.transform.GetComponent().enabled = true; arrow.arrowHeadRB.AddForce( currentArrow.transform.forward * bow.GetArrowVelocity(), ForceMode.VelocityChange ); arrow.arrowHeadRB.AddTorque( currentArrow.transform.forward * 10 ); arrow.shaftRB.velocity = arrow.arrowHeadRB.velocity; arrow.shaftRB.angularVelocity = arrow.arrowHeadRB.angularVelocity; nocked = false; nockedWithType = GrabTypes.None; currentArrow.GetComponent().ArrowReleased( bow.GetArrowVelocity() ); bow.ArrowReleased(); allowArrowSpawn = false; Invoke( "EnableArrowSpawn", 0.5f ); StartCoroutine( ArrowReleaseHaptics() ); currentArrow = null; allowTeleport.teleportAllowed = true; } //------------------------------------------------- private void EnableArrowSpawn() { allowArrowSpawn = true; } //------------------------------------------------- private IEnumerator ArrowReleaseHaptics() { yield return new WaitForSeconds( 0.05f ); hand.otherHand.TriggerHapticPulse( 1500 ); yield return new WaitForSeconds( 0.05f ); hand.otherHand.TriggerHapticPulse( 800 ); yield return new WaitForSeconds( 0.05f ); hand.otherHand.TriggerHapticPulse( 500 ); yield return new WaitForSeconds( 0.05f ); hand.otherHand.TriggerHapticPulse( 300 ); } //------------------------------------------------- private void OnHandFocusLost( Hand hand ) { gameObject.SetActive( false ); } //------------------------------------------------- private void OnHandFocusAcquired( Hand hand ) { gameObject.SetActive( true ); } //------------------------------------------------- private void FindBow() { bow = hand.otherHand.GetComponentInChildren(); } } }