SurfaceManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.SceneManagement;
  4. namespace HQFPSWeapons
  5. {
  6. public struct SurfaceEffectSpawnEvent
  7. {
  8. public GameObject EffectObj;
  9. public int SurfaceId;
  10. public SurfaceEffects EffectType;
  11. public float AudioVolume;
  12. }
  13. public class TerrainInfo
  14. {
  15. public Vector3 Position { get; private set; }
  16. public TerrainData Data { get; private set; }
  17. private Terrain m_Terrain;
  18. private TerrainLayer[] m_Layers;
  19. public TerrainInfo(Terrain terrain)
  20. {
  21. Position = terrain.GetPosition();
  22. m_Terrain = terrain;
  23. Data = terrain.terrainData;
  24. m_Layers = Data.terrainLayers;
  25. }
  26. public Texture GetSplatmapPrototypeId(int i)
  27. {
  28. if(m_Layers != null && m_Layers.Length > i)
  29. return m_Layers[i].diffuseTexture;
  30. return null;
  31. }
  32. public float[,,] GetAlphamaps(int x, int y, int width, int height)
  33. {
  34. return m_Terrain.terrainData.GetAlphamaps(x, y, width, height);
  35. }
  36. }
  37. /// <summary>
  38. /// Global surface effects system
  39. /// </summary>
  40. public class SurfaceManager : Singleton<SurfaceManager>
  41. {
  42. [SerializeField]
  43. private bool m_SpatializeAudio = false;
  44. [SerializeField]
  45. private SurfaceInfo m_DefaultSurface;
  46. [Space]
  47. [SerializeField]
  48. private string m_SurfacesPath = "Surfaces/";
  49. private SurfaceInfo[] m_Surfaces;
  50. private Dictionary<Collider, TerrainInfo> m_SceneTerrains;
  51. public static void SpawnEffect(RaycastHit hitInfo, SurfaceEffects effectType, float audioVolume)
  52. {
  53. SpawnEffect(hitInfo, effectType, audioVolume, hitInfo.point, Quaternion.LookRotation(hitInfo.normal));
  54. }
  55. public static void SpawnEffect(RaycastHit hitInfo, SurfaceEffects effectType, float audioVolume, Vector3 position, Quaternion rotation)
  56. {
  57. SurfaceInfo surfInfo = Instance.Internal_GetSurfaceInfo(hitInfo);
  58. if(surfInfo != null)
  59. {
  60. PoolableObject effect = PoolingManager.Instance.GetObject(surfInfo.GetInstanceID().ToString() + effectType.ToString());
  61. if(effect != null)
  62. {
  63. effect.transform.position = position;
  64. effect.transform.rotation = rotation;
  65. effect.GetComponent<SurfaceEffect>().Play(audioVolume);
  66. }
  67. }
  68. }
  69. public static void SpawnEffect(int surfaceId, SurfaceEffects effectType, float audioVolume, Vector3 position, Quaternion rotation)
  70. {
  71. SurfaceInfo surfInfo = Instance.GetSurfaceWithId(surfaceId);
  72. PoolableObject effect = PoolingManager.Instance.GetObject(surfInfo.GetInstanceID().ToString() + effectType.ToString());
  73. if(effect != null)
  74. {
  75. effect.transform.position = position;
  76. effect.transform.rotation = rotation;
  77. effect.GetComponent<SurfaceEffect>().Play(audioVolume);
  78. }
  79. }
  80. public static SurfaceInfo GetSurfaceInfo(RaycastHit hitInfo)
  81. {
  82. return Instance.Internal_GetSurfaceInfo(hitInfo);
  83. }
  84. public static SurfaceInfo GetSurfaceInfo(Collider collider, Vector3 point, int triIndex)
  85. {
  86. return Instance.Internal_GetSurfaceInfoWithCollider(collider, point, triIndex);
  87. }
  88. private SurfaceInfo Internal_GetSurfaceInfo(RaycastHit hitInfo)
  89. {
  90. if(m_Surfaces.Length == 0)
  91. return null;
  92. //Check if the collider has a surface identity component beforehand, if not get the texture from the renderer
  93. if (hitInfo.collider.TryGetComponent(out SurfaceIdentity surfId))
  94. {
  95. foreach (var surfInfo in m_Surfaces)
  96. {
  97. if (surfInfo.name == surfId.Surface.name)
  98. return surfInfo;
  99. }
  100. }
  101. Texture texture;
  102. TerrainInfo terrainInfo;
  103. if(m_SceneTerrains.TryGetValue(hitInfo.collider, out terrainInfo))
  104. {
  105. float[] textureMix = GetTerrainTextureMix(hitInfo.point, terrainInfo.Data, terrainInfo.Position);
  106. int textureIndex = GetTerrainTextureIndex(textureMix);
  107. texture = terrainInfo.GetSplatmapPrototypeId(textureIndex);
  108. }
  109. else
  110. texture = GetMeshTextureId(hitInfo.collider, hitInfo.triangleIndex);
  111. if(texture != null)
  112. {
  113. for(int i = 0;i < m_Surfaces.Length;i++)
  114. {
  115. if(m_Surfaces[i].HasTexture(texture))
  116. return m_Surfaces[i];
  117. }
  118. }
  119. return m_DefaultSurface;
  120. }
  121. private SurfaceInfo Internal_GetSurfaceInfoWithCollider(Collider collider, Vector3 point, int triIndex)
  122. {
  123. if (m_Surfaces.Length == 0)
  124. return null;
  125. //Check if the collider has a surface identity component beforehand, if not get the texture from the renderer
  126. if (collider.TryGetComponent(out SurfaceIdentity surfId))
  127. {
  128. foreach (var surfInfo in m_Surfaces)
  129. {
  130. if (surfInfo.name == surfId.Surface.name)
  131. return surfInfo;
  132. }
  133. }
  134. Texture texture;
  135. TerrainInfo terrainInfo;
  136. if (m_SceneTerrains.TryGetValue(collider, out terrainInfo))
  137. {
  138. float[] textureMix = GetTerrainTextureMix(point, terrainInfo.Data, terrainInfo.Position);
  139. int textureIndex = GetTerrainTextureIndex(textureMix);
  140. texture = terrainInfo.GetSplatmapPrototypeId(textureIndex);
  141. }
  142. else
  143. texture = GetMeshTextureId(collider, triIndex);
  144. if (texture != null)
  145. {
  146. for (int i = 0; i < m_Surfaces.Length; i++)
  147. {
  148. if (m_Surfaces[i].HasTexture(texture))
  149. return m_Surfaces[i];
  150. }
  151. }
  152. return m_DefaultSurface;
  153. }
  154. private SurfaceInfo GetSurfaceWithId(int surfaceId)
  155. {
  156. if(m_Surfaces.Length > surfaceId && surfaceId >= 0)
  157. return m_Surfaces[surfaceId];
  158. return null;
  159. }
  160. private void Awake()
  161. {
  162. m_Surfaces = Resources.LoadAll<SurfaceInfo>(m_SurfacesPath);
  163. for(int i = 0;i < m_Surfaces.Length;i++)
  164. {
  165. string surfName = m_Surfaces[i].name;
  166. m_Surfaces[i] = Instantiate(m_Surfaces[i]);
  167. m_Surfaces[i].name = surfName;
  168. m_Surfaces[i].CacheTextures();
  169. }
  170. SceneManager.sceneLoaded += CacheTerrains;
  171. CacheTerrains(new Scene(), LoadSceneMode.Single);
  172. CacheSurfaceEffects();
  173. }
  174. private void CacheSurfaceEffects()
  175. {
  176. foreach(var surfInfo in m_Surfaces)
  177. {
  178. string surfInstanceId = surfInfo.GetInstanceID().ToString();
  179. CreatePoolForEffect(surfInfo.name + "_" + SurfaceEffects.SoftFootstep.ToString(), surfInfo.SoftFootstepEffect, 25, 50, true, surfInstanceId + SurfaceEffects.SoftFootstep.ToString());
  180. CreatePoolForEffect(surfInfo.name + "_" + SurfaceEffects.HardFootstep.ToString(), surfInfo.HardFootstepEffect, 25, 50, true, surfInstanceId + SurfaceEffects.HardFootstep.ToString());
  181. CreatePoolForEffect(surfInfo.name + "_" + SurfaceEffects.FallImpact.ToString(), surfInfo.FallImpactEffect, 25, 50, true, surfInstanceId + SurfaceEffects.FallImpact.ToString());
  182. CreatePoolForEffect(surfInfo.name + "_" + SurfaceEffects.BulletHit.ToString(), surfInfo.BulletHitEffect, 50, 100, true, surfInstanceId + SurfaceEffects.BulletHit.ToString());
  183. CreatePoolForEffect(surfInfo.name + "_" + SurfaceEffects.Slash.ToString(), surfInfo.SlashEffect, 25, 50, true, surfInstanceId + SurfaceEffects.Slash.ToString());
  184. CreatePoolForEffect(surfInfo.name + "_" + SurfaceEffects.Stab.ToString(), surfInfo.StabEffect, 25, 50, true, surfInstanceId + SurfaceEffects.Stab.ToString());
  185. if(m_DefaultSurface != null && m_DefaultSurface.name == surfInfo.name)
  186. m_DefaultSurface = surfInfo;
  187. }
  188. }
  189. private void CreatePoolForEffect(string name, SurfaceInfo.EffectPair effectPair, int poolSizeMin, int poolSizeMax, bool autoShrink, string poolId)
  190. {
  191. GameObject effectTemplate = new GameObject(name);
  192. SurfaceEffect effectComponent = effectTemplate.AddComponent<SurfaceEffect>();
  193. effectComponent.Init(effectPair.AudioEffect, effectPair.VisualEffect, m_SpatializeAudio);
  194. PoolingManager.Instance.CreatePool(effectTemplate, poolSizeMin, poolSizeMin, autoShrink, poolId, 5f);
  195. Destroy(effectTemplate);
  196. }
  197. private void CacheTerrains(Scene scene, LoadSceneMode loadSceneMode)
  198. {
  199. m_SceneTerrains = new Dictionary<Collider, TerrainInfo>();
  200. Terrain[] sceneTerrains = FindObjectsOfType<Terrain>();
  201. for(int i = 0;i < sceneTerrains.Length;i++)
  202. {
  203. TerrainCollider terrainCollider = sceneTerrains[i].GetComponent<TerrainCollider>();
  204. if(terrainCollider == null)
  205. continue;
  206. m_SceneTerrains.Add(terrainCollider, new TerrainInfo(sceneTerrains[i]));
  207. }
  208. }
  209. private Texture GetMeshTextureId(Collider collider, int triangleIndex)
  210. {
  211. Renderer renderer = collider.GetComponent<Renderer>();
  212. MeshCollider meshCollider = collider as MeshCollider;
  213. if(!renderer || !renderer.sharedMaterial || !renderer.sharedMaterial.mainTexture)
  214. return null;
  215. else if(!meshCollider || meshCollider.convex)
  216. return renderer.material.mainTexture;
  217. Mesh mesh = meshCollider.sharedMesh;
  218. int materialIndex = -1;
  219. int lookupIndex1 = mesh.triangles[triangleIndex * 3];
  220. int lookupIndex2 = mesh.triangles[triangleIndex * 3 + 1];
  221. int lookupIndex3 = mesh.triangles[triangleIndex * 3 + 2];
  222. for(int i = 0;i < mesh.subMeshCount;i++)
  223. {
  224. int[] triangles = mesh.GetTriangles(i);
  225. for(int j = 0;j < triangles.Length;j += 3)
  226. {
  227. if(triangles[j] == lookupIndex1 && triangles[j + 1] == lookupIndex2 && triangles[j + 2] == lookupIndex3)
  228. {
  229. materialIndex = i;
  230. break;
  231. }
  232. }
  233. if(materialIndex != -1)
  234. break;
  235. }
  236. return renderer.materials[materialIndex].mainTexture;
  237. }
  238. private float[] GetTerrainTextureMix(Vector3 worldPos, TerrainData terrainData, Vector3 terrainPos)
  239. {
  240. // Returns an array containing the relative mix of textures
  241. // on the terrain at this world position.
  242. // The number of values in the array will equal the number
  243. // of textures added to the terrain.
  244. // Calculate which splat map cell the worldPos falls within (ignoring y)
  245. int mapX = (int)(((worldPos.x - terrainPos.x) / terrainData.size.x) * terrainData.alphamapWidth);
  246. int mapZ = (int)(((worldPos.z - terrainPos.z) / terrainData.size.z) * terrainData.alphamapHeight);
  247. // Get the splat data for this cell as a 1x1xN 3D array (where N = number of textures)
  248. float[,,] splatmapData = terrainData.GetAlphamaps(mapX, mapZ, 1, 1);
  249. // Extract the 3D array data to a 1D array:
  250. float[] cellMix = new float[splatmapData.GetUpperBound(2) + 1];
  251. for(int n = 0;n < cellMix.Length;n++)
  252. cellMix[n] = splatmapData[0, 0, n];
  253. return cellMix;
  254. }
  255. private int GetTerrainTextureIndex(float[] textureMix)
  256. {
  257. // Returns the zero-based index of the most dominant texture
  258. float maxMix = 0;
  259. int maxIndex = 0;
  260. // Loop through each mix value and find the maximum.
  261. for(int n = 0;n < textureMix.Length;n++)
  262. {
  263. if(textureMix[n] > maxMix)
  264. {
  265. maxIndex = n;
  266. maxMix = textureMix[n];
  267. }
  268. }
  269. return maxIndex;
  270. }
  271. }
  272. }