Created
March 26, 2026 14:12
-
-
Save bsimser/00812a0077afef0e5aabfb9c4137ae49 to your computer and use it in GitHub Desktop.
Unity Tilemap to PNG Exporter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.Linq; | |
| using UnityEditor; | |
| using UnityEngine; | |
| using UnityEngine.Tilemaps; | |
| public class TilemapExporter : EditorWindow | |
| { | |
| private string outputPath = ""; | |
| private int pixelsPerCell = 16; | |
| private bool autoDetectPPC = true; | |
| private bool exportObjects = true; | |
| private Vector2 scrollPos; | |
| [MenuItem("Tools/Tilemap Exporter")] | |
| public static void ShowWindow() | |
| { | |
| GetWindow<TilemapExporter>("Tilemap Exporter"); | |
| } | |
| private void OnEnable() | |
| { | |
| if (string.IsNullOrEmpty(outputPath)) | |
| outputPath = Path.Combine(Application.dataPath, "TilemapExport"); | |
| } | |
| private void OnGUI() | |
| { | |
| // Setup the editor layout | |
| EditorGUILayout.LabelField("Tilemap to PNG Exporter", EditorStyles.boldLabel); | |
| EditorGUILayout.Space(); | |
| EditorGUILayout.BeginHorizontal(); | |
| outputPath = EditorGUILayout.TextField("Output Folder", outputPath); | |
| if (GUILayout.Button("Browse...", GUILayout.Width(80))) | |
| { | |
| string selected = EditorUtility.OpenFolderPanel("Select Output Folder", outputPath, ""); | |
| if (!string.IsNullOrEmpty(selected)) | |
| outputPath = selected; | |
| } | |
| EditorGUILayout.EndHorizontal(); | |
| EditorGUILayout.Space(); | |
| autoDetectPPC = EditorGUILayout.Toggle("Auto-Detect Pixels/Cell", autoDetectPPC); | |
| GUI.enabled = !autoDetectPPC; | |
| pixelsPerCell = EditorGUILayout.IntField("Pixels Per Cell", pixelsPerCell); | |
| GUI.enabled = true; | |
| exportObjects = EditorGUILayout.Toggle("Export Sprite Objects", exportObjects); | |
| EditorGUILayout.Space(); | |
| EditorGUILayout.LabelField("Scene Tilemaps", EditorStyles.boldLabel); | |
| // Get the tilemaps in the scene | |
| var tilemaps = FindObjectsOfType<Tilemap>(); | |
| SortByRenderOrder(tilemaps); | |
| // Collect standalone SpriteRenderers (not part of tilemaps) for sprite export | |
| var spriteRenderers = GetStandaloneSpriteRenderers(tilemaps); | |
| if (tilemaps.Length == 0) | |
| { | |
| EditorGUILayout.HelpBox("No Tilemaps found in the current scene.", MessageType.Warning); | |
| return; | |
| } | |
| // Figure out the extent of the tilemap bounds and display the information to the user | |
| scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.MaxHeight(300)); | |
| foreach (var tm in tilemaps) | |
| { | |
| tm.CompressBounds(); | |
| var bounds = tm.cellBounds; | |
| var renderer = tm.GetComponent<TilemapRenderer>(); | |
| string sortInfo = renderer != null | |
| ? string.Format("Layer: {0}, Order: {1}", renderer.sortingLayerName, renderer.sortingOrder) | |
| : "No Renderer"; | |
| int tileCount = CountTiles(tm); | |
| EditorGUILayout.LabelField( | |
| string.Format(" {0} [{1}] Bounds: {2}x{3} Tiles: {4}", | |
| tm.gameObject.name, sortInfo, bounds.size.x, bounds.size.y, tileCount)); | |
| } | |
| EditorGUILayout.EndScrollView(); | |
| // Show sprite objects | |
| if (exportObjects && spriteRenderers.Length > 0) | |
| { | |
| EditorGUILayout.Space(); | |
| EditorGUILayout.LabelField( | |
| string.Format("Sprite Objects: {0}", spriteRenderers.Length), EditorStyles.boldLabel); | |
| } | |
| // Compute and display full bounds | |
| int ppc = autoDetectPPC ? DetectPixelsPerCell(tilemaps) : pixelsPerCell; | |
| BoundsInt fullBounds = ComputeUnionBounds(tilemaps, exportObjects ? spriteRenderers : null, ppc); | |
| EditorGUILayout.Space(); | |
| EditorGUILayout.LabelField( | |
| string.Format("Union Bounds: ({0},{1}) to ({2},{3}) Size: {4}x{5}", | |
| fullBounds.xMin, fullBounds.yMin, fullBounds.xMax, fullBounds.yMax, | |
| fullBounds.size.x, fullBounds.size.y)); | |
| int texW = fullBounds.size.x * ppc; | |
| int texH = fullBounds.size.y * ppc; | |
| EditorGUILayout.LabelField(string.Format("Output Size: {0}x{1} px ({2} px/cell)", texW, texH, ppc)); | |
| if (texW <= 0 || texH <= 0) | |
| { | |
| EditorGUILayout.HelpBox("Computed texture size is zero. Are the tilemaps empty?", MessageType.Warning); | |
| return; | |
| } | |
| if (texW > 16384 || texH > 16384) | |
| { | |
| EditorGUILayout.HelpBox( | |
| string.Format("Output would be {0}x{1} which exceeds the maximum size of 16384 pixels. Consider reducing the tilemap size or pixels/cell.", texW, texH), | |
| MessageType.Error); | |
| } | |
| EditorGUILayout.Space(); | |
| if (GUILayout.Button("Export All", GUILayout.Height(30))) | |
| { | |
| ExportAll(tilemaps, exportObjects ? spriteRenderers : new SpriteRenderer[0], fullBounds, ppc); | |
| } | |
| } | |
| private void ExportAll(Tilemap[] tilemaps, SpriteRenderer[] spriteRenderers, BoundsInt fullBounds, int ppc) | |
| { | |
| if (!Directory.Exists(outputPath)) | |
| Directory.CreateDirectory(outputPath); | |
| int texW = fullBounds.size.x * ppc; | |
| int texH = fullBounds.size.y * ppc; | |
| // Cache readable copies of source textures to avoid re-blitting | |
| var readableCache = new Dictionary<Texture2D, Texture2D>(); | |
| try | |
| { | |
| for (int i = 0; i < tilemaps.Length; i++) | |
| { | |
| var tilemap = tilemaps[i]; | |
| var renderer = tilemap.GetComponent<TilemapRenderer>(); | |
| string layerName = renderer != null ? renderer.sortingLayerName : "Default"; | |
| int order = renderer != null ? renderer.sortingOrder : 0; | |
| string progressLabel = string.Format("Exporting {0} ({1}/{2})", tilemap.gameObject.name, i + 1, tilemaps.Length); | |
| if (EditorUtility.DisplayCancelableProgressBar("Tilemap Export", progressLabel, (float)i / tilemaps.Length)) | |
| { | |
| Debug.Log("Tilemap export cancelled by user."); | |
| break; | |
| } | |
| var outputTex = new Texture2D(texW, texH, TextureFormat.RGBA32, false); | |
| outputTex.filterMode = FilterMode.Point; | |
| // Fill with transparent | |
| var clearPixels = new Color32[texW * texH]; | |
| outputTex.SetPixels32(clearPixels); | |
| tilemap.CompressBounds(); | |
| var bounds = tilemap.cellBounds; | |
| int totalCells = bounds.size.x * bounds.size.y; | |
| int processedCells = 0; | |
| for (int y = bounds.yMin; y < bounds.yMax; y++) | |
| { | |
| for (int x = bounds.xMin; x < bounds.xMax; x++) | |
| { | |
| processedCells++; | |
| if (processedCells % 500 == 0) | |
| { | |
| float cellProgress = (float)processedCells / totalCells; | |
| float totalProgress = (i + cellProgress) / tilemaps.Length; | |
| if (EditorUtility.DisplayCancelableProgressBar("Tilemap Export", | |
| string.Format("{0}: {1}/{2} cells", tilemap.gameObject.name, processedCells, totalCells), | |
| totalProgress)) | |
| { | |
| Debug.Log("Tilemap export cancelled by user."); | |
| DestroyImmediate(outputTex); | |
| return; | |
| } | |
| } | |
| var cellPos = new Vector3Int(x, y, 0); | |
| Sprite sprite = tilemap.GetSprite(cellPos); | |
| if (sprite == null) | |
| continue; | |
| // Get sprite pixels | |
| Color32[] spritePixels = GetSpritePixels(sprite, readableCache); | |
| if (spritePixels == null) | |
| continue; | |
| int spriteW = Mathf.RoundToInt(sprite.rect.width); | |
| int spriteH = Mathf.RoundToInt(sprite.rect.height); | |
| // Handle tile transforms (flips, rotations) | |
| Matrix4x4 matrix = tilemap.GetTransformMatrix(cellPos); | |
| spritePixels = ApplyTransform(spritePixels, spriteW, spriteH, matrix, out spriteW, out spriteH); | |
| // Position in output texture (cell coords relative to full bounds origin) | |
| int px = (x - fullBounds.xMin) * ppc; | |
| int py = (y - fullBounds.yMin) * ppc; | |
| // Blit sprite pixels onto output, respecting alpha | |
| BlitPixels(outputTex, px, py, spritePixels, spriteW, spriteH, ppc); | |
| } | |
| } | |
| outputTex.Apply(); | |
| // Save PNG | |
| string safeName = SanitizeFileName(tilemap.gameObject.name); | |
| string fileName = string.Format("{0}_{1}_{2}.png", layerName, order, safeName); | |
| string filePath = Path.Combine(outputPath, fileName); | |
| byte[] pngData = outputTex.EncodeToPNG(); | |
| File.WriteAllBytes(filePath, pngData); | |
| Debug.LogFormat("Exported: {0} ({1} bytes)", filePath, pngData.Length); | |
| DestroyImmediate(outputTex); | |
| } | |
| // Export sprite objects layer | |
| if (spriteRenderers.Length > 0) | |
| { | |
| EditorUtility.DisplayProgressBar("Tilemap Export", "Exporting sprite objects...", 0.95f); | |
| ExportSpriteObjects(spriteRenderers, fullBounds, ppc, texW, texH, readableCache); | |
| } | |
| } | |
| finally | |
| { | |
| // Cleanup cached textures | |
| foreach (var kvp in readableCache) | |
| DestroyImmediate(kvp.Value); | |
| readableCache.Clear(); | |
| EditorUtility.ClearProgressBar(); | |
| } | |
| int totalFiles = tilemaps.Length + (spriteRenderers.Length > 0 ? 1 : 0); | |
| Debug.LogFormat("Export complete. {0} files written to: {1}", totalFiles, outputPath); | |
| EditorUtility.RevealInFinder(outputPath); | |
| } | |
| private void ExportSpriteObjects(SpriteRenderer[] renderers, BoundsInt fullBounds, int ppc, | |
| int texW, int texH, Dictionary<Texture2D, Texture2D> readableCache) | |
| { | |
| var outputTex = new Texture2D(texW, texH, TextureFormat.RGBA32, false); | |
| outputTex.filterMode = FilterMode.Point; | |
| // Fill with transparent | |
| var clearPixels = new Color32[texW * texH]; | |
| outputTex.SetPixels32(clearPixels); | |
| for (int i = 0; i < renderers.Length; i++) | |
| { | |
| var sr = renderers[i]; | |
| if (sr.sprite == null) | |
| continue; | |
| if (i % 50 == 0) | |
| { | |
| EditorUtility.DisplayProgressBar("Tilemap Export", | |
| string.Format("Sprite objects: {0}/{1}", i + 1, renderers.Length), | |
| (float)i / renderers.Length); | |
| } | |
| Sprite sprite = sr.sprite; | |
| Color32[] spritePixels = GetSpritePixels(sprite, readableCache); | |
| if (spritePixels == null) | |
| continue; | |
| int spriteW = Mathf.RoundToInt(sprite.rect.width); | |
| int spriteH = Mathf.RoundToInt(sprite.rect.height); | |
| // Apply SpriteRenderer flips | |
| if (sr.flipX) | |
| spritePixels = FlipHorizontal(spritePixels, spriteW, spriteH); | |
| if (sr.flipY) | |
| spritePixels = FlipVertical(spritePixels, spriteW, spriteH); | |
| // Apply transform scale flips (negative scale = flip) | |
| Vector3 lossyScale = sr.transform.lossyScale; | |
| if (lossyScale.x < 0) | |
| spritePixels = FlipHorizontal(spritePixels, spriteW, spriteH); | |
| if (lossyScale.y < 0) | |
| spritePixels = FlipVertical(spritePixels, spriteW, spriteH); | |
| // Convert world position to pixel position | |
| // The sprite's pivot determines the anchor point | |
| Vector2 pivot = sprite.pivot; // in pixels from bottom-left of sprite | |
| Vector3 worldPos = sr.transform.position; | |
| // World to pixel: (worldPos - boundsOrigin) * ppc, then offset by pivot | |
| float px = (worldPos.x - fullBounds.xMin) * ppc - pivot.x; | |
| float py = (worldPos.y - fullBounds.yMin) * ppc - pivot.y; | |
| BlitPixelsDirect(outputTex, Mathf.RoundToInt(px), Mathf.RoundToInt(py), | |
| spritePixels, spriteW, spriteH); | |
| } | |
| outputTex.Apply(); | |
| string filePath = Path.Combine(outputPath, "objects.png"); | |
| byte[] pngData = outputTex.EncodeToPNG(); | |
| File.WriteAllBytes(filePath, pngData); | |
| Debug.LogFormat("Exported: {0} ({1} bytes, {2} sprites)", filePath, pngData.Length, renderers.Length); | |
| DestroyImmediate(outputTex); | |
| } | |
| private static Color32[] GetSpritePixels(Sprite sprite, Dictionary<Texture2D, Texture2D> cache) | |
| { | |
| Texture2D sourceTex = sprite.texture; | |
| if (sourceTex == null) | |
| return null; | |
| // Get or create a readable copy of the source texture | |
| if (!cache.TryGetValue(sourceTex, out Texture2D readable)) | |
| { | |
| readable = MakeReadable(sourceTex); | |
| cache[sourceTex] = readable; | |
| } | |
| // Extract sprite rect - textureRect can throw for tightly-packed sprites | |
| Rect spriteRect; | |
| try | |
| { | |
| spriteRect = sprite.textureRect; | |
| } | |
| catch | |
| { | |
| spriteRect = sprite.rect; | |
| } | |
| int rx = Mathf.RoundToInt(spriteRect.x); | |
| int ry = Mathf.RoundToInt(spriteRect.y); | |
| int rw = Mathf.RoundToInt(spriteRect.width); | |
| int rh = Mathf.RoundToInt(spriteRect.height); | |
| // Clamp to texture bounds | |
| if (rx < 0) rx = 0; | |
| if (ry < 0) ry = 0; | |
| if (rx + rw > readable.width) rw = readable.width - rx; | |
| if (ry + rh > readable.height) rh = readable.height - ry; | |
| if (rw <= 0 || rh <= 0) | |
| return null; | |
| // Extract only the sprite's region from the full texture | |
| Color32[] fullPixels = readable.GetPixels32(0); | |
| Color32[] spritePixels = new Color32[rw * rh]; | |
| for (int y = 0; y < rh; y++) | |
| { | |
| for (int x = 0; x < rw; x++) | |
| { | |
| spritePixels[y * rw + x] = fullPixels[(ry + y) * readable.width + (rx + x)]; | |
| } | |
| } | |
| return spritePixels; | |
| } | |
| private static Texture2D MakeReadable(Texture2D source) | |
| { | |
| RenderTexture rt = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.ARGB32); | |
| rt.filterMode = FilterMode.Point; | |
| Graphics.Blit(source, rt); | |
| RenderTexture prev = RenderTexture.active; | |
| RenderTexture.active = rt; | |
| Texture2D readable = new Texture2D(source.width, source.height, TextureFormat.RGBA32, false); | |
| readable.filterMode = FilterMode.Point; | |
| readable.ReadPixels(new Rect(0, 0, source.width, source.height), 0, 0); | |
| readable.Apply(); | |
| RenderTexture.active = prev; | |
| RenderTexture.ReleaseTemporary(rt); | |
| return readable; | |
| } | |
| private static void BlitPixels(Texture2D dest, int destX, int destY, Color32[] src, int srcW, int srcH, int cellSize) | |
| { | |
| // Center the sprite within the cell if sizes differ | |
| int offsetX = (cellSize - srcW) / 2; | |
| int offsetY = (cellSize - srcH) / 2; | |
| BlitPixelsDirect(dest, destX + offsetX, destY + offsetY, src, srcW, srcH); | |
| } | |
| private static void BlitPixelsDirect(Texture2D dest, int destX, int destY, Color32[] src, int srcW, int srcH) | |
| { | |
| for (int sy = 0; sy < srcH; sy++) | |
| { | |
| int dy = destY + sy; | |
| if (dy < 0 || dy >= dest.height) | |
| continue; | |
| for (int sx = 0; sx < srcW; sx++) | |
| { | |
| int dx = destX + sx; | |
| if (dx < 0 || dx >= dest.width) | |
| continue; | |
| Color32 srcColor = src[sy * srcW + sx]; | |
| // Skip fully transparent pixels | |
| if (srcColor.a == 0) | |
| continue; | |
| // Alpha composite onto destination | |
| if (srcColor.a == 255) | |
| { | |
| dest.SetPixel(dx, dy, srcColor); | |
| } | |
| else | |
| { | |
| Color existing = dest.GetPixel(dx, dy); | |
| Color blended = AlphaBlend(existing, srcColor); | |
| dest.SetPixel(dx, dy, blended); | |
| } | |
| } | |
| } | |
| } | |
| private static Color AlphaBlend(Color bg, Color fg) | |
| { | |
| float a = fg.a + bg.a * (1f - fg.a); | |
| if (a < 0.001f) | |
| return Color.clear; | |
| float r = (fg.r * fg.a + bg.r * bg.a * (1f - fg.a)) / a; | |
| float g = (fg.g * fg.a + bg.g * bg.a * (1f - fg.a)) / a; | |
| float b = (fg.b * fg.a + bg.b * bg.a * (1f - fg.a)) / a; | |
| return new Color(r, g, b, a); | |
| } | |
| private static Color32[] ApplyTransform(Color32[] pixels, int w, int h, Matrix4x4 m, out int outW, out int outH) | |
| { | |
| // Identity or near-identity - no transform needed | |
| if (IsIdentity(m)) | |
| { | |
| outW = w; | |
| outH = h; | |
| return pixels; | |
| } | |
| // Extract flip and 90-degree rotation from the matrix | |
| bool flipX = m.m00 < -0.5f; | |
| bool flipY = m.m11 < -0.5f; | |
| // 90 degree rotation: m00≈0, m01≈-1, m10≈1, m11≈0 (or inverse) | |
| bool rotate90 = Mathf.Abs(m.m00) < 0.5f && Mathf.Abs(m.m01) > 0.5f; | |
| Color32[] result = (Color32[])pixels.Clone(); | |
| if (flipX) | |
| { | |
| result = FlipHorizontal(result, w, h); | |
| } | |
| if (flipY) | |
| { | |
| result = FlipVertical(result, w, h); | |
| } | |
| if (rotate90) | |
| { | |
| bool clockwise = m.m01 < 0; | |
| result = Rotate90(result, w, h, clockwise); | |
| outW = h; | |
| outH = w; | |
| } | |
| else | |
| { | |
| outW = w; | |
| outH = h; | |
| } | |
| return result; | |
| } | |
| private static bool IsIdentity(Matrix4x4 m) | |
| { | |
| return Mathf.Abs(m.m00 - 1f) < 0.01f | |
| && Mathf.Abs(m.m11 - 1f) < 0.01f | |
| && Mathf.Abs(m.m01) < 0.01f | |
| && Mathf.Abs(m.m10) < 0.01f; | |
| } | |
| private static Color32[] FlipHorizontal(Color32[] pixels, int w, int h) | |
| { | |
| var result = new Color32[w * h]; | |
| for (int y = 0; y < h; y++) | |
| for (int x = 0; x < w; x++) | |
| result[y * w + x] = pixels[y * w + (w - 1 - x)]; | |
| return result; | |
| } | |
| private static Color32[] FlipVertical(Color32[] pixels, int w, int h) | |
| { | |
| var result = new Color32[w * h]; | |
| for (int y = 0; y < h; y++) | |
| for (int x = 0; x < w; x++) | |
| result[y * w + x] = pixels[(h - 1 - y) * w + x]; | |
| return result; | |
| } | |
| private static Color32[] Rotate90(Color32[] pixels, int w, int h, bool clockwise) | |
| { | |
| var result = new Color32[w * h]; | |
| for (int y = 0; y < h; y++) | |
| { | |
| for (int x = 0; x < w; x++) | |
| { | |
| if (clockwise) | |
| result[x * h + (h - 1 - y)] = pixels[y * w + x]; | |
| else | |
| result[(w - 1 - x) * h + y] = pixels[y * w + x]; | |
| } | |
| } | |
| return result; | |
| } | |
| private static SpriteRenderer[] GetStandaloneSpriteRenderers(Tilemap[] tilemaps) | |
| { | |
| var allSR = FindObjectsOfType<SpriteRenderer>(); | |
| var tilemapObjects = new HashSet<GameObject>(); | |
| foreach (var tm in tilemaps) | |
| { | |
| // Exclude SpriteRenderers that are on the same GameObject as a Tilemap or TilemapRenderer | |
| tilemapObjects.Add(tm.gameObject); | |
| } | |
| return allSR.Where(sr => sr.sprite != null && !tilemapObjects.Contains(sr.gameObject) | |
| && sr.GetComponent<Tilemap>() == null && sr.GetComponent<TilemapRenderer>() == null) | |
| .OrderBy(sr => sr.sortingLayerID) | |
| .ThenBy(sr => sr.sortingOrder) | |
| .ToArray(); | |
| } | |
| private static BoundsInt ComputeUnionBounds(Tilemap[] tilemaps, SpriteRenderer[] spriteRenderers, int ppc) | |
| { | |
| int xMin = int.MaxValue, yMin = int.MaxValue; | |
| int xMax = int.MinValue, yMax = int.MinValue; | |
| foreach (var tm in tilemaps) | |
| { | |
| tm.CompressBounds(); | |
| var b = tm.cellBounds; | |
| if (b.size.x == 0 || b.size.y == 0) | |
| continue; | |
| if (b.xMin < xMin) xMin = b.xMin; | |
| if (b.yMin < yMin) yMin = b.yMin; | |
| if (b.xMax > xMax) xMax = b.xMax; | |
| if (b.yMax > yMax) yMax = b.yMax; | |
| } | |
| // Expand bounds to include sprite objects | |
| if (spriteRenderers != null) | |
| { | |
| foreach (var sr in spriteRenderers) | |
| { | |
| if (sr.sprite == null) continue; | |
| Bounds wb = sr.bounds; | |
| int sxMin = Mathf.FloorToInt(wb.min.x); | |
| int syMin = Mathf.FloorToInt(wb.min.y); | |
| int sxMax = Mathf.CeilToInt(wb.max.x); | |
| int syMax = Mathf.CeilToInt(wb.max.y); | |
| if (sxMin < xMin) xMin = sxMin; | |
| if (syMin < yMin) yMin = syMin; | |
| if (sxMax > xMax) xMax = sxMax; | |
| if (syMax > yMax) yMax = syMax; | |
| } | |
| } | |
| if (xMin == int.MaxValue) | |
| return new BoundsInt(0, 0, 0, 0, 0, 0); | |
| return new BoundsInt(xMin, yMin, 0, xMax - xMin, yMax - yMin, 1); | |
| } | |
| private static int DetectPixelsPerCell(Tilemap[] tilemaps) | |
| { | |
| foreach (var tm in tilemaps) | |
| { | |
| tm.CompressBounds(); | |
| var bounds = tm.cellBounds; | |
| foreach (var pos in bounds.allPositionsWithin) | |
| { | |
| Sprite sprite = tm.GetSprite(pos); | |
| if (sprite != null) | |
| { | |
| // Use sprite rect width as pixels per cell | |
| int ppc = Mathf.RoundToInt(sprite.rect.width); | |
| if (ppc > 0) | |
| return ppc; | |
| } | |
| } | |
| } | |
| return 16; // fallback | |
| } | |
| private static int CountTiles(Tilemap tm) | |
| { | |
| int count = 0; | |
| var bounds = tm.cellBounds; | |
| foreach (var pos in bounds.allPositionsWithin) | |
| { | |
| if (tm.HasTile(pos)) | |
| count++; | |
| } | |
| return count; | |
| } | |
| private static void SortByRenderOrder(Tilemap[] tilemaps) | |
| { | |
| System.Array.Sort(tilemaps, (a, b) => | |
| { | |
| var ra = a.GetComponent<TilemapRenderer>(); | |
| var rb = b.GetComponent<TilemapRenderer>(); | |
| int layerA = ra != null ? ra.sortingLayerID : 0; | |
| int layerB = rb != null ? rb.sortingLayerID : 0; | |
| if (layerA != layerB) | |
| return layerA.CompareTo(layerB); | |
| int orderA = ra != null ? ra.sortingOrder : 0; | |
| int orderB = rb != null ? rb.sortingOrder : 0; | |
| return orderA.CompareTo(orderB); | |
| }); | |
| } | |
| private static string SanitizeFileName(string name) | |
| { | |
| char[] invalid = Path.GetInvalidFileNameChars(); | |
| foreach (char c in invalid) | |
| name = name.Replace(c, '_'); | |
| return name; | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple tool to export all tilemaps (and optionallly sprites) in a scene into separate PNG files. Maybe you have an old Unity project with a tilemap you want to move to Godot or another engine? This will create one PNG for each tilemap it finds in the scene, along with a file named "objects.png" that will contain any non-tilemap sprites it finds. Just add this to an Editor folder in your project and find the window in the Tools menu. Enjoy!