//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: The arrow for the longbow // //============================================================================= using UnityEngine; using System.Collections; namespace Valve.VR.InteractionSystem { //------------------------------------------------------------------------- public class Arrow : MonoBehaviour { public ParticleSystem glintParticle; public Rigidbody arrowHeadRB; public Rigidbody shaftRB; public PhysicMaterial targetPhysMaterial; private Vector3 prevPosition; private Quaternion prevRotation; private Vector3 prevVelocity; private Vector3 prevHeadPosition; public SoundPlayOneshot fireReleaseSound; public SoundPlayOneshot airReleaseSound; public SoundPlayOneshot hitTargetSound; public PlaySound hitGroundSound; private bool inFlight; private bool released; private bool hasSpreadFire = false; private int travelledFrames = 0; private GameObject scaleParentObject = null; //------------------------------------------------- void Start() { Physics.IgnoreCollision( shaftRB.GetComponent(), Player.instance.headCollider ); } //------------------------------------------------- void FixedUpdate() { if ( released && inFlight ) { prevPosition = transform.position; prevRotation = transform.rotation; prevVelocity = GetComponent().velocity; prevHeadPosition = arrowHeadRB.transform.position; travelledFrames++; } } //------------------------------------------------- public void ArrowReleased( float inputVelocity ) { inFlight = true; released = true; airReleaseSound.Play(); if ( glintParticle != null ) { glintParticle.Play(); } if ( gameObject.GetComponentInChildren().isBurning ) { fireReleaseSound.Play(); } // Check if arrow is shot inside or too close to an object RaycastHit[] hits = Physics.SphereCastAll( transform.position, 0.01f, transform.forward, 0.80f, Physics.DefaultRaycastLayers, QueryTriggerInteraction.Ignore ); foreach ( RaycastHit hit in hits ) { if ( hit.collider.gameObject != gameObject && hit.collider.gameObject != arrowHeadRB.gameObject && hit.collider != Player.instance.headCollider ) { Destroy( gameObject ); return; } } travelledFrames = 0; prevPosition = transform.position; prevRotation = transform.rotation; prevHeadPosition = arrowHeadRB.transform.position; prevVelocity = GetComponent().velocity; SetCollisionMode(CollisionDetectionMode.ContinuousDynamic); Destroy( gameObject, 30 ); } protected void SetCollisionMode(CollisionDetectionMode newMode, bool force = false) { Rigidbody[] rigidBodies = this.GetComponentsInChildren(); for (int rigidBodyIndex = 0; rigidBodyIndex < rigidBodies.Length; rigidBodyIndex++) { if (rigidBodies[rigidBodyIndex].isKinematic == false || force) rigidBodies[rigidBodyIndex].collisionDetectionMode = newMode; } } //------------------------------------------------- void OnCollisionEnter( Collision collision ) { if ( inFlight ) { Rigidbody rb = GetComponent(); float rbSpeed = rb.velocity.sqrMagnitude; bool canStick = ( targetPhysMaterial != null && collision.collider.sharedMaterial == targetPhysMaterial && rbSpeed > 0.2f ); bool hitBalloon = collision.collider.gameObject.GetComponent() != null; if ( travelledFrames < 2 && !canStick ) { // Reset transform but halve your velocity transform.position = prevPosition - prevVelocity * Time.deltaTime; transform.rotation = prevRotation; Vector3 reflfectDir = Vector3.Reflect( arrowHeadRB.velocity, collision.contacts[0].normal ); arrowHeadRB.velocity = reflfectDir * 0.25f; shaftRB.velocity = reflfectDir * 0.25f; travelledFrames = 0; return; } if ( glintParticle != null ) { glintParticle.Stop( true ); } // Only play hit sounds if we're moving quickly if ( rbSpeed > 0.1f ) { hitGroundSound.Play(); } FireSource arrowFire = gameObject.GetComponentInChildren(); FireSource fireSourceOnTarget = collision.collider.GetComponentInParent(); if ( arrowFire != null && arrowFire.isBurning && ( fireSourceOnTarget != null ) ) { if ( !hasSpreadFire ) { collision.collider.gameObject.SendMessageUpwards( "FireExposure", gameObject, SendMessageOptions.DontRequireReceiver ); hasSpreadFire = true; } } else { // Only count collisions with good speed so that arrows on the ground can't deal damage // always pop balloons if ( rbSpeed > 0.1f || hitBalloon ) { collision.collider.gameObject.SendMessageUpwards( "ApplyDamage", SendMessageOptions.DontRequireReceiver ); gameObject.SendMessage( "HasAppliedDamage", SendMessageOptions.DontRequireReceiver ); } } if ( hitBalloon ) { // Revert my physics properties cause I don't want balloons to influence my travel transform.position = prevPosition; transform.rotation = prevRotation; arrowHeadRB.velocity = prevVelocity; Physics.IgnoreCollision( arrowHeadRB.GetComponent(), collision.collider ); Physics.IgnoreCollision( shaftRB.GetComponent(), collision.collider ); } if ( canStick ) { StickInTarget( collision, travelledFrames < 2 ); } // Player Collision Check (self hit) if ( Player.instance && collision.collider == Player.instance.headCollider ) { Player.instance.PlayerShotSelf(); } } } //------------------------------------------------- private void StickInTarget( Collision collision, bool bSkipRayCast ) { Vector3 prevForward = prevRotation * Vector3.forward; // Only stick in target if the collider is front of the arrow head if ( !bSkipRayCast ) { RaycastHit[] hitInfo; hitInfo = Physics.RaycastAll( prevHeadPosition - prevVelocity * Time.deltaTime, prevForward, prevVelocity.magnitude * Time.deltaTime * 2.0f ); bool properHit = false; for ( int i = 0; i < hitInfo.Length; ++i ) { RaycastHit hit = hitInfo[i]; if ( hit.collider == collision.collider ) { properHit = true; break; } } if ( !properHit ) { return; } } Destroy( glintParticle ); inFlight = false; SetCollisionMode(CollisionDetectionMode.Discrete, true); shaftRB.velocity = Vector3.zero; shaftRB.angularVelocity = Vector3.zero; shaftRB.isKinematic = true; shaftRB.useGravity = false; shaftRB.transform.GetComponent().enabled = false; arrowHeadRB.velocity = Vector3.zero; arrowHeadRB.angularVelocity = Vector3.zero; arrowHeadRB.isKinematic = true; arrowHeadRB.useGravity = false; arrowHeadRB.transform.GetComponent().enabled = false; hitTargetSound.Play(); // If the hit item has a parent, dock an empty object to that // this fixes an issue with scaling hierarchy. I suspect this is not sustainable for a large object / scaling hierarchy. scaleParentObject = new GameObject( "Arrow Scale Parent" ); Transform parentTransform = collision.collider.transform; // Don't do this for weebles because of how it has a fixed joint ExplosionWobble wobble = collision.collider.gameObject.GetComponent(); if ( !wobble ) { if ( parentTransform.parent ) { parentTransform = parentTransform.parent; } } scaleParentObject.transform.parent = parentTransform; // Move the arrow to the place on the target collider we were expecting to hit prior to the impact itself knocking it around transform.parent = scaleParentObject.transform; transform.rotation = prevRotation; transform.position = prevPosition; 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 ) ) ); } //------------------------------------------------- void OnDestroy() { if ( scaleParentObject != null ) { Destroy( scaleParentObject ); } } } }