IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
Arrow.cs
1 //======= Copyright (c) Valve Corporation, All rights reserved. ===============
2 //
3 // Purpose: The arrow for the longbow
4 //
5 //=============================================================================
6 
7 using UnityEngine;
8 using System.Collections;
9 
10 namespace Valve.VR.InteractionSystem
11 {
12  //-------------------------------------------------------------------------
13  public class Arrow : MonoBehaviour
14  {
15  public ParticleSystem glintParticle;
16  public Rigidbody arrowHeadRB;
17  public Rigidbody shaftRB;
18 
19  public PhysicMaterial targetPhysMaterial;
20 
21  private Vector3 prevPosition;
22  private Quaternion prevRotation;
23  private Vector3 prevVelocity;
24  private Vector3 prevHeadPosition;
25 
26  public SoundPlayOneshot fireReleaseSound;
27  public SoundPlayOneshot airReleaseSound;
28  public SoundPlayOneshot hitTargetSound;
29 
30  public PlaySound hitGroundSound;
31 
32  private bool inFlight;
33  private bool released;
34  private bool hasSpreadFire = false;
35 
36  private int travelledFrames = 0;
37 
38  private GameObject scaleParentObject = null;
39 
40 
41  //-------------------------------------------------
42  void Start()
43  {
44  Physics.IgnoreCollision( shaftRB.GetComponent<Collider>(), Player.instance.headCollider );
45  }
46 
47 
48  //-------------------------------------------------
49  void FixedUpdate()
50  {
51  if ( released && inFlight )
52  {
53  prevPosition = transform.position;
54  prevRotation = transform.rotation;
55  prevVelocity = GetComponent<Rigidbody>().velocity;
56  prevHeadPosition = arrowHeadRB.transform.position;
57  travelledFrames++;
58  }
59  }
60 
61 
62  //-------------------------------------------------
63  public void ArrowReleased( float inputVelocity )
64  {
65  inFlight = true;
66  released = true;
67 
68  airReleaseSound.Play();
69 
70  if ( glintParticle != null )
71  {
72  glintParticle.Play();
73  }
74 
75  if ( gameObject.GetComponentInChildren<FireSource>().isBurning )
76  {
77  fireReleaseSound.Play();
78  }
79 
80  // Check if arrow is shot inside or too close to an object
81  RaycastHit[] hits = Physics.SphereCastAll( transform.position, 0.01f, transform.forward, 0.80f, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore );
82  foreach ( RaycastHit hit in hits )
83  {
84  if ( hit.collider.gameObject != gameObject && hit.collider.gameObject != arrowHeadRB.gameObject && hit.collider != Player.instance.headCollider )
85  {
86  Destroy( gameObject );
87  return;
88  }
89  }
90 
91  travelledFrames = 0;
92  prevPosition = transform.position;
93  prevRotation = transform.rotation;
94  prevHeadPosition = arrowHeadRB.transform.position;
95  prevVelocity = GetComponent<Rigidbody>().velocity;
96 
97  Destroy( gameObject, 30 );
98  }
99 
100 
101  //-------------------------------------------------
102  void OnCollisionEnter( Collision collision )
103  {
104  if ( inFlight )
105  {
106  Rigidbody rb = GetComponent<Rigidbody>();
107  float rbSpeed = rb.velocity.sqrMagnitude;
108  bool canStick = ( targetPhysMaterial != null && collision.collider.sharedMaterial == targetPhysMaterial && rbSpeed > 0.2f );
109  bool hitBalloon = collision.collider.gameObject.GetComponent<Balloon>() != null;
110 
111  if ( travelledFrames < 2 && !canStick )
112  {
113  // Reset transform but halve your velocity
114  transform.position = prevPosition - prevVelocity * Time.deltaTime;
115  transform.rotation = prevRotation;
116 
117  Vector3 reflfectDir = Vector3.Reflect( arrowHeadRB.velocity, collision.contacts[0].normal );
118  arrowHeadRB.velocity = reflfectDir * 0.25f;
119  shaftRB.velocity = reflfectDir * 0.25f;
120 
121  travelledFrames = 0;
122  return;
123  }
124 
125  if ( glintParticle != null )
126  {
127  glintParticle.Stop( true );
128  }
129 
130  // Only play hit sounds if we're moving quickly
131  if ( rbSpeed > 0.1f )
132  {
133  hitGroundSound.Play();
134  }
135 
136  FireSource arrowFire = gameObject.GetComponentInChildren<FireSource>();
137  FireSource fireSourceOnTarget = collision.collider.GetComponentInParent<FireSource>();
138 
139  if ( arrowFire != null && arrowFire.isBurning && ( fireSourceOnTarget != null ) )
140  {
141  if ( !hasSpreadFire )
142  {
143  collision.collider.gameObject.SendMessageUpwards( "FireExposure", gameObject, SendMessageOptions.DontRequireReceiver );
144  hasSpreadFire = true;
145  }
146  }
147  else
148  {
149  // Only count collisions with good speed so that arrows on the ground can't deal damage
150  // always pop balloons
151  if ( rbSpeed > 0.1f || hitBalloon )
152  {
153  collision.collider.gameObject.SendMessageUpwards( "ApplyDamage", SendMessageOptions.DontRequireReceiver );
154  gameObject.SendMessage( "HasAppliedDamage", SendMessageOptions.DontRequireReceiver );
155  }
156  }
157 
158  if ( hitBalloon )
159  {
160  // Revert my physics properties cause I don't want balloons to influence my travel
161  transform.position = prevPosition;
162  transform.rotation = prevRotation;
163  arrowHeadRB.velocity = prevVelocity;
164  Physics.IgnoreCollision( arrowHeadRB.GetComponent<Collider>(), collision.collider );
165  Physics.IgnoreCollision( shaftRB.GetComponent<Collider>(), collision.collider );
166  }
167 
168  if ( canStick )
169  {
170  StickInTarget( collision, travelledFrames < 2 );
171  }
172 
173  // Player Collision Check (self hit)
174  if ( Player.instance && collision.collider == Player.instance.headCollider )
175  {
176  Player.instance.PlayerShotSelf();
177  }
178  }
179  }
180 
181 
182  //-------------------------------------------------
183  private void StickInTarget( Collision collision, bool bSkipRayCast )
184  {
185  Vector3 prevForward = prevRotation * Vector3.forward;
186 
187  // Only stick in target if the collider is front of the arrow head
188  if ( !bSkipRayCast )
189  {
190  RaycastHit[] hitInfo;
191  hitInfo = Physics.RaycastAll( prevHeadPosition - prevVelocity * Time.deltaTime, prevForward, prevVelocity.magnitude * Time.deltaTime * 2.0f );
192  bool properHit = false;
193  for ( int i = 0; i < hitInfo.Length; ++i )
194  {
195  RaycastHit hit = hitInfo[i];
196 
197  if ( hit.collider == collision.collider )
198  {
199  properHit = true;
200  break;
201  }
202  }
203 
204  if ( !properHit )
205  {
206  return;
207  }
208  }
209 
210  Destroy( glintParticle );
211 
212  inFlight = false;
213 
214  shaftRB.velocity = Vector3.zero;
215  shaftRB.angularVelocity = Vector3.zero;
216  shaftRB.isKinematic = true;
217  shaftRB.useGravity = false;
218  shaftRB.transform.GetComponent<BoxCollider>().enabled = false;
219 
220  arrowHeadRB.velocity = Vector3.zero;
221  arrowHeadRB.angularVelocity = Vector3.zero;
222  arrowHeadRB.isKinematic = true;
223  arrowHeadRB.useGravity = false;
224  arrowHeadRB.transform.GetComponent<BoxCollider>().enabled = false;
225 
226  hitTargetSound.Play();
227 
228 
229  // If the hit item has a parent, dock an empty object to that
230  // this fixes an issue with scaling hierarchy. I suspect this is not sustainable for a large object / scaling hierarchy.
231  scaleParentObject = new GameObject( "Arrow Scale Parent" );
232  Transform parentTransform = collision.collider.transform;
233 
234  // Don't do this for weebles because of how it has a fixed joint
235  ExplosionWobble wobble = collision.collider.gameObject.GetComponent<ExplosionWobble>();
236  if ( !wobble )
237  {
238  if ( parentTransform.parent )
239  {
240  parentTransform = parentTransform.parent;
241  }
242  }
243 
244  scaleParentObject.transform.parent = parentTransform;
245 
246  // Move the arrow to the place on the target collider we were expecting to hit prior to the impact itself knocking it around
247  transform.parent = scaleParentObject.transform;
248  transform.rotation = prevRotation;
249  transform.position = prevPosition;
250  transform.position = collision.contacts[0].point - transform.forward * ( 0.75f - ( Util.RemapNumberClamped( prevVelocity.magnitude, 0f, 10f, 0.0f, 0.1f ) + Random.Range( 0.0f, 0.05f ) ) );
251  }
252 
253 
254  //-------------------------------------------------
255  void OnDestroy()
256  {
257  if ( scaleParentObject != null )
258  {
259  Destroy( scaleParentObject );
260  }
261  }
262  }
263 }