LightningRenderer.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Funly.SkyStudio
  5. {
  6. // GPU instanced lightning bolt render for a specific art style item.
  7. [RequireComponent(typeof(AudioSource))]
  8. public class LightningRenderer : BaseSpriteInstancedRenderer
  9. {
  10. static List<LightningSpawnArea> m_SpawnAreas = new List<LightningSpawnArea>();
  11. float m_LightningProbability;
  12. float m_NextSpawnTime;
  13. SkyProfile m_SkyProfile;
  14. LightningArtItem m_Style;
  15. float m_TimeOfDay;
  16. AudioSource m_AudioSource;
  17. float m_LightningIntensity;
  18. float m_ThunderSoundDelay;
  19. float m_SpawnCoolDown;
  20. // Frequency in time that we'll evaluate lighting probability at, so we're frame rate independant.
  21. const float k_ProbabiltyCheckInterval = .5f;
  22. private void Start()
  23. {
  24. // Verify GPU instancing is supported.
  25. if (SystemInfo.supportsInstancing == false) {
  26. Debug.LogError("Can't render lightning since GPU instancing is not supported on this platform.");
  27. enabled = false;
  28. return;
  29. }
  30. m_AudioSource = GetComponent<AudioSource>();
  31. }
  32. protected override Bounds CalculateMeshBounds()
  33. {
  34. return new Bounds(Vector3.zero, new Vector3(500, 500, 500));
  35. }
  36. // Allocate a new data item to track a sprite mesh rendering.
  37. protected override BaseSpriteItemData CreateSpriteItemData()
  38. {
  39. return new BaseSpriteItemData();
  40. }
  41. // Hook to let client prepare and check if we should render before trying.
  42. protected override bool IsRenderingEnabled()
  43. {
  44. if (m_SkyProfile == null || m_SkyProfile.IsFeatureEnabled(ProfileFeatureKeys.LightningFeature) == false) {
  45. return false;
  46. }
  47. if (m_SpawnAreas.Count == 0) {
  48. return false;
  49. }
  50. return true;
  51. }
  52. // Select where the next sprite will be rendered at.
  53. protected override void CalculateSpriteTRS(BaseSpriteItemData data, out Vector3 spritePosition, out Quaternion spriteRotation, out Vector3 spriteScale)
  54. {
  55. LightningSpawnArea area = GetRandomLightningSpawnArea();
  56. float boltScale = CalculateLightningBoltScaleForArea(area);
  57. spriteScale = new Vector3(boltScale, boltScale, boltScale);
  58. spritePosition = GetRandomWorldPositionInsideSpawnArea(area);
  59. // Always face the sprite textures towards the main camera.
  60. if (Camera.main == null) {
  61. Debug.LogError("Can't billboard lightning to viewer since there is no main camera tagged.");
  62. spriteRotation = area.transform.rotation;
  63. } else {
  64. spriteRotation = Quaternion.LookRotation(spritePosition - Camera.main.transform.position, Vector3.up);
  65. }
  66. }
  67. // Configure a new sprite item data object properties, (could be new or recycled).
  68. protected override void ConfigureSpriteItemData(BaseSpriteItemData data)
  69. {
  70. // A new bolt is getting prepared, so let's schedule the sound effect.
  71. if (m_SkyProfile.IsFeatureEnabled(ProfileFeatureKeys.ThunderFeature)) {
  72. Invoke("PlayThunderBoltSound", m_ThunderSoundDelay);
  73. }
  74. }
  75. // Setup any per-instance data you need to pass.
  76. protected override void PrepareDataArraysForRendering(int instanceId, BaseSpriteItemData data)
  77. {
  78. // No custom properties.
  79. }
  80. protected override void PopulatePropertyBlockForRendering(ref MaterialPropertyBlock propertyBlock)
  81. {
  82. propertyBlock.SetFloat("_Intensity", m_LightningIntensity);
  83. }
  84. protected override int GetNextSpawnCount()
  85. {
  86. // Cool down from last spawn.
  87. if (m_NextSpawnTime > Time.time) {
  88. return 0;
  89. }
  90. m_NextSpawnTime = Time.time + k_ProbabiltyCheckInterval;
  91. if (Random.value < m_LightningProbability) {
  92. m_NextSpawnTime += m_SpawnCoolDown;
  93. return 1;
  94. }
  95. return 0;
  96. }
  97. public void UpdateForTimeOfDay(SkyProfile skyProfile, float timeOfDay, LightningArtItem artItem)
  98. {
  99. m_SkyProfile = skyProfile;
  100. m_TimeOfDay = timeOfDay;
  101. m_Style = artItem;
  102. if (m_SkyProfile == null) {
  103. Debug.LogError("Assigned null sky profile!");
  104. return;
  105. }
  106. if (m_Style == null) {
  107. Debug.LogError("Can't render lightning without an art item");
  108. return;
  109. }
  110. SyncDataFromSkyProfile();
  111. }
  112. void SyncDataFromSkyProfile()
  113. {
  114. m_LightningProbability = m_SkyProfile.GetNumberPropertyValue(ProfilePropertyKeys.LightningProbabilityKey, m_TimeOfDay);
  115. m_LightningIntensity = m_SkyProfile.GetNumberPropertyValue(ProfilePropertyKeys.LightningIntensityKey, m_TimeOfDay);
  116. m_SpawnCoolDown = m_SkyProfile.GetNumberPropertyValue(ProfilePropertyKeys.LightningStrikeCoolDown, m_TimeOfDay);
  117. m_ThunderSoundDelay = m_SkyProfile.GetNumberPropertyValue(ProfilePropertyKeys.ThunderSoundDelayKey, m_TimeOfDay);
  118. // Scale the probability for this art style.
  119. m_LightningProbability *= m_Style.strikeProbability;
  120. m_LightningIntensity *= m_Style.intensity;
  121. m_SpriteSheetLayout.columns = m_Style.columns;
  122. m_SpriteSheetLayout.rows = m_Style.rows;
  123. m_SpriteSheetLayout.frameCount = m_Style.totalFrames;
  124. m_SpriteSheetLayout.frameRate = m_Style.animateSpeed;
  125. m_SpriteTexture = m_Style.spriteSheetTexture;
  126. m_TintColor = m_Style.tintColor * m_SkyProfile.GetColorPropertyValue(ProfilePropertyKeys.LightningTintColorKey, m_TimeOfDay);
  127. modelMesh = m_Style.mesh;
  128. renderMaterial = new Material(m_Style.material);
  129. }
  130. private LightningSpawnArea GetRandomLightningSpawnArea()
  131. {
  132. if (m_SpawnAreas.Count == 0) {
  133. return null;
  134. }
  135. int randomSpawnIndex = Mathf.RoundToInt(Random.Range(0, m_SpawnAreas.Count)) % m_SpawnAreas.Count;
  136. return m_SpawnAreas[randomSpawnIndex];
  137. }
  138. private void PlayThunderBoltSound()
  139. {
  140. if (m_Style.thunderSound != null) {
  141. m_AudioSource.volume = m_SkyProfile.GetNumberPropertyValue(ProfilePropertyKeys.ThunderSoundVolumeKey, m_TimeOfDay);
  142. m_AudioSource.PlayOneShot(m_Style.thunderSound);
  143. }
  144. }
  145. public static void AddSpawnArea(LightningSpawnArea area)
  146. {
  147. if (m_SpawnAreas.Contains(area) == false) {
  148. m_SpawnAreas.Add(area);
  149. }
  150. }
  151. public static void RemoveSpawnArea(LightningSpawnArea area)
  152. {
  153. if (m_SpawnAreas.Contains(area)) {
  154. m_SpawnAreas.Remove(area);
  155. }
  156. }
  157. Vector3 GetRandomWorldPositionInsideSpawnArea(LightningSpawnArea area)
  158. {
  159. float xPos = Random.Range(-area.lightningArea.x, area.lightningArea.x) / 2.0f;
  160. float zPos = Random.Range(-area.lightningArea.z, area.lightningArea.z) / 2.0f;
  161. float yPos = 0;
  162. if (m_Style.alignment == LightningArtItem.Alignment.TopAlign) {
  163. yPos = (area.lightningArea.y / 2.0f) - (m_Style.size / 2.0f);
  164. }
  165. return area.transform.TransformPoint(new Vector3(xPos, yPos, zPos));
  166. }
  167. float CalculateLightningBoltScaleForArea(LightningSpawnArea area)
  168. {
  169. if (m_Style.alignment == LightningArtItem.Alignment.ScaleToFit) {
  170. return area.lightningArea.y / 2.0f;
  171. } else {
  172. return m_Style.size;
  173. }
  174. }
  175. }
  176. }