UnityNavMesh2DAdapter.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. #if UNITY_NAVIGATION_COMPONENTS
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.AI;
  6. using System.Linq;
  7. using UnityEngine.Tilemaps;
  8. using Unity.AI.Navigation;
  9. #if UNITY_EDITOR
  10. using UnityEditor;
  11. using UnityEditor.SceneManagement;
  12. #endif
  13. namespace DunGen.Adapters
  14. {
  15. [AddComponentMenu("DunGen/NavMesh/Unity NavMesh Adapter (2D)")]
  16. public class UnityNavMesh2DAdapter : NavMeshAdapter
  17. {
  18. #region Nested Types
  19. [Serializable]
  20. public sealed class NavMeshAgentLinkInfo
  21. {
  22. public int AgentTypeID = 0;
  23. public int AreaTypeID = 0;
  24. public bool DisableLinkWhenDoorIsClosed = true;
  25. }
  26. #endregion
  27. private static Quaternion rotation = Quaternion.Euler(-90f, 0f, 0f);
  28. public bool AddNavMeshLinksBetweenRooms = true;
  29. public List<NavMeshAgentLinkInfo> NavMeshAgentTypes = new List<NavMeshAgentLinkInfo>() { new NavMeshAgentLinkInfo() };
  30. public float NavMeshLinkDistanceFromDoorway = 1f;
  31. #region Accessors
  32. public int AgentTypeID { get { return agentTypeID; } set { agentTypeID = value; } }
  33. public bool OverrideTileSize { get { return overrideTileSize; } set { overrideTileSize = value; } }
  34. public int TileSize { get { return tileSize; } set { tileSize = value; } }
  35. public bool OverrideVoxelSize { get { return overrideVoxelSize; } set { overrideVoxelSize = value; } }
  36. public float VoxelSize { get { return voxelSize; } set { voxelSize = value; } }
  37. public NavMeshData NavMeshData { get { return navMeshData; } set { navMeshData = value; } }
  38. public LayerMask LayerMask { get { return layerMask; } set { layerMask = value; } }
  39. public int DefaultArea { get { return defaultArea; } set { defaultArea = value; } }
  40. public bool IgnoreNavMeshAgent { get { return ignoreNavMeshAgent; } set { ignoreNavMeshAgent = value; } }
  41. public bool IgnoreNavMeshObstacle { get { return ignoreNavMeshObstacle; } set { ignoreNavMeshObstacle = value; } }
  42. public int UnwalkableArea { get { return unwalkableArea; } set { unwalkableArea = value; } }
  43. #endregion
  44. [SerializeField]
  45. private int agentTypeID;
  46. [SerializeField]
  47. private bool overrideTileSize;
  48. [SerializeField]
  49. private int tileSize = 256;
  50. [SerializeField]
  51. private bool overrideVoxelSize;
  52. [SerializeField]
  53. private float voxelSize;
  54. [SerializeField]
  55. private NavMeshData navMeshData;
  56. [SerializeField]
  57. private LayerMask layerMask = ~0;
  58. [SerializeField]
  59. private int defaultArea;
  60. [SerializeField]
  61. private bool ignoreNavMeshAgent = true;
  62. [SerializeField]
  63. private bool ignoreNavMeshObstacle = true;
  64. [SerializeField]
  65. private int unwalkableArea = 1;
  66. private NavMeshDataInstance m_NavMeshDataInstance;
  67. private Dictionary<Sprite, Mesh> cachedSpriteMeshes = new Dictionary<Sprite, Mesh>();
  68. public override void Generate(Dungeon dungeon)
  69. {
  70. BakeNavMesh(dungeon);
  71. // Add links between rooms
  72. if (AddNavMeshLinksBetweenRooms)
  73. {
  74. foreach (var connection in dungeon.Connections)
  75. foreach (var linkInfo in NavMeshAgentTypes)
  76. AddNavMeshLink(connection, linkInfo);
  77. }
  78. if (OnProgress != null)
  79. OnProgress(new NavMeshGenerationProgress() { Description = "Done", Percentage = 1.0f });
  80. }
  81. protected void AddData()
  82. {
  83. #if UNITY_EDITOR
  84. var isInPreviewScene = EditorSceneManager.IsPreviewSceneObject(this);
  85. var isPrefab = isInPreviewScene || EditorUtility.IsPersistent(this);
  86. if (isPrefab)
  87. return;
  88. #endif
  89. if (m_NavMeshDataInstance.valid)
  90. return;
  91. if (navMeshData != null)
  92. {
  93. m_NavMeshDataInstance = NavMesh.AddNavMeshData(navMeshData, transform.position, rotation);
  94. m_NavMeshDataInstance.owner = this;
  95. }
  96. }
  97. protected void RemoveData()
  98. {
  99. m_NavMeshDataInstance.Remove();
  100. m_NavMeshDataInstance = new NavMeshDataInstance();
  101. foreach (var pair in cachedSpriteMeshes)
  102. DestroyImmediate(pair.Value);
  103. cachedSpriteMeshes.Clear();
  104. }
  105. protected virtual void BakeNavMesh(Dungeon dungeon)
  106. {
  107. var sources = CollectSources();
  108. var sourcesBounds = CalculateWorldBounds(sources);
  109. var data = NavMeshBuilder.BuildNavMeshData(GetBuildSettings(),
  110. sources,
  111. sourcesBounds,
  112. transform.position,
  113. rotation);
  114. if (data != null)
  115. {
  116. data.name = gameObject.name;
  117. RemoveData();
  118. navMeshData = data;
  119. if (isActiveAndEnabled)
  120. AddData();
  121. }
  122. if (OnProgress != null)
  123. OnProgress(new NavMeshGenerationProgress() { Description = "Done", Percentage = 1.0f });
  124. }
  125. protected void AppendModifierVolumes(ref List<NavMeshBuildSource> sources)
  126. {
  127. #if UNITY_EDITOR
  128. var myStage = StageUtility.GetStageHandle(gameObject);
  129. if (!myStage.IsValid())
  130. return;
  131. #endif
  132. // Modifiers
  133. List<NavMeshModifierVolume> modifiers;
  134. modifiers = new List<NavMeshModifierVolume>(GetComponentsInChildren<NavMeshModifierVolume>());
  135. modifiers.RemoveAll(x => !x.isActiveAndEnabled);
  136. foreach (var m in modifiers)
  137. {
  138. if ((layerMask & (1 << m.gameObject.layer)) == 0)
  139. continue;
  140. if (!m.AffectsAgentType(agentTypeID))
  141. continue;
  142. #if UNITY_EDITOR
  143. if (!myStage.Contains(m.gameObject))
  144. continue;
  145. #endif
  146. var mcenter = m.transform.TransformPoint(m.center);
  147. var scale = m.transform.lossyScale;
  148. var msize = new Vector3(m.size.x * Mathf.Abs(scale.x), m.size.y * Mathf.Abs(scale.y), m.size.z * Mathf.Abs(scale.z));
  149. var src = new NavMeshBuildSource();
  150. src.shape = NavMeshBuildSourceShape.ModifierBox;
  151. src.transform = Matrix4x4.TRS(mcenter, m.transform.rotation, Vector3.one);
  152. src.size = msize;
  153. src.area = m.area;
  154. sources.Add(src);
  155. }
  156. }
  157. protected virtual List<NavMeshBuildSource> CollectSources()
  158. {
  159. var sources = new List<NavMeshBuildSource>();
  160. var markups = new List<NavMeshBuildMarkup>();
  161. List<NavMeshModifier> modifiers;
  162. modifiers = new List<NavMeshModifier>(GetComponentsInChildren<NavMeshModifier>());
  163. modifiers.RemoveAll(x => !x.isActiveAndEnabled);
  164. foreach (var m in modifiers)
  165. {
  166. if ((layerMask & (1 << m.gameObject.layer)) == 0)
  167. continue;
  168. if (!m.AffectsAgentType(agentTypeID))
  169. continue;
  170. var markup = new NavMeshBuildMarkup();
  171. markup.root = m.transform;
  172. markup.overrideArea = m.overrideArea;
  173. markup.area = m.area;
  174. markup.ignoreFromBuild = m.ignoreFromBuild;
  175. markups.Add(markup);
  176. }
  177. // Collect sprites
  178. foreach (var spriteRenderer in UnityUtil.FindObjectsByType<SpriteRenderer>())
  179. {
  180. var sprite = spriteRenderer.sprite;
  181. var mesh = GetMesh(sprite);
  182. if (mesh != null)
  183. {
  184. int area = ((layerMask & (1 << spriteRenderer.gameObject.layer)) == 0) ? unwalkableArea : 0;
  185. sources.Add(new NavMeshBuildSource()
  186. {
  187. transform = spriteRenderer.transform.localToWorldMatrix,
  188. size = mesh.bounds.extents * 2f,
  189. shape = NavMeshBuildSourceShape.Mesh,
  190. area = area,
  191. sourceObject = mesh,
  192. component = spriteRenderer,
  193. });
  194. }
  195. }
  196. // Collect tilemaps
  197. NavMeshBuildSource source = new NavMeshBuildSource
  198. {
  199. shape = NavMeshBuildSourceShape.Mesh,
  200. area = 0
  201. };
  202. foreach (var tilemap in UnityUtil.FindObjectsByType<Tilemap>())
  203. {
  204. for (int x = tilemap.cellBounds.xMin; x < tilemap.cellBounds.xMax; x++)
  205. {
  206. for (int y = tilemap.cellBounds.yMin; y < tilemap.cellBounds.yMax; y++)
  207. {
  208. Vector3Int tilePos = new Vector3Int(x, y, 0);
  209. if (!tilemap.HasTile(tilePos))
  210. continue;
  211. // Currently assumes ColliderType.Sprite
  212. var tile = tilemap.GetTile<UnityEngine.Tilemaps.Tile>(tilePos);
  213. var mesh = GetMesh(tilemap.GetSprite(tilePos));
  214. if (mesh != null)
  215. {
  216. source.transform = Matrix4x4.TRS(tilemap.GetCellCenterWorld(tilePos) - tilemap.layoutGrid.cellGap, tilemap.transform.rotation, tilemap.transform.lossyScale) * tilemap.orientationMatrix * tilemap.GetTransformMatrix(tilePos);
  217. source.sourceObject = mesh;
  218. source.component = tilemap;
  219. source.area = (tile.colliderType == UnityEngine.Tilemaps.Tile.ColliderType.None) ? 0 : unwalkableArea;
  220. sources.Add(source);
  221. }
  222. }
  223. }
  224. }
  225. if (ignoreNavMeshAgent)
  226. sources.RemoveAll((x) => (x.component != null && x.component.gameObject.GetComponent<NavMeshAgent>() != null));
  227. if (ignoreNavMeshObstacle)
  228. sources.RemoveAll((x) => (x.component != null && x.component.gameObject.GetComponent<NavMeshObstacle>() != null));
  229. AppendModifierVolumes(ref sources);
  230. return sources;
  231. }
  232. protected Mesh GetMesh(Sprite sprite)
  233. {
  234. if (sprite == null)
  235. return null;
  236. Mesh mesh;
  237. if (!cachedSpriteMeshes.TryGetValue(sprite, out mesh))
  238. {
  239. mesh = new Mesh
  240. {
  241. vertices = sprite.vertices.Select(v => new Vector3(v.x, v.y, 0)).ToArray(),
  242. triangles = sprite.triangles.Select(i => (int)i).ToArray()
  243. };
  244. mesh.RecalculateBounds();
  245. mesh.RecalculateNormals();
  246. mesh.RecalculateTangents();
  247. cachedSpriteMeshes[sprite] = mesh;
  248. }
  249. return mesh;
  250. }
  251. protected void AddNavMeshLink(DoorwayConnection connection, NavMeshAgentLinkInfo agentLinkInfo)
  252. {
  253. var doorway = connection.A.gameObject;
  254. var agentSettings = NavMesh.GetSettingsByID(agentLinkInfo.AgentTypeID);
  255. // We need to account for the agent's radius when setting the link's width
  256. float linkWidth = Mathf.Max(connection.A.Socket.Size.x - (agentSettings.agentRadius * 2), 0.01f);
  257. // Add NavMeshLink to one of the doorways
  258. var link = doorway.AddComponent<NavMeshLink>();
  259. link.agentTypeID = agentLinkInfo.AgentTypeID;
  260. link.bidirectional = true;
  261. link.area = agentLinkInfo.AreaTypeID;
  262. link.startPoint = new Vector3(0, 0, -NavMeshLinkDistanceFromDoorway);
  263. link.endPoint = new Vector3(0, 0, NavMeshLinkDistanceFromDoorway);
  264. link.width = linkWidth;
  265. if (agentLinkInfo.DisableLinkWhenDoorIsClosed)
  266. {
  267. // If there is a door in this doorway, hookup event listeners to enable/disable the link when the door is opened/closed respectively
  268. GameObject doorObj = (connection.A.UsedDoorPrefabInstance != null) ? connection.A.UsedDoorPrefabInstance : (connection.B.UsedDoorPrefabInstance != null) ? connection.B.UsedDoorPrefabInstance : null;
  269. if (doorObj != null)
  270. {
  271. var door = doorObj.GetComponent<Door>();
  272. link.enabled = door.IsOpen;
  273. if (door != null)
  274. door.OnDoorStateChanged += (d, o) => link.enabled = o;
  275. }
  276. }
  277. }
  278. public NavMeshBuildSettings GetBuildSettings()
  279. {
  280. var buildSettings = NavMesh.GetSettingsByID(agentTypeID);
  281. if (buildSettings.agentTypeID == -1)
  282. {
  283. Debug.LogWarning("No build settings for agent type ID " + AgentTypeID, this);
  284. buildSettings.agentTypeID = agentTypeID;
  285. }
  286. if (OverrideTileSize)
  287. {
  288. buildSettings.overrideTileSize = true;
  289. buildSettings.tileSize = TileSize;
  290. }
  291. if (OverrideVoxelSize)
  292. {
  293. buildSettings.overrideVoxelSize = true;
  294. buildSettings.voxelSize = VoxelSize;
  295. }
  296. return buildSettings;
  297. }
  298. protected Bounds CalculateWorldBounds(List<NavMeshBuildSource> sources)
  299. {
  300. // Use the unscaled matrix for the NavMeshSurface
  301. Matrix4x4 worldToLocal = Matrix4x4.TRS(transform.position, rotation, Vector3.one);
  302. worldToLocal = worldToLocal.inverse;
  303. var result = new Bounds();
  304. foreach (var src in sources)
  305. {
  306. switch (src.shape)
  307. {
  308. case NavMeshBuildSourceShape.Mesh:
  309. {
  310. var m = src.sourceObject as Mesh;
  311. result.Encapsulate(GetWorldBounds(worldToLocal * src.transform, m.bounds));
  312. break;
  313. }
  314. case NavMeshBuildSourceShape.Terrain:
  315. {
  316. // Terrain pivot is lower/left corner - shift bounds accordingly
  317. var t = src.sourceObject as TerrainData;
  318. result.Encapsulate(GetWorldBounds(worldToLocal * src.transform, new Bounds(0.5f * t.size, t.size)));
  319. break;
  320. }
  321. case NavMeshBuildSourceShape.Box:
  322. case NavMeshBuildSourceShape.Sphere:
  323. case NavMeshBuildSourceShape.Capsule:
  324. case NavMeshBuildSourceShape.ModifierBox:
  325. result.Encapsulate(GetWorldBounds(worldToLocal * src.transform, new Bounds(Vector3.zero, src.size)));
  326. break;
  327. }
  328. }
  329. // Inflate the bounds a bit to avoid clipping co-planar sources
  330. result.Expand(0.1f);
  331. return result;
  332. }
  333. #region Statics
  334. static Vector3 Abs(Vector3 v)
  335. {
  336. return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
  337. }
  338. static Bounds GetWorldBounds(Matrix4x4 mat, Bounds bounds)
  339. {
  340. var absAxisX = Abs(mat.MultiplyVector(Vector3.right));
  341. var absAxisY = Abs(mat.MultiplyVector(Vector3.up));
  342. var absAxisZ = Abs(mat.MultiplyVector(Vector3.forward));
  343. var worldPosition = mat.MultiplyPoint(bounds.center);
  344. var worldSize = absAxisX * bounds.size.x + absAxisY * bounds.size.y + absAxisZ * bounds.size.z;
  345. return new Bounds(worldPosition, worldSize);
  346. }
  347. #endregion
  348. }
  349. }
  350. #endif