ShaftedProjectile.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. using System;
  2. using System.Collections;
  3. using UnityEngine;
  4. using Random = UnityEngine.Random;
  5. using UnityEngine.Events;
  6. #if UNITY_EDITOR
  7. using UnityEditor;
  8. #endif
  9. namespace HQFPSWeapons
  10. {
  11. [RequireComponent(typeof(Collider))]
  12. [RequireComponent(typeof(Rigidbody))]
  13. public class ShaftedProjectile : Projectile
  14. {
  15. public struct ImpactInfo
  16. {
  17. public Hitbox Hitbox;
  18. }
  19. public Message<ImpactInfo> Impact = new Message<ImpactInfo>();
  20. public Message Launched = new Message();
  21. [BHeader("General", true)]
  22. [SerializeField]
  23. private LayerMask m_Mask = new LayerMask();
  24. [SerializeField]
  25. private float m_MaxDistance = 2f;
  26. [SerializeField]
  27. private TrailRenderer m_Trail = null;
  28. [SerializeField]
  29. private bool m_AllowSticking = false;
  30. [BHeader("Damage")]
  31. [SerializeField]
  32. [Range(0f, 200f)]
  33. private float m_MaxDamageSpeed = 50f;
  34. [SerializeField]
  35. [Range(0f, 200f)]
  36. private float m_MaxDamage = 100f;
  37. [SerializeField]
  38. private AnimationCurve m_DamageCurve = new AnimationCurve(
  39. new Keyframe(0f, 1f),
  40. new Keyframe(0.8f, 0.5f),
  41. new Keyframe(1f, 0f));
  42. [SerializeField]
  43. private float m_ImpactForce = 15f;
  44. [BHeader("Penetration")]
  45. [SerializeField]
  46. private float m_PenetrationOffset = 0.2f;
  47. [SerializeField]
  48. private Vector2 m_RandomRotation = Vector2.zero;
  49. [SerializeField]
  50. private bool m_PlayTwangAnimation = false;
  51. [SerializeField]
  52. private UnityEvent m_OnImpact = null;
  53. [SerializeField]
  54. [ShowIf("m_PlayTwangAnimation", true)]
  55. private TwangSettings m_TwangSettings = null;
  56. [BHeader("Audio")]
  57. [SerializeField]
  58. private AudioSource m_AudioSource = null;
  59. [SerializeField]
  60. private SurfaceEffects m_PenetrationEffect = new SurfaceEffects();
  61. [BHeader("Pickup")]
  62. [SerializeField]
  63. private bool m_EnablePickupOnImpact = true;
  64. [SerializeField]
  65. private ItemPickup m_Pickup = null;
  66. private LivingEntity m_Launcher;
  67. private Collider m_Collider;
  68. private Rigidbody m_Rigidbody;
  69. private bool m_Done;
  70. private bool m_Launched;
  71. private Transform m_Pivot;
  72. public override void Launch(LivingEntity launcher)
  73. {
  74. if (m_Launcher != null)
  75. {
  76. Debug.LogWarningFormat(this, "Already launched this projectile!", name);
  77. return;
  78. }
  79. m_Launcher = launcher;
  80. m_Launched = true;
  81. OnLaunched();
  82. Launched.Send();
  83. }
  84. public void CheckForSurfaces(Vector3 position, Vector3 direction)
  85. {
  86. RaycastHit hitInfo;
  87. Ray ray = new Ray(position, direction);
  88. if (Physics.Raycast(ray, out hitInfo, m_MaxDistance, m_Mask, QueryTriggerInteraction.Ignore))
  89. {
  90. SurfaceManager.SpawnEffect(hitInfo, SurfaceEffects.Stab, 1f);
  91. float currentSpeed = m_Rigidbody.velocity.magnitude;
  92. float impulse = m_ImpactForce;
  93. var damageable = hitInfo.collider.GetComponent<IDamageable>();
  94. // If the object is damageable...
  95. if (damageable != null)
  96. {
  97. float damageMod = m_DamageCurve.Evaluate(1f - currentSpeed / m_MaxDamageSpeed);
  98. float damage = m_MaxDamage * damageMod;
  99. var damageData = new HealthEventData(-damage, DamageType.Stab, hitInfo.point, ray.direction, impulse, hitInfo.normal, m_Launcher);
  100. damageable.TakeDamage(damageData);
  101. }
  102. if (hitInfo.rigidbody != null)
  103. {
  104. // If the object is a rigidbody, apply an impact force
  105. hitInfo.rigidbody.AddForceAtPosition(transform.forward * impulse, hitInfo.point, ForceMode.Impulse);
  106. }
  107. SurfaceManager.SpawnEffect(hitInfo, m_PenetrationEffect, 1f);
  108. // Stick the projectile in the object
  109. transform.position = hitInfo.point + transform.forward * m_PenetrationOffset;
  110. var hitbox = hitInfo.collider.GetComponent<Hitbox>();
  111. m_OnImpact.Invoke();
  112. Impact.Send(new ImpactInfo() { Hitbox = hitbox });
  113. m_Collider.enabled = true;
  114. if (m_AllowSticking && m_Pickup != null)
  115. {
  116. transform.SetParent(hitInfo.transform);
  117. OnSurfacePenetrated();
  118. Physics.IgnoreCollision(m_Collider, hitInfo.collider);
  119. }
  120. m_Done = true;
  121. }
  122. }
  123. public void OnLaunched()
  124. {
  125. if (m_Pickup != null)
  126. m_Pickup.EnablePickup(false);
  127. m_Collider.enabled = false;
  128. if(m_Trail != null)
  129. m_Trail.enabled = true;
  130. }
  131. public void OnSurfacePenetrated()
  132. {
  133. m_Rigidbody.isKinematic = true;
  134. if (m_PlayTwangAnimation)
  135. StartCoroutine(C_DoTwang());
  136. if (m_Pickup != null && m_EnablePickupOnImpact)
  137. m_Pickup.EnablePickup(true);
  138. }
  139. private void Awake()
  140. {
  141. m_Collider = GetComponent<Collider>();
  142. m_Rigidbody = GetComponent<Rigidbody>();
  143. if(m_Trail != null)
  144. m_Trail.enabled = false;
  145. }
  146. private void FixedUpdate()
  147. {
  148. if (m_Launched && !m_Done)
  149. CheckForSurfaces(transform.position, transform.forward);
  150. }
  151. #if UNITY_EDITOR
  152. private void OnDrawGizmosSelected()
  153. {
  154. if (m_PlayTwangAnimation)
  155. {
  156. Vector3 twangPivotPosition = transform.position + transform.TransformVector(m_TwangSettings.MovementPivot);
  157. Gizmos.color = new Color(1f, 0f, 0f, 0.85f);
  158. Gizmos.DrawSphere(twangPivotPosition, 0.03f);
  159. Vector3 sceneCamPosition = SceneView.currentDrawingSceneView.camera.transform.position;
  160. Vector3 sceneCamForward = SceneView.currentDrawingSceneView.camera.transform.forward;
  161. // Make sure we don't draw the label when not looking at it
  162. if (Vector3.Dot(sceneCamForward, twangPivotPosition - sceneCamPosition) >= 0f)
  163. Handles.Label(twangPivotPosition, "Twang Pivot");
  164. }
  165. }
  166. #endif
  167. private IEnumerator C_DoTwang()
  168. {
  169. m_Pivot = new GameObject("Shafted Projectile Pivot").transform;
  170. m_Pivot.position = transform.position + Vector3Utils.LocalToWorld(m_TwangSettings.MovementPivot, transform);
  171. m_Pivot.rotation = transform.rotation;
  172. float stopTime = Time.time + m_TwangSettings.Duration;
  173. float range = m_TwangSettings.Range;
  174. float currentVelocity = 0f;
  175. m_TwangSettings.Audio.Play(ItemSelection.Method.RandomExcludeLast, m_AudioSource);
  176. Quaternion localRotation = m_Pivot.localRotation;
  177. Quaternion randomRotation = Quaternion.Euler(new Vector2(
  178. Random.Range(-m_RandomRotation.x, m_RandomRotation.x),
  179. Random.Range(-m_RandomRotation.y, m_RandomRotation.y)));
  180. while (Time.time < stopTime)
  181. {
  182. m_Pivot.localRotation = localRotation * randomRotation * Quaternion.Euler(Random.Range(-range, range), Random.Range(-range, range), 0f);
  183. range = Mathf.SmoothDamp(range, 0f, ref currentVelocity, stopTime - Time.time);
  184. yield return null;
  185. }
  186. }
  187. private void OnDestroy()
  188. {
  189. if (m_Pivot != null)
  190. Destroy(m_Pivot.gameObject);
  191. }
  192. #region Internal
  193. [Serializable]
  194. public class TwangSettings
  195. {
  196. public Vector3 MovementPivot;
  197. public float Duration = 1f;
  198. public float Range = 18f;
  199. public SoundPlayer Audio;
  200. }
  201. #endregion
  202. }
  203. }