using NavMeshPlus.Components; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; using UnityEngine.Tilemaps; using Object = UnityEngine.Object; namespace NavMeshPlus.Extensions { class NavMeshBuilder2dState: IDisposable { public Dictionary spriteMeshMap; public Dictionary coliderMeshMap; public Action lookupCallback; public int defaultArea; public int layerMask; public int agentID; public bool overrideByGrid; public GameObject useMeshPrefab; public bool compressBounds; public Vector3 overrideVector; public NavMeshCollectGeometry CollectGeometry; public CollectObjects CollectObjects; public GameObject parent; public bool hideEditorLogs; protected IEnumerable _root; private bool _disposed; public IEnumerable Root => _root ?? GetRoot(); public NavMeshBuilder2dState() { spriteMeshMap = new Dictionary(); coliderMeshMap = new Dictionary(); _root = null; } public Mesh GetMesh(Sprite sprite) { Mesh mesh; if (spriteMeshMap.ContainsKey(sprite)) { mesh = spriteMeshMap[sprite]; } else { mesh = new Mesh(); NavMeshBuilder2d.sprite2mesh(sprite, mesh); spriteMeshMap.Add(sprite, mesh); } return mesh; } public Mesh GetMesh(Collider2D collider) { #if UNITY_2019_3_OR_NEWER Mesh mesh; uint hash = collider.GetShapeHash(); if (coliderMeshMap.ContainsKey(hash)) { mesh = coliderMeshMap[hash]; } else { mesh = collider.CreateMesh(false, false); coliderMeshMap.Add(hash, mesh); } return mesh; #else throw new InvalidOperationException("PhysicsColliders supported in Unity 2019.3 and higher."); #endif } public void SetRoot(IEnumerable root) { _root = root; } public IEnumerable GetRoot() { switch (CollectObjects) { case CollectObjects.Children: return new[] { parent }; case CollectObjects.Volume: case CollectObjects.All: default: { var list = new List(); var roots = new List(); for (int i = 0; i < SceneManager.sceneCount; ++i) { var s = SceneManager.GetSceneAt(i); if (!s.isLoaded) continue; s.GetRootGameObjects(list); roots.AddRange(list); } return roots; } } } protected virtual void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { // TODO: dispose managed state (managed objects). foreach (var item in spriteMeshMap) { #if UNITY_EDITOR Object.DestroyImmediate(item.Value); #else Object.Destroy(item.Value); #endif } foreach (var item in coliderMeshMap) { #if UNITY_EDITOR Object.DestroyImmediate(item.Value); #else Object.Destroy(item.Value); #endif } spriteMeshMap.Clear(); coliderMeshMap.Clear(); } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. _disposed = true; } public void Dispose() { // Dispose of unmanaged resources. Dispose(true); // Suppress finalization. GC.SuppressFinalize(this); } } class NavMeshBuilder2d { public static void CollectSources(List sources, NavMeshBuilder2dState builder) { foreach (var it in builder.Root) { CollectSources(it, sources, builder); } if (!builder.hideEditorLogs) Debug.Log("Sources " + sources.Count); } public static void CollectSources(GameObject root, List sources, NavMeshBuilder2dState builder) { foreach (var modifier in root.GetComponentsInChildren()) { if (((0x1 << modifier.gameObject.layer) & builder.layerMask) == 0) { continue; } if (!modifier.AffectsAgentType(builder.agentID)) { continue; } int area = builder.defaultArea; //if it is walkable if (builder.defaultArea != 1 && !modifier.ignoreFromBuild) { AddDefaultWalkableTilemap(sources, builder, modifier); } if (modifier.overrideArea) { area = modifier.area; } if (!modifier.ignoreFromBuild) { CollectSources(sources, builder, modifier, area); } } } public static void CollectSources(List sources, NavMeshBuilder2dState builder, NavMeshModifier modifier, int area) { if (builder.CollectGeometry == NavMeshCollectGeometry.PhysicsColliders) { var collider = modifier.GetComponent(); if (collider != null) { CollectSources(sources, collider, area, builder); } } else { var tilemap = modifier.GetComponent(); if (tilemap != null) { CollectTileSources(sources, tilemap, area, builder); } var sprite = modifier.GetComponent(); if (sprite != null) { CollectSources(sources, sprite, area, builder); } } } private static void AddDefaultWalkableTilemap(List sources, NavMeshBuilder2dState builder, NavMeshModifier modifier) { var tilemap = modifier.GetComponent(); if (tilemap != null) { if (builder.compressBounds) { tilemap.CompressBounds(); } if (!builder.hideEditorLogs) Debug.Log($"Walkable Bounds [{tilemap.name}]: {tilemap.localBounds}"); var box = BoxBoundSource(NavMeshSurface.GetWorldBounds(tilemap.transform.localToWorldMatrix, tilemap.localBounds)); box.area = builder.defaultArea; sources.Add(box); } } public static void CollectSources(List sources, SpriteRenderer spriteRenderer, int area, NavMeshBuilder2dState builder) { if (spriteRenderer == null || spriteRenderer.sprite == null) { return; } Mesh mesh; mesh = builder.GetMesh(spriteRenderer.sprite); if (mesh == null) { if (!builder.hideEditorLogs) Debug.Log($"{spriteRenderer.name} mesh is null"); return; } var src = new NavMeshBuildSource(); src.shape = NavMeshBuildSourceShape.Mesh; src.component = spriteRenderer; src.area = area; src.transform = Matrix4x4.TRS(Vector3.Scale(spriteRenderer.transform.position, builder.overrideVector), spriteRenderer.transform.rotation, spriteRenderer.transform.lossyScale); src.sourceObject = mesh; sources.Add(src); builder.lookupCallback?.Invoke(spriteRenderer.gameObject, src); } public static void CollectSources(List sources, Collider2D collider, int area, NavMeshBuilder2dState builder) { if (collider.usedByComposite) { collider = collider.GetComponent(); } Mesh mesh; mesh = builder.GetMesh(collider); if (mesh == null) { if (!builder.hideEditorLogs) Debug.Log($"{collider.name} mesh is null"); return; } var src = new NavMeshBuildSource(); src.shape = NavMeshBuildSourceShape.Mesh; src.area = area; src.component = collider; src.sourceObject = mesh; if (collider.attachedRigidbody) { src.transform = Matrix4x4.TRS(Vector3.Scale(collider.attachedRigidbody.transform.position, builder.overrideVector), collider.attachedRigidbody.transform.rotation, Vector3.one); } else { src.transform = Matrix4x4.identity; } sources.Add(src); builder.lookupCallback?.Invoke(collider.gameObject, src); } public static void CollectTileSources(List sources, Tilemap tilemap, int area, NavMeshBuilder2dState builder) { var bound = tilemap.cellBounds; var modifierTilemap = tilemap.GetComponent(); var vec3int = new Vector3Int(0, 0, 0); var size = new Vector3(tilemap.layoutGrid.cellSize.x, tilemap.layoutGrid.cellSize.y, 0); Mesh sharedMesh = null; Quaternion rot = default; if (builder.useMeshPrefab != null) { sharedMesh = builder.useMeshPrefab.GetComponent().sharedMesh; size = builder.useMeshPrefab.transform.localScale; rot = builder.useMeshPrefab.transform.rotation; } for (int i = bound.xMin; i < bound.xMax; i++) { for (int j = bound.yMin; j < bound.yMax; j++) { var src = new NavMeshBuildSource(); src.area = area; vec3int.x = i; vec3int.y = j; if (!tilemap.HasTile(vec3int)) { continue; } CollectTile(tilemap, builder, vec3int, size, sharedMesh, rot, ref src); if (modifierTilemap && modifierTilemap.TryGetTileModifier(vec3int, tilemap, out NavMeshModifierTilemap.TileModifier tileModifier)) { src.area = tileModifier.overrideArea ? tileModifier.area : area; } sources.Add(src); builder.lookupCallback?.Invoke(tilemap.GetInstantiatedObject(vec3int), src); } } } private static void CollectTile(Tilemap tilemap, NavMeshBuilder2dState builder, Vector3Int vec3int, Vector3 size, Mesh sharedMesh, Quaternion rot, ref NavMeshBuildSource src) { if (!builder.overrideByGrid && tilemap.GetColliderType(vec3int) == Tile.ColliderType.Sprite) { var sprite = tilemap.GetSprite(vec3int); if (sprite != null) { Mesh mesh = builder.GetMesh(sprite); src.component = tilemap; src.transform = GetCellTransformMatrix(tilemap, builder.overrideVector, vec3int); src.shape = NavMeshBuildSourceShape.Mesh; src.sourceObject = mesh; } } else if (builder.useMeshPrefab != null || (builder.overrideByGrid && builder.useMeshPrefab != null)) { src.transform = Matrix4x4.TRS(Vector3.Scale(tilemap.GetCellCenterWorld(vec3int), builder.overrideVector), rot, size); src.shape = NavMeshBuildSourceShape.Mesh; src.sourceObject = sharedMesh; } else //default to box { src.transform = GetCellTransformMatrix(tilemap, builder.overrideVector, vec3int); src.shape = NavMeshBuildSourceShape.Box; src.size = size; } } public static Matrix4x4 GetCellTransformMatrix(Tilemap tilemap, Vector3 scale, Vector3Int vec3int) { return Matrix4x4.TRS(Vector3.Scale(tilemap.GetCellCenterWorld(vec3int), scale) - tilemap.layoutGrid.cellGap, tilemap.transform.rotation, tilemap.transform.lossyScale) * tilemap.orientationMatrix * tilemap.GetTransformMatrix(vec3int); } internal static void sprite2mesh(Sprite sprite, Mesh mesh) { Vector3[] vert = new Vector3[sprite.vertices.Length]; for (int i = 0; i < sprite.vertices.Length; i++) { vert[i] = new Vector3(sprite.vertices[i].x, sprite.vertices[i].y, 0); } mesh.vertices = vert; mesh.uv = sprite.uv; int[] tri = new int[sprite.triangles.Length]; for (int i = 0; i < sprite.triangles.Length; i++) { tri[i] = sprite.triangles[i]; } mesh.triangles = tri; } static private NavMeshBuildSource BoxBoundSource(Bounds localBounds) { var src = new NavMeshBuildSource(); src.transform = Matrix4x4.Translate(localBounds.center); src.shape = NavMeshBuildSourceShape.Box; src.size = localBounds.size; src.area = 0; return src; } } }