LightningSpellScript.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. //
  2. // Procedural Lightning for Unity
  3. // (c) 2015 Digital Ruby, LLC
  4. // Source code may be used for personal or commercial projects.
  5. // Source code may NOT be redistributed or sold.
  6. //
  7. using UnityEngine;
  8. using System.Collections;
  9. using System;
  10. namespace DigitalRuby.ThunderAndLightning
  11. {
  12. /// <summary>
  13. /// Base script for lightning bolt spells
  14. /// </summary>
  15. public abstract class LightningSpellScript : MonoBehaviour
  16. {
  17. /// <summary>The start point of the spell. Set this to a muzzle end or hand.</summary>
  18. [Header("Direction and distance")]
  19. [Tooltip("The start point of the spell. Set this to a muzzle end or hand.")]
  20. public GameObject SpellStart;
  21. /// <summary>The end point of the spell. Set this to an empty game object. This will change depending on things like collisions, randomness, etc. Not all spells need an end object, but create this anyway to be sure.</summary>
  22. [Tooltip("The end point of the spell. Set this to an empty game object. " +
  23. "This will change depending on things like collisions, randomness, etc. " +
  24. "Not all spells need an end object, but create this anyway to be sure.")]
  25. public GameObject SpellEnd;
  26. /// <summary>The direction of the spell. Should be normalized. Does not change unless explicitly modified.</summary>
  27. [HideInInspector]
  28. [Tooltip("The direction of the spell. Should be normalized. Does not change unless explicitly modified.")]
  29. public Vector3 Direction;
  30. /// <summary>The maximum distance of the spell</summary>
  31. [Tooltip("The maximum distance of the spell")]
  32. public float MaxDistance = 15.0f;
  33. /// <summary>Whether the collision is an exploision. If not explosion, collision is directional.</summary>
  34. [Header("Collision")]
  35. [Tooltip("Whether the collision is an exploision. If not explosion, collision is directional.")]
  36. public bool CollisionIsExplosion;
  37. /// <summary>The radius of the collision explosion</summary>
  38. [Tooltip("The radius of the collision explosion")]
  39. public float CollisionRadius = 1.0f;
  40. /// <summary>The force to explode with when there is a collision</summary>
  41. [Tooltip("The force to explode with when there is a collision")]
  42. public float CollisionForce = 50.0f;
  43. /// <summary>Collision force mode</summary>
  44. [Tooltip("Collision force mode")]
  45. public ForceMode CollisionForceMode = ForceMode.Impulse;
  46. /// <summary>The particle system for collisions. For best effects, this should emit particles in bursts at time 0 and not loop.</summary>
  47. [Tooltip("The particle system for collisions. For best effects, this should emit particles in bursts at time 0 and not loop.")]
  48. public ParticleSystem CollisionParticleSystem;
  49. /// <summary>The layers that the spell should collide with</summary>
  50. [Tooltip("The layers that the spell should collide with")]
  51. public LayerMask CollisionMask = -1;
  52. /// <summary>Collision audio source</summary>
  53. [Tooltip("Collision audio source")]
  54. public AudioSource CollisionAudioSource;
  55. /// <summary>Collision audio clips. One will be chosen at random and played one shot with CollisionAudioSource.</summary>
  56. [Tooltip("Collision audio clips. One will be chosen at random and played one shot with CollisionAudioSource.")]
  57. public AudioClip[] CollisionAudioClips;
  58. /// <summary>Collision sound volume range.</summary>
  59. [Tooltip("Collision sound volume range.")]
  60. public RangeOfFloats CollisionVolumeRange = new RangeOfFloats { Minimum = 0.4f, Maximum = 0.6f };
  61. /// <summary>The duration in seconds that the spell will last. Not all spells support a duration. For one shot spells, this is how long the spell cast / emission light, etc. will last.</summary>
  62. [Header("Duration and Cooldown")]
  63. [Tooltip("The duration in seconds that the spell will last. Not all spells support a duration. For one shot spells, this is how long the spell cast / emission light, etc. will last.")]
  64. public float Duration = 0.0f;
  65. /// <summary>The cooldown in seconds. Once cast, the spell must wait for the cooldown before being cast again.</summary>
  66. [Tooltip("The cooldown in seconds. Once cast, the spell must wait for the cooldown before being cast again.")]
  67. public float Cooldown = 0.0f;
  68. /// <summary>Emission sound</summary>
  69. [Header("Emission")]
  70. [Tooltip("Emission sound")]
  71. public AudioSource EmissionSound;
  72. /// <summary>Emission particle system. For best results use world space, turn off looping and play on awake.</summary>
  73. [Tooltip("Emission particle system. For best results use world space, turn off looping and play on awake.")]
  74. public ParticleSystem EmissionParticleSystem;
  75. /// <summary>Light to illuminate when spell is cast</summary>
  76. [Tooltip("Light to illuminate when spell is cast")]
  77. public Light EmissionLight;
  78. private int stopToken;
  79. private IEnumerator StopAfterSecondsCoRoutine(float seconds)
  80. {
  81. int token = stopToken;
  82. yield return WaitForSecondsLightning.WaitForSecondsLightningPooled(seconds);
  83. if (token == stopToken)
  84. {
  85. StopSpell();
  86. }
  87. }
  88. /// <summary>
  89. /// Duration, in seconds, remaining for the spell
  90. /// </summary>
  91. protected float DurationTimer { get; private set; }
  92. /// <summary>
  93. /// Cooldown, in seconds, remaining before spell can be cast again
  94. /// </summary>
  95. protected float CooldownTimer { get; private set; }
  96. /// <summary>
  97. /// Apply collision force at a point
  98. /// </summary>
  99. /// <param name="point">Point to apply force at</param>
  100. protected void ApplyCollisionForce(Vector3 point)
  101. {
  102. // apply collision force if needed
  103. if (CollisionForce > 0.0f && CollisionRadius > 0.0f)
  104. {
  105. Collider[] colliders = Physics.OverlapSphere(point, CollisionRadius, CollisionMask);
  106. foreach (Collider c in colliders)
  107. {
  108. Rigidbody r = c.GetComponent<Rigidbody>();
  109. if (r != null)
  110. {
  111. if (CollisionIsExplosion)
  112. {
  113. r.AddExplosionForce(CollisionForce, point, CollisionRadius, CollisionForce * 0.02f, CollisionForceMode);
  114. }
  115. else
  116. {
  117. r.AddForce(CollisionForce * Direction, CollisionForceMode);
  118. }
  119. }
  120. }
  121. }
  122. }
  123. /// <summary>
  124. /// Play a collision sound
  125. /// </summary>
  126. /// <param name="pos">Location of the sound</param>
  127. protected void PlayCollisionSound(Vector3 pos)
  128. {
  129. if (CollisionAudioSource != null && CollisionAudioClips != null && CollisionAudioClips.Length > 0)
  130. {
  131. int index = UnityEngine.Random.Range(0, CollisionAudioClips.Length - 1);
  132. float volume = UnityEngine.Random.Range(CollisionVolumeRange.Minimum, CollisionVolumeRange.Maximum);
  133. CollisionAudioSource.transform.position = pos;
  134. CollisionAudioSource.PlayOneShot(CollisionAudioClips[index], volume);
  135. }
  136. }
  137. /// <summary>
  138. /// Start. Derived classes should call base class method first.
  139. /// </summary>
  140. protected virtual void Start()
  141. {
  142. if (EmissionLight != null)
  143. {
  144. EmissionLight.enabled = false;
  145. }
  146. }
  147. /// <summary>
  148. /// Update. Derived classes should call base class method first.
  149. /// </summary>
  150. protected virtual void Update()
  151. {
  152. CooldownTimer = Mathf.Max(0.0f, CooldownTimer - LightningBoltScript.DeltaTime);
  153. DurationTimer = Mathf.Max(0.0f, DurationTimer - LightningBoltScript.DeltaTime);
  154. }
  155. /// <summary>
  156. /// Late Update.
  157. /// </summary>
  158. protected virtual void LateUpdate()
  159. {
  160. }
  161. /// <summary>
  162. /// On destroy - derived classes should call base class method first
  163. /// </summary>
  164. protected virtual void OnDestroy() { }
  165. /// <summary>
  166. /// Start the spell
  167. /// </summary>
  168. protected abstract void OnCastSpell();
  169. /// <summary>
  170. /// Stop the spell
  171. /// </summary>
  172. protected abstract void OnStopSpell();
  173. /// <summary>
  174. /// On activated
  175. /// </summary>
  176. protected virtual void OnActivated() { }
  177. /// <summary>
  178. /// On deactivated
  179. /// </summary>
  180. protected virtual void OnDeactivated() { }
  181. /// <summary>
  182. /// Cast the spell
  183. /// </summary>
  184. /// <returns>True if was able to cast, false if not (i.e. cooldown not met yet)</returns>
  185. public bool CastSpell()
  186. {
  187. if (!CanCastSpell)
  188. {
  189. return false;
  190. }
  191. Casting = true;
  192. DurationTimer = Duration;
  193. CooldownTimer = Cooldown;
  194. OnCastSpell();
  195. if (Duration > 0.0f)
  196. {
  197. StopAfterSeconds(Duration);
  198. }
  199. if (EmissionParticleSystem != null)
  200. {
  201. EmissionParticleSystem.Play();
  202. }
  203. if (EmissionLight != null)
  204. {
  205. EmissionLight.transform.position = SpellStart.transform.position;
  206. EmissionLight.enabled = true;
  207. }
  208. if (EmissionSound != null)
  209. {
  210. EmissionSound.Play();
  211. }
  212. return true;
  213. }
  214. /// <summary>
  215. /// Stop casting a spell. Some spells are single shot and this method does nothing. Spells
  216. /// that are continouous for example would stop with this method call.
  217. /// </summary>
  218. public void StopSpell()
  219. {
  220. if (Casting)
  221. {
  222. stopToken++;
  223. if (EmissionParticleSystem != null)
  224. {
  225. EmissionParticleSystem.Stop();
  226. }
  227. if (EmissionLight != null)
  228. {
  229. EmissionLight.enabled = false;
  230. }
  231. if (EmissionSound != null && EmissionSound.loop)
  232. {
  233. EmissionSound.Stop();
  234. }
  235. DurationTimer = 0.0f;
  236. Casting = false;
  237. OnStopSpell();
  238. }
  239. }
  240. /// <summary>
  241. /// Equip / ready the spell
  242. /// </summary>
  243. public void ActivateSpell()
  244. {
  245. OnActivated();
  246. }
  247. /// <summary>
  248. /// Unequip the spell
  249. /// </summary>
  250. public void DeactivateSpell()
  251. {
  252. OnDeactivated();
  253. }
  254. /// <summary>
  255. /// Stop the spell after a certain amount of seconds. If the spell is stopped before seconds elapses, nothing happens.
  256. /// </summary>
  257. /// <param name="seconds">Seconds to wait before stopping</param>
  258. public void StopAfterSeconds(float seconds)
  259. {
  260. StartCoroutine(StopAfterSecondsCoRoutine(seconds));
  261. }
  262. /// <summary>
  263. /// Find a game object searching recursively through all children and grand-children, etc.
  264. /// </summary>
  265. /// <param name="t">Transform</param>
  266. /// <param name="name">Name of object to find</param>
  267. /// <returns>GameObject or null if not found</returns>
  268. public static GameObject FindChildRecursively(Transform t, string name)
  269. {
  270. if (t.name == name)
  271. {
  272. return t.gameObject;
  273. }
  274. for (int i = 0; i < t.childCount; i++)
  275. {
  276. GameObject obj = FindChildRecursively(t.GetChild(i), name);
  277. if (obj != null)
  278. {
  279. return obj;
  280. }
  281. }
  282. return null;
  283. }
  284. /// <summary>
  285. /// Is the spell currently being cast?
  286. /// </summary>
  287. public bool Casting { get; private set; }
  288. /// <summary>
  289. /// Determines whether the spell can be cast or not
  290. /// </summary>
  291. public bool CanCastSpell { get { return (!Casting && CooldownTimer <= 0.0f); } }
  292. }
  293. }