IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
Longbow.cs
1 //======= Copyright (c) Valve Corporation, All rights reserved. ===============
2 //
3 // Purpose: The bow
4 //
5 //=============================================================================
6 
7 using UnityEngine;
8 using System.Collections;
9 using System.Collections.Generic;
10 
11 namespace Valve.VR.InteractionSystem
12 {
13  //-------------------------------------------------------------------------
14  [RequireComponent( typeof( Interactable ) )]
15  public class Longbow : MonoBehaviour
16  {
17  public enum Handedness { Left, Right };
18 
19  public Handedness currentHandGuess = Handedness.Left;
20  private float timeOfPossibleHandSwitch = 0f;
21  private float timeBeforeConfirmingHandSwitch = 1.5f;
22  private bool possibleHandSwitch = false;
23 
24  public Transform pivotTransform;
25  public Transform handleTransform;
26 
27  private Hand hand;
28  private ArrowHand arrowHand;
29 
30  public Transform nockTransform;
31  public Transform nockRestTransform;
32 
33  public bool autoSpawnArrowHand = true;
34  public ItemPackage arrowHandItemPackage;
35  public GameObject arrowHandPrefab;
36 
37  public bool nocked;
38  public bool pulled;
39 
40  private const float minPull = 0.05f;
41  private const float maxPull = 0.5f;
42  private float nockDistanceTravelled = 0f;
43  private float hapticDistanceThreshold = 0.01f;
44  private float lastTickDistance;
45  private const float bowPullPulseStrengthLow = 100;
46  private const float bowPullPulseStrengthHigh = 500;
47  private Vector3 bowLeftVector;
48 
49  public float arrowMinVelocity = 3f;
50  public float arrowMaxVelocity = 30f;
51  private float arrowVelocity = 30f;
52 
53  private float minStrainTickTime = 0.1f;
54  private float maxStrainTickTime = 0.5f;
55  private float nextStrainTick = 0;
56 
57  private bool lerpBackToZeroRotation;
58  private float lerpStartTime;
59  private float lerpDuration = 0.15f;
60  private Quaternion lerpStartRotation;
61 
62  private float nockLerpStartTime;
63 
64  private Quaternion nockLerpStartRotation;
65 
66  public float drawOffset = 0.06f;
67 
68  public LinearMapping bowDrawLinearMapping;
69 
70  private bool deferNewPoses = false;
71  private Vector3 lateUpdatePos;
72  private Quaternion lateUpdateRot;
73 
74  public SoundBowClick drawSound;
75  private float drawTension;
76  public SoundPlayOneshot arrowSlideSound;
77  public SoundPlayOneshot releaseSound;
78  public SoundPlayOneshot nockSound;
79 
80  SteamVR_Events.Action newPosesAppliedAction;
81 
82 
83  //-------------------------------------------------
84  private void OnAttachedToHand( Hand attachedHand )
85  {
86  hand = attachedHand;
87  }
88 
89 
90  //-------------------------------------------------
91  void Awake()
92  {
93  newPosesAppliedAction = SteamVR_Events.NewPosesAppliedAction( OnNewPosesApplied );
94  }
95 
96 
97  //-------------------------------------------------
98  void OnEnable()
99  {
100  newPosesAppliedAction.enabled = true;
101  }
102 
103 
104  //-------------------------------------------------
105  void OnDisable()
106  {
107  newPosesAppliedAction.enabled = false;
108  }
109 
110 
111  //-------------------------------------------------
112  void LateUpdate()
113  {
114  if ( deferNewPoses )
115  {
116  lateUpdatePos = transform.position;
117  lateUpdateRot = transform.rotation;
118  }
119  }
120 
121 
122  //-------------------------------------------------
123  private void OnNewPosesApplied()
124  {
125  if ( deferNewPoses )
126  {
127  // Set longbow object back to previous pose position to avoid jitter
128  transform.position = lateUpdatePos;
129  transform.rotation = lateUpdateRot;
130 
131  deferNewPoses = false;
132  }
133  }
134 
135 
136  //-------------------------------------------------
137  private void HandAttachedUpdate( Hand hand )
138  {
139  // Reset transform since we cheated it right after getting poses on previous frame
140  transform.localPosition = Vector3.zero;
141  transform.localRotation = Quaternion.identity;
142 
143  // Update handedness guess
144  EvaluateHandedness();
145 
146  if ( nocked )
147  {
148  deferNewPoses = true;
149 
150  Vector3 nockToarrowHand = ( arrowHand.arrowNockTransform.parent.position - nockRestTransform.position ); // Vector from bow nock transform to arrowhand nock transform - used to align bow when drawing
151 
152  // Align bow
153  // Time lerp value used for ramping into drawn bow orientation
154  float lerp = Util.RemapNumberClamped( Time.time, nockLerpStartTime, ( nockLerpStartTime + lerpDuration ), 0f, 1f );
155 
156  float pullLerp = Util.RemapNumberClamped( nockToarrowHand.magnitude, minPull, maxPull, 0f, 1f ); // Normalized current state of bow draw 0 - 1
157 
158  Vector3 arrowNockTransformToHeadset = ( ( Player.instance.hmdTransform.position + ( Vector3.down * 0.05f ) ) - arrowHand.arrowNockTransform.parent.position ).normalized;
159  Vector3 arrowHandPosition = ( arrowHand.arrowNockTransform.parent.position + ( ( arrowNockTransformToHeadset * drawOffset ) * pullLerp ) ); // Use this line to lerp arrowHand nock position
160  //Vector3 arrowHandPosition = arrowHand.arrowNockTransform.position; // Use this line if we don't want to lerp arrowHand nock position
161 
162  Vector3 pivotToString = ( arrowHandPosition - pivotTransform.position ).normalized;
163  Vector3 pivotToLowerHandle = ( handleTransform.position - pivotTransform.position ).normalized;
164  bowLeftVector = -Vector3.Cross( pivotToLowerHandle, pivotToString );
165  pivotTransform.rotation = Quaternion.Lerp( nockLerpStartRotation, Quaternion.LookRotation( pivotToString, bowLeftVector ), lerp );
166 
167  // Move nock position
168  if ( Vector3.Dot( nockToarrowHand, -nockTransform.forward ) > 0 )
169  {
170  float distanceToarrowHand = nockToarrowHand.magnitude * lerp;
171 
172  nockTransform.localPosition = new Vector3( 0f, 0f, Mathf.Clamp( -distanceToarrowHand, -maxPull, 0f ) );
173 
174  nockDistanceTravelled = -nockTransform.localPosition.z;
175 
176  arrowVelocity = Util.RemapNumber( nockDistanceTravelled, minPull, maxPull, arrowMinVelocity, arrowMaxVelocity );
177 
178  drawTension = Util.RemapNumberClamped( nockDistanceTravelled, 0, maxPull, 0f, 1f );
179 
180  this.bowDrawLinearMapping.value = drawTension; // Send drawTension value to LinearMapping script, which drives the bow draw animation
181 
182  if ( nockDistanceTravelled > minPull )
183  {
184  pulled = true;
185  }
186  else
187  {
188  pulled = false;
189  }
190 
191  if ( ( nockDistanceTravelled > ( lastTickDistance + hapticDistanceThreshold ) ) || nockDistanceTravelled < ( lastTickDistance - hapticDistanceThreshold ) )
192  {
193  ushort hapticStrength = (ushort)Util.RemapNumber( nockDistanceTravelled, 0, maxPull, bowPullPulseStrengthLow, bowPullPulseStrengthHigh );
194  hand.controller.TriggerHapticPulse( hapticStrength );
195  hand.otherHand.controller.TriggerHapticPulse( hapticStrength );
196 
197  drawSound.PlayBowTensionClicks( drawTension );
198 
199  lastTickDistance = nockDistanceTravelled;
200  }
201 
202  if ( nockDistanceTravelled >= maxPull )
203  {
204  if ( Time.time > nextStrainTick )
205  {
206  hand.controller.TriggerHapticPulse( 400 );
207  hand.otherHand.controller.TriggerHapticPulse( 400 );
208 
209  drawSound.PlayBowTensionClicks( drawTension );
210 
211  nextStrainTick = Time.time + Random.Range( minStrainTickTime, maxStrainTickTime );
212  }
213  }
214  }
215  else
216  {
217  nockTransform.localPosition = new Vector3( 0f, 0f, 0f );
218 
219  this.bowDrawLinearMapping.value = 0f;
220  }
221  }
222  else
223  {
224  if ( lerpBackToZeroRotation )
225  {
226  float lerp = Util.RemapNumber( Time.time, lerpStartTime, lerpStartTime + lerpDuration, 0, 1 );
227 
228  pivotTransform.localRotation = Quaternion.Lerp( lerpStartRotation, Quaternion.identity, lerp );
229 
230  if ( lerp >= 1 )
231  {
232  lerpBackToZeroRotation = false;
233  }
234  }
235  }
236  }
237 
238 
239  //-------------------------------------------------
240  public void ArrowReleased()
241  {
242  nocked = false;
243  hand.HoverUnlock( GetComponent<Interactable>() );
244  hand.otherHand.HoverUnlock( arrowHand.GetComponent<Interactable>() );
245 
246  if ( releaseSound != null )
247  {
248  releaseSound.Play();
249  }
250 
251  this.StartCoroutine( this.ResetDrawAnim() );
252  }
253 
254 
255  //-------------------------------------------------
256  private IEnumerator ResetDrawAnim()
257  {
258  float startTime = Time.time;
259  float startLerp = drawTension;
260 
261  while ( Time.time < ( startTime + 0.02f ) )
262  {
263  float lerp = Util.RemapNumberClamped( Time.time, startTime, startTime + 0.02f, startLerp, 0f );
264  this.bowDrawLinearMapping.value = lerp;
265  yield return null;
266  }
267 
268  this.bowDrawLinearMapping.value = 0;
269 
270  yield break;
271  }
272 
273 
274  //-------------------------------------------------
275  public float GetArrowVelocity()
276  {
277  return arrowVelocity;
278  }
279 
280 
281  //-------------------------------------------------
282  public void StartRotationLerp()
283  {
284  lerpStartTime = Time.time;
285  lerpBackToZeroRotation = true;
286  lerpStartRotation = pivotTransform.localRotation;
287 
288  Util.ResetTransform( nockTransform );
289  }
290 
291 
292  //-------------------------------------------------
293  public void StartNock( ArrowHand currentArrowHand )
294  {
295  arrowHand = currentArrowHand;
296  hand.HoverLock( GetComponent<Interactable>() );
297  nocked = true;
298  nockLerpStartTime = Time.time;
299  nockLerpStartRotation = pivotTransform.rotation;
300 
301  // Sound of arrow sliding on nock as it's being pulled back
302  arrowSlideSound.Play();
303 
304  // Decide which hand we're drawing with and lerp to the correct side
305  DoHandednessCheck();
306  }
307 
308 
309  //-------------------------------------------------
310  private void EvaluateHandedness()
311  {
312  Hand.HandType handType = hand.GuessCurrentHandType();
313 
314  if ( handType == Hand.HandType.Left )// Bow hand is further left than arrow hand.
315  {
316  // We were considering a switch, but the current controller orientation matches our currently assigned handedness, so no longer consider a switch
317  if ( possibleHandSwitch && currentHandGuess == Handedness.Left )
318  {
319  possibleHandSwitch = false;
320  }
321 
322  // If we previously thought the bow was right-handed, and were not already considering switching, start considering a switch
323  if ( !possibleHandSwitch && currentHandGuess == Handedness.Right )
324  {
325  possibleHandSwitch = true;
326  timeOfPossibleHandSwitch = Time.time;
327  }
328 
329  // If we are considering a handedness switch, and it's been this way long enough, switch
330  if ( possibleHandSwitch && Time.time > ( timeOfPossibleHandSwitch + timeBeforeConfirmingHandSwitch ) )
331  {
332  currentHandGuess = Handedness.Left;
333  possibleHandSwitch = false;
334  }
335  }
336  else // Bow hand is further right than arrow hand
337  {
338  // We were considering a switch, but the current controller orientation matches our currently assigned handedness, so no longer consider a switch
339  if ( possibleHandSwitch && currentHandGuess == Handedness.Right )
340  {
341  possibleHandSwitch = false;
342  }
343 
344  // If we previously thought the bow was right-handed, and were not already considering switching, start considering a switch
345  if ( !possibleHandSwitch && currentHandGuess == Handedness.Left )
346  {
347  possibleHandSwitch = true;
348  timeOfPossibleHandSwitch = Time.time;
349  }
350 
351  // If we are considering a handedness switch, and it's been this way long enough, switch
352  if ( possibleHandSwitch && Time.time > ( timeOfPossibleHandSwitch + timeBeforeConfirmingHandSwitch ) )
353  {
354  currentHandGuess = Handedness.Right;
355  possibleHandSwitch = false;
356  }
357  }
358  }
359 
360 
361  //-------------------------------------------------
362  private void DoHandednessCheck()
363  {
364  // Based on our current best guess about hand, switch bow orientation and arrow lerp direction
365  if ( currentHandGuess == Handedness.Left )
366  {
367  pivotTransform.localScale = new Vector3( 1f, 1f, 1f );
368  }
369  else
370  {
371  pivotTransform.localScale = new Vector3( 1f, -1f, 1f );
372  }
373  }
374 
375 
376  //-------------------------------------------------
377  public void ArrowInPosition()
378  {
379  DoHandednessCheck();
380 
381  if ( nockSound != null )
382  {
383  nockSound.Play();
384  }
385  }
386 
387 
388  //-------------------------------------------------
389  public void ReleaseNock()
390  {
391  // ArrowHand tells us to do this when we release the buttons when bow is nocked but not drawn far enough
392  nocked = false;
393  hand.HoverUnlock( GetComponent<Interactable>() );
394  this.StartCoroutine( this.ResetDrawAnim() );
395  }
396 
397 
398  //-------------------------------------------------
399  private void ShutDown()
400  {
401  if ( hand != null && hand.otherHand.currentAttachedObject != null )
402  {
403  if ( hand.otherHand.currentAttachedObject.GetComponent<ItemPackageReference>() != null )
404  {
405  if ( hand.otherHand.currentAttachedObject.GetComponent<ItemPackageReference>().itemPackage == arrowHandItemPackage )
406  {
407  hand.otherHand.DetachObject( hand.otherHand.currentAttachedObject );
408  }
409  }
410  }
411  }
412 
413 
414  //-------------------------------------------------
415  private void OnHandFocusLost( Hand hand )
416  {
417  gameObject.SetActive( false );
418  }
419 
420 
421  //-------------------------------------------------
422  private void OnHandFocusAcquired( Hand hand )
423  {
424  gameObject.SetActive( true );
425  OnAttachedToHand( hand );
426  }
427 
428 
429  //-------------------------------------------------
430  private void OnDetachedFromHand( Hand hand )
431  {
432  Destroy( gameObject );
433  }
434 
435 
436  //-------------------------------------------------
437  void OnDestroy()
438  {
439  ShutDown();
440  }
441  }
442 }