using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; namespace BNG { /// /// An example bow item. Configurable force and damage. /// public class Bow : GrabbableEvents { [Header("Bow Settings")] /// /// How much force to apply to the arrow, multiplied by how far back the bow is pulled /// [Tooltip("")] public float BowForce = 50f; [Tooltip("If True the BowModel Transform will align itself with the grabber holding the arrow")] public bool AlignBowToArrow = true; [Tooltip("If AlignBowToArrow is true this transform will align itself with the grabber holding the arrow")] public Transform BowModel; [Header("Arrow Settings")] [Tooltip("Arrow will rotate around this if bow is held in left hand or ArrowRestLeftHanded is null")] public Transform ArrowRest; /// /// If true, the player can grab a new arrow by holding the trigger down near the knock /// public bool CanGrabArrowFromKnock = true; [Tooltip("Name of the prefab used to create an arrow. Must be in a /Resources/ directory.")] public string ArrowPrefabName = "Arrow2"; [Tooltip("Arrow will rotate around this if bow is being held in right hand")] public Transform ArrowRestLeftHanded; // Arrow will rotate around this public Transform ArrowKnock; // Pull this back [Header("Arrow Positioning")] public bool IgnoreXPosition = false; public bool IgnoreYPosition = false; public bool AllowNegativeZ = true; [Header("Arrow Grabbing")] public bool CanGrabArrow = false; [HideInInspector] public Grabber ClosestGrabber; [HideInInspector] public Arrow GrabbedArrow; Grabbable arrowGrabbable; [HideInInspector] public Grabber arrowGrabber; // Which grabber is Grabbing the Arrow [HideInInspector] public Vector3 LastValidPosition; [Header("String Settings")] public float MaxStringDistance = 0.3f; public float StringDistance = 0; public float DrawPercent { get; private set; } = 0; private float _lastDrawPercent; // DrawPercent Last Frame private float _lastDrawHaptic; private float _lastDrawHapticTime; // Last time.time we played a haptic private bool playedDrawSound = false; Vector3 initialKnockPosition; bool holdingArrow = false; Grabbable bowGrabbable; [Header("Debug Text")] public Text PercentageUI; // Used for bow haptics List drawDefs; AudioSource audioSource; void Start() { initialKnockPosition = ArrowKnock.localPosition; bowGrabbable = GetComponent(); audioSource = GetComponent(); // Define a few haptic positions drawDefs = new List() { { new DrawDefinition() { DrawPercentage = 30f, HapticAmplitude = 0.1f, HapticFrequency = 0.1f } }, { new DrawDefinition() { DrawPercentage = 40f, HapticAmplitude = 0.1f, HapticFrequency = 0.1f } }, { new DrawDefinition() { DrawPercentage = 50f, HapticAmplitude = 0.1f, HapticFrequency = 0.1f } }, { new DrawDefinition() { DrawPercentage = 60f, HapticAmplitude = 0.1f, HapticFrequency = 0.1f } }, { new DrawDefinition() { DrawPercentage = 70f, HapticAmplitude = 0.1f, HapticFrequency = 0.1f } }, { new DrawDefinition() { DrawPercentage = 80f, HapticAmplitude = 0.1f, HapticFrequency = 0.1f } }, { new DrawDefinition() { DrawPercentage = 90f, HapticAmplitude = 0.1f, HapticFrequency = 0.9f } }, { new DrawDefinition() { DrawPercentage = 100f, HapticAmplitude = 0.1f, HapticFrequency = 1f } }, }; } void Update() { updateDrawDistance(); checkBowHaptics(); // Dropped bow. Make sure arrow has been fired if (!bowGrabbable.BeingHeld) { // Dropped bow; release arrow if(holdingArrow) { ReleaseArrow(); } resetStringPosition(); return; } holdingArrow = GrabbedArrow != null; // Grab an arrow by holding trigger in grab area if (canGrabArrowFromKnock()) { GameObject arrow = Instantiate(Resources.Load(ArrowPrefabName, typeof(GameObject))) as GameObject; arrow.transform.position = ArrowKnock.transform.position; arrow.transform.LookAt(getArrowRest()); // Use trigger when grabbing from knock Grabbable g = arrow.GetComponent(); g.GrabButton = GrabButton.Trigger; // We will apply our own velocity on drop g.AddControllerVelocityOnDrop = false; GrabArrow(arrow.GetComponent()); } // No arrow, lerp knock back to start if (GrabbedArrow == null) { resetStringPosition(); } if(arrowGrabber != null) { StringDistance = Vector3.Distance(transform.position, arrowGrabber.transform.position); } else { StringDistance = 0; } // Move arrow knock, align the arrow if (holdingArrow) { setKnockPosition(); alignArrow(); checkDrawSound(); checkBowHaptics(); // Let Go of Trigger, shoot arrow if (getGrabArrowInput() <= 0.2f) { ReleaseArrow(); } } alignBow(); } Transform getArrowRest() { if(bowGrabbable.GetPrimaryGrabber() != null && bowGrabbable.GetPrimaryGrabber().HandSide == ControllerHand.Right && ArrowRestLeftHanded != null) { return ArrowRestLeftHanded; } return ArrowRest; } bool canGrabArrowFromKnock() { // Setting override if(!CanGrabArrowFromKnock) { return false; } // Use opposite hand of what's holding the bow ControllerHand hand = bowGrabbable.GetControllerHand(bowGrabbable.GetPrimaryGrabber()) == ControllerHand.Left ? ControllerHand.Right : ControllerHand.Left; return CanGrabArrow && getTriggerInput(hand) > 0.75f && !holdingArrow; } float getGrabArrowInput() { // If we are holding an arrow, check the arrow details for input if (arrowGrabber != null && arrowGrabbable != null) { GrabButton grabButton = arrowGrabber.GetGrabButton(arrowGrabbable); // Grip Controls if (grabButton == GrabButton.Grip) { return getGripInput(arrowGrabber.HandSide); } // Trigger else if (grabButton == GrabButton.Trigger) { return getTriggerInput(arrowGrabber.HandSide); } } return 0; } float getGripInput(ControllerHand handSide) { if (handSide == ControllerHand.Left) { return input.LeftGrip; } else if (handSide == ControllerHand.Right) { return input.RightGrip; } return 0; } float getTriggerInput(ControllerHand handSide) { if (handSide == ControllerHand.Left) { return input.LeftTrigger; } else if (handSide == ControllerHand.Right) { return input.RightTrigger; } return 0; } void setKnockPosition() { // Set knock to hand if within range if(StringDistance <= MaxStringDistance) { ArrowKnock.position = arrowGrabber.transform.position; } else { ArrowKnock.localPosition = initialKnockPosition; ArrowKnock.LookAt(arrowGrabber.transform, ArrowKnock.forward); ArrowKnock.position += ArrowKnock.forward * (MaxStringDistance * 0.65f); } // Constrain position if (IgnoreXPosition) { ArrowKnock.localPosition = new Vector3(getArrowRest().localPosition.x, ArrowKnock.localPosition.y, ArrowKnock.localPosition.z); } if (IgnoreYPosition) { ArrowKnock.localPosition = new Vector3(ArrowKnock.localPosition.x, 0, ArrowKnock.localPosition.z); } // Z Position if(!AllowNegativeZ && ArrowKnock.localPosition.z > initialKnockPosition.z) { ArrowKnock.localPosition = new Vector3(ArrowKnock.localPosition.x, ArrowKnock.localPosition.y, initialKnockPosition.z); } } void checkDrawSound() { if(holdingArrow && !playedDrawSound && DrawPercent > 30f) { playBowDraw(); playedDrawSound = true; } } void updateDrawDistance() { _lastDrawPercent = DrawPercent; float knockDistance = Math.Abs(Vector3.Distance(ArrowKnock.localPosition, initialKnockPosition)); DrawPercent = (knockDistance / MaxStringDistance) * 100; if (PercentageUI != null) { PercentageUI.text = (int)DrawPercent + "%"; } } void checkBowHaptics() { // If we aren't pulling back then skip the check // Only apply haptics on pull back if (DrawPercent < _lastDrawPercent) { return; } // Don't apply haptics if we just applied them recently if(Time.time - _lastDrawHapticTime < 0.11) { return; } if(drawDefs == null) { return; } DrawDefinition d = drawDefs.FirstOrDefault(x => x.DrawPercentage <= DrawPercent && x.DrawPercentage != _lastDrawHaptic); if(d != null && arrowGrabber != null) { input.VibrateController(d.HapticFrequency, d.HapticAmplitude, 0.1f, arrowGrabber.HandSide); _lastDrawHaptic = d.DrawPercentage; _lastDrawHapticTime = Time.time; } } void resetStringPosition() { ArrowKnock.localPosition = Vector3.Lerp(ArrowKnock.localPosition, initialKnockPosition, Time.deltaTime * 100); } protected virtual void alignArrow() { GrabbedArrow.transform.parent = this.transform; GrabbedArrow.GetComponent().collisionDetectionMode = CollisionDetectionMode.Discrete; GrabbedArrow.GetComponent().isKinematic = true; GrabbedArrow.transform.position = ArrowKnock.transform.position; GrabbedArrow.transform.LookAt(getArrowRest()); } public Vector3 BowUp = Vector3.forward; public float AlignBowSpeed = 20f; protected virtual void alignBow() { // Bail early if (AlignBowToArrow == false || BowModel == null || grab == null || !grab.BeingHeld) { return; } // Reset Alignment if(grab != null && grab.BeingHeld) { if(holdingArrow) { if (GrabbedArrow != null) { BowModel.transform.rotation = GrabbedArrow.transform.rotation; } else { BowModel.transform.localRotation = Quaternion.Slerp(BowModel.transform.localRotation, Quaternion.identity, Time.deltaTime * AlignBowSpeed); } Vector3 eulers = BowModel.transform.localEulerAngles; eulers.z = 0; BowModel.transform.localEulerAngles = eulers; } else { BowModel.transform.localRotation = Quaternion.Slerp(BowModel.transform.localRotation, Quaternion.identity, Time.deltaTime * AlignBowSpeed); } } } public virtual void ResetBowAlignment() { if (BowModel != null) { BowModel.localEulerAngles = Vector3.zero; } } public void GrabArrow(Arrow arrow) { arrowGrabber = ClosestGrabber; // Signal the grabbable that we're being held GrabbedArrow = arrow.GetComponent(); GrabbedArrow.ShaftCollider.enabled = false; arrowGrabbable = arrow.GetComponent(); if (arrowGrabbable) { arrowGrabbable.GrabItem(arrowGrabber); arrowGrabber.HeldGrabbable = arrowGrabbable; arrowGrabbable.AddControllerVelocityOnDrop = false; } Collider playerCollder = GameObject.FindGameObjectWithTag("Player").GetComponentInChildren(); if(playerCollder) { Physics.IgnoreCollision(GrabbedArrow.ShaftCollider, playerCollder); } holdingArrow = true; } public void ReleaseArrow() { // Start sound immediately playBowRelease(); // No longer "holding" the arrow if (arrowGrabbable) { // Reset Arrow Grab to Grip arrowGrabbable.GrabButton = GrabButton.Grip; arrowGrabbable.DropItem(false, true); // We can apply velocity now arrowGrabbable.AddControllerVelocityOnDrop = true; } // Calculate shot force float shotForce = BowForce * StringDistance; GrabbedArrow.ShootArrow(GrabbedArrow.transform.forward * shotForce); // Make sure hands are showing if we hid them arrowGrabber.ResetHandGraphics(); resetArrowValues(); } public override void OnRelease() { ResetBowAlignment(); resetStringPosition(); } // Make sure all starting values are reset void resetArrowValues() { GrabbedArrow = null; arrowGrabbable = null; arrowGrabber = null; holdingArrow = false; playedDrawSound = false; } void playSoundInterval(float fromSeconds, float toSeconds, float volume) { if(audioSource) { if(audioSource.isPlaying) { audioSource.Stop(); } audioSource.pitch = Time.timeScale; audioSource.time = fromSeconds; audioSource.volume = volume; audioSource.Play(); audioSource.SetScheduledEndTime(AudioSettings.dspTime + (toSeconds - fromSeconds)); } } void playBowDraw() { playSoundInterval(0, 1.66f, 0.4f); } void playBowRelease() { playSoundInterval(1.67f, 2.2f, 0.3f); } } /// /// A list of how and when to play a haptic according to DrawPercentage /// public class DrawDefinition { public float DrawPercentage { get; set; } public float HapticAmplitude { get; set; } public float HapticFrequency { get; set; } } }