SuperTiled2Unity icon indicating copy to clipboard operation
SuperTiled2Unity copied to clipboard

Feature request: Add option to not import sprites where every pixel is 100% transparent

Open SharlatanY opened this issue 6 years ago • 4 comments

As discussed in #36 :

Would it be possible to add an option to not import sprites where every pixel is 100% transparent (alpha = 0)? I tried around a bit with discarding them but since your indexing them and use that index in various places, I can't just add a fix in one file and wouldn't want to do a bigger refactoring which I will have to try to merge again every time you publish an update of ST2U.

Reason: Sometimes you realize that you don't need some sprites after all and delete them from your sprite sheet. There are good tools out there to repack your sprite sheets that would make this task easy. The only problem is, changing a sprite sheet that's used for a *.tsx file, which in turn is already used by a Tiled map, is a huge pain. Because of that, you just delete the sprites in the source picture (=make the pixels they were at transparent). Since all sprites are imported, even the ones that are 100% invisible, this leads to two problems: Firstly, the project gets cluttered with a lot of sprites that are never used. Secondly, since those transparent sprites are also packed into the atlas, This can increase the required atlas size or amount of atlases a lot, which in turn increases build size by a lot. If you have big tiles/and or small atlases, you sometimes can even end up with an atlas that only contains transparent sprites but of course uses up just as much space as if it was filled with "real" tiles.

SharlatanY avatar Feb 10 '19 16:02 SharlatanY

This issue has important impact.

1. ST2U users might be unaware of Sprite Atlas impact: When we started our Unity game that uses SuperTiled2Unity and 27 tilesheets, we hadn't considered the Sprite Atlas settings yet. Reducing the default Atlas width/height from 2048 to 256 changed the build size from 871MB to 464.8 MB merely due to unused sprite atlas space. Many users might not be aware of this setting and run into the same trap, so it might be advisable to mention this setting in the ST2U documentation.

2. Invisible tiles are prevalent: Many of our tilesets contain many invisible tiles, often 50%, sometimes 85% (when there is 1 tile space beween neighbouring tiles for instance). So even after having minimized the Sprite Atlas size, the created atlas can still waste that much space (50-85%) in such a case!

See this example, black are the used sprites, magenta are these invisible tiles: Screenshot_from_2021-03-05_02-32-27

Doing the spritesheet image again was not an option because it would change all the tilemaps one had created so far with it.

3. Deleting invisible tiles: To remove these 100% transparent tiles, I have patched SuperTiled2Unity to simply not add these tiles if they are detected. For this I had to change only 1 file (contrary to what's mentioned the original post), TileSetLoader.cs.

On Roads: Could not find tile 6404. Tile with flags: 3221231876. Make sure the tilesets were successfully imported.

The subsequent errors if a tilemap references an invisible sprite are not addressed in our copy of SuperTiled2Unity: These error messages are useful because they provide tilemap authors with the information that they have placed tiles that now are 100% invisible and should be deleted (instead of somehow changing the code to silently ignore the tiles).

The only catch is that one can't delete them easily. To account for that, I have written a short python script. I guess not so consistent with the existing tools, perhaps such a feature should be a button in Tiled instead. The script works this way: If one imports a *.tmx.meta file with a SuperTiled2Unity code that doesn't add invisible tiles to the SpriteAtlas, SuperTiled2Unity will spill out an error message complaining that it could not find these tiles in the SpriteAtlas and write these to the *tmx.meta file (But only if the Apply button is pressed, not when using rightclick -> reimport). The script then can read these errors and delete the offending tiles from the tilemap. I assume this is also benefitial for the rendering performance because not rendering invisible tiles is probably faster than not having to consider rendering them. We ended up with 2000 such invisible tiles in only 8 tilemaps.

Now I'm not even sure anymore if the Sprite Atlas created by SuperTiled2Unity does anything, because I could not find any performance difference (same FPS and exact same number of batch renders) between using excessively large ST2U Sprite Atlases, minimal atlases and not using atlases at all. We have imported with "Tiles as objects" disabled and changd the resulting TilemapsRenderers to Chunk rendering mode (perhaps this should also be an ST2U Import setting instead of something that devs have to code using a Custom Importer). It's probably more advisable to let users create own Sprite Atlases because that allows grouping related sprites much better (and group sprites of multiple tilesheets).

Here the hacky code we have used, in case it is useful to any user or inspiring to a dev:

commit 37019d6f31ce50c90598ec23d2b749529a0e93f7
Author: Alexander Heinsius <[email protected]>
Date:   Fri Mar 5 05:43:04 2021 +0100

    Improve FPS and reduce build output size by deleting invisible sprites from SuperTiled2Unity Sprite Atlas and Tilemaps.
    
    Warning:
    We now get an error in case someone has placed an invisible tile on a tilemap, or
    in case someone deleted or relocated a tile from a tilesheet that was still placed on a tilemap!
    
    These invisible tiles have to be deleted manually, save the tilemap as CSV and delete the offending tiles with a text editor!
    
    There were in total about 2000 invisible / obsolete tiles placed on tilemaps that all had caused useless draw calls!

diff --git a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs
index e8895628..abe5762a 100644
--- a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs
+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs
@@ -231,7 +231,13 @@ namespace SuperTiled2Unity.Editor
                     }
                     else if (!badTiles.Contains(tileId.JustTileId))
                     {
-                        ReportError("Could not find tile {0}. Make sure the tilesets were successfully imported.", tileId.JustTileId);
+                        /// Keep this error message in sync with delete-invisible-tiles.py
+                        ReportError(
+                            "On {0}: Could not find tile {1}.{2} Make sure the tilesets were successfully imported.",
+                            goTilemap.name,
+                            tileId.JustTileId,
+                            tileId.JustTileId != utId ? " Tile with flags: " + utId + "." : "");
+
                         badTiles.Add(tileId.JustTileId);
                     }
                 }
diff --git a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TxAssetImporter.cs b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TxAssetImporter.cs
index 8f624416..79db94c0 100644
--- a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TxAssetImporter.cs
+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TxAssetImporter.cs
@@ -103,7 +103,8 @@ namespace SuperTiled2Unity.Editor
                 }
                 else
                 {
-                    ReportError("Could not find tile '{0}' in tileset", tileId.JustTileId);
+                    /// Keep this error message in sync with delete-invisible-tiles.py
+                    ReportError("On " + xObject.ToString() + " Could not find tile '{0}' in tileset", tileId.JustTileId);
                 }
             }
         }
diff --git a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs
index c33114fc..a3d7959a 100644
--- a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs
+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs
@@ -139,6 +139,8 @@ namespace SuperTiled2Unity.Editor
                 return;
             }
 
+            Texture2D tex2d_tmp = Get_Temporary_Texture(tex2d);
+
             for (int i = 0; i < m_TilesetScript.m_TileCount; i++)
             {
                 // Get grid x,y coords
@@ -166,10 +168,39 @@ namespace SuperTiled2Unity.Editor
 
                 // Add the tile to our atlas
                 Rect rcSource = new Rect(srcx, srcy, m_TilesetScript.m_TileWidth, m_TilesetScript.m_TileHeight);
-                atlas.AddTile(i, tex2d, rcSource);
+
+                if (!Is_Invisible_Tile(tex2d_tmp, ref rcSource))
+                    atlas.AddTile(i, tex2d, rcSource);
             }
         }
 
+        // Return a copy that is readable without having to flag the original texture as readable and writable.
+        private Texture2D Get_Temporary_Texture(Texture2D tex2d)
+        {
+            RenderTexture render_texture = RenderTexture.GetTemporary(tex2d.width, tex2d.height);
+            render_texture.filterMode = tex2d.filterMode;
+
+            RenderTexture.active = render_texture;
+            Graphics.Blit(tex2d, render_texture);
+
+            Texture2D temp_tex2d = new Texture2D(tex2d.width, tex2d.height);
+            temp_tex2d.ReadPixels(new Rect(0, 0, tex2d.width, tex2d.height), 0, 0);
+            temp_tex2d.Apply();
+
+            RenderTexture.active = null;
+            return temp_tex2d;
+        }
+
+        private bool Is_Invisible_Tile(Texture2D tex2d, ref Rect rcSource)
+        {
+            for (int x = (int)rcSource.xMin; x < rcSource.xMax; ++x)
+                for (int y = (int)rcSource.yMin; y < rcSource.yMax; ++y)
+                    if (tex2d.GetPixel(x, y).a != 0)
+                        return false;
+
+            return true;
+        }
+
         private void BuildTilesetFromCollection(XElement xTileset, AtlasBuilder atlas)
         {
             m_TilesetScript.m_IsImageCollection = true;
diff --git a/Assets/Resources/delete-invisible-tiles.py b/Assets/Resources/delete-invisible-tiles.py
new file mode 100644
index 00000000..4dae58fa
--- /dev/null
+++ b/Assets/Resources/delete-invisible-tiles.py
@@ -0,0 +1,142 @@
+import sys
+
+# When someone deleted a tile from a tilesheet that is still placed on a map, we can and should get ST2U import errors:
+# For example "On Cliffs1: Could not find tile 1396. Make sure the tilesets were successfully"
+#
+# We utilize this error to delete the obsolete tiles from the tilemap using this script.
+# This improves rendering performance by not rendering invisible tiles and
+# by not including them in the ST2U Sprite Atlas, thus also reducing build size.
+#
+# Workflow:
+# Make sure to have backed up your tilemaps before running this.
+#
+# 1. Save the ST2U errors to the meta file by for example:
+# - Changing edgesPerEllipse from 33 to 32 so that the Apply button becomes enabled, and clicking Apply.
+#
+# 2. Open the Tilemap with Tiled, open the Map Properties, change encoding from GZIP compressed to CSV and save.
+#
+# 3.  Run this script like this:
+#       python3 delete-invisible-tiles.py TileMaps/Levels/WWA_2.1_IN_AND_OUT.tmx
+#
+# 4. Change edgesPerEllipse to 32 again and see that the errors in the meta files have disappeared.
+#
+# 5. Compare the modified tile layers and check that nothing visible has changed.
+#
+# 6. Enable GZIP compression on the tilemap again and save.
+#
+# The procedure of excluding invisible tiles from Tilesheets allows us to reduce the Sprite Atlas size by 50% to 85%! 
+
+def parse_line1(line):
+    # Keep in sync with TmxAssetImporter.TileLayer.cs!
+    # Expected SuperTiled2Unity Debug output:
+    #   - On Cliffs1: Could not find tile 1396. Make sure the tilesets were successfully
+    #   - On Roads: Could not find tile 6404. Tile with flags: 3221231876. Make sure the tilesets were successfully imported.
+
+    tile_layer_name_prefix = "On "
+    tile_layer_name_postfix = ": Could not find tile "
+    tile_id_postfix = ". Make sure"
+    unsigned_tile_id_prefix = ". Tile with flags: "
+
+    if not tile_layer_name_prefix in line or \
+       not tile_layer_name_postfix in line or \
+       not tile_id_postfix in line:
+        return False
+
+    tile_layer_name = line[line.find(tile_layer_name_prefix) + len(tile_layer_name_prefix) : line.find(tile_layer_name_postfix)]
+
+    if unsigned_tile_id_prefix in line:
+        tile_id = line[line.find(unsigned_tile_id_prefix) + len(unsigned_tile_id_prefix) : line.find(tile_id_postfix)]
+    else:
+        tile_id = line[line.find(tile_layer_name_postfix) + len(tile_layer_name_postfix): line.find(tile_id_postfix)]
+
+    return [tile_layer_name, tile_id]
+
+def parse_line2(line):
+    # Expected SuperTiled2Unity Debug output:
+    # ReportError("On " + xObject.ToString() + " Could not find tile '{0}' in tileset", tileId.JustTileId);
+    # not implemented, didn't encounter this one
+    return False
+
+def get_errors_in_tilemap(tilemap):
+
+    tilemap_errors = {}
+    metafile = tilemap + ".meta"
+
+    with open(metafile, 'r') as fd:
+        for line in fd:
+            result = parse_line1(line);
+            if not result:
+                continue
+
+            [tile_layer_name, tile_id] = result
+            
+            if tile_layer_name in tilemap_errors:
+                tilemap_errors[tile_layer_name].append(tile_id)
+            else:
+                tilemap_errors[tile_layer_name] = [tile_id]
+            
+    return tilemap_errors
+
+def get_csv(line):
+    stripped = line.strip()
+    csv = stripped.split(",")
+
+    if stripped.endswith(","):
+        csv.pop()
+
+    for value in csv:
+        if not value.isdecimal():
+            return False
+
+    if len(csv) == 0:
+        return False
+
+    return csv
+
+def fix_line(line, tile_layer_name, tilemap_errors):
+
+    # Expected format:
+    #  <layer id="336" name="Sea" width="60" height="27">
+    if "<layer " in line and ">" in line:
+        prefix = 'name="'
+        postfix = '"'
+        start=line.find(prefix) + len(prefix)
+        end=line.find(postfix, start)
+        tile_layer_name = line[start:end]
+        return line, tile_layer_name
+
+    if not tile_layer_name in tilemap_errors:
+        return line, tile_layer_name
+
+    tile_ids = get_csv(line)
+    if not tile_ids:
+        return line, tile_layer_name
+
+    for i, tile_id in enumerate(tile_ids):
+        if tile_id in tilemap_errors[tile_layer_name]:
+            tile_ids[i] = "0"
+
+    fixed_line = ",".join(tile_ids)
+
+    if line.strip().endswith(","):
+        fixed_line += ","
+
+    return fixed_line + "\n", tile_layer_name
+
+def correct_tilemap_errors(tilemap):
+
+    tilemap_errors = get_errors_in_tilemap(tilemap)
+    tile_layer_name = ""
+    new_file = ""
+
+    with open(tilemap, 'r') as fd:
+        for line in fd:
+            fixed_line, tile_layer_name = fix_line(line, tile_layer_name, tilemap_errors)
+
+            new_file += fixed_line
+
+    text_file = open(tilemap, "w")
+    text_file.write(new_file)
+    text_file.close()
+
+correct_tilemap_errors(' '.join(sys.argv[1:]))

elexis1 avatar Mar 06 '21 23:03 elexis1

Some more insights on the topic:

In many cases, the default ST2U sprite atlas is useless.

(1) Single image tilesheet: For example in every case where a tilesheet is built from a single image. Then ST2U creates a reference to the original Texture2D if the atlas option is disabled, or it creates a clone of the Texture2D but for no benefit whatsoever while driving up the build size. So I suspect this situation could be detected by code and ST2U could never create an atlas in such a situation.

(2) Prefab replacement collection: At least in our project we rely heavily on prefab replacements because that's the only way we can get the Z Sorting right while also having correct sprite pivot point. And units with code also obviously need to use prefab replacements. So we have many spritesheets that exclusively contain prefab replacements. Since the prefab replacements SpriteRenderers do not refer to the Sprite Atlas texture generated by ST2U, the atlas is useless waste in such a situation and the generation should be disabled. It would be good to recommend that in some ST2U tutorial to newcomers who only notice this issue when it might be too late (for example if the build is 500MB larger because of magenta sprte atlases :-P).

So in fact in our project there was not a single ST2U Sprite Atlas that was useful.

Then the next question arises. How do we optimize the rendering performance using custom Sprite Atlases, in particular when we use ST2U?

The objects that are Prefab Replacements should have their textures put on custom Sprite Atlases, grouped in some logical way. https://docs.unity3d.com/Manual/SpriteAtlasWorkflow.html

The ST2U objects and tiles that are not Prefab Replacements should be put into custom Sprite Atlases too if one wants to optimize using Sprite Atlases. (Using the ST2U sprite atlases in that situation won't be useful since it's the same as using the single Texture2D, as mentioned.)

Using a custom Sprite Atlas for such ST2U tiles and non-prefab-replacement objects has the advantage that we can combine multiple spritesheets into a single atlas. Not only that,

importantly the Unity Sprite Atlas also supports Tight Packing, Rotation and Padding size. ST2U does not actually create a "Sprite Atlas", but only a Texture2D that the ST2U sprites refer to.

After we patched ST2U to expose these tiles and rearranging our filestructure, we ended up with being able to fit all project assets into 3 Sprite Atlases, whereas before we had created 20 atlases and none of them were even useful or used!

The first problem we had to patch out of ST2U is that ST2U hides the Tiles that it creates. This makes it impossible to create a Sprite Atlas that refers to these textures!

+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/AtlasBuilder.cs
@@ -223,3 +223,4 @@ namespace SuperTiled2Unity.Editor
                 sprite.name = spriteName;
-                sprite.hideFlags = HideFlags.HideInHierarchy;
+                /// Don't hide these Sprites, because we want to be able to create a custom Sprite Atlas with it!
+                /// sprite.hideFlags = HideFlags.HideInHierarchy;
                 m_TiledAssetImporter.SuperImportContext.AddObjectToAsset(spriteName, sprite);

This finally allows us to include those tiles in a more efficient Sprite Atlas.

However a Sprite Atlas should also be minimal, so ST2U must be patched to discard any tiles that are useless.

Minimum Alpha Import Setting: Here we come back to the original topic - excluding the 100% invisible tiles, for which I uploaded some hackish patch above. But why stop at 100% invisible tiles? After we have removed the hiding of the tiles, we can see the individual tiles in the Atlas and if we view all of them individually by selection, we can find (depending on the artwork you use) tiles that only contain pixels that are 0% to 20% alpha - i.e. for all intends and purposes invisible to the human eye.

For that reason I have added a "Minimum Alpha" setting that discards all tiles that have no pixel above the given alpha value and reduced the tilecount for one tilesheet by 10%. Such "practically invisible" tiles can occur when artists paint with a semi transparent brush that fades out to the sides, so it doesn't seem uncommon.

Another type of "practically invisible" tiles is when there are less than X "practically visible" pixels on a tile. For example if a Tile only has 1 to 4 visible pixels, its still a practically invisible tile. So we can add another setting to exclude tiles that have so few visible pixels. I have not coded that because I don't want to remove even 2-4 important pixels (false positives).

Exclude prefab replacement sprites: We can furhter decrease the amount of uselessly generated ST2U tiles by not generating tiles for prefab replacements. In the current code, you hide the Sprites created by ST2U, so noone can currently refer to these tiles in prefabs. It's technically possible to do that after above patch, but it's probably fragile and thus not advisable to do, since the GUIDs might change if someone changed the original sprite image (inserting or removing tiles).

In our codebase, we have patched out generation of Sprites for Prefab Replacements and thus minimized the generated sprite and tile count by several hundred. So then we can simply assign a folder with *.tsx files to a Sprite Atlas and we will get 100% useful tiles and 100% efficient atlases (in terms of tight packing).

Given how many sprites are needed to be drawn if one wants to create a good game with even a mediocre amount of depth, it is absolutely essential to use optimized Sprite Atlases. Therefore this is an important issue for people who want to use ST2U to create such a game!

elexis1 avatar Mar 12 '21 22:03 elexis1

Here the patch to exclude "Prefab Replacement tiles".

The patch assumes that if a "m_Type" Custom Property is specifed, that it will be a prefab replacement. Our custom importer also enforces this and shows us an error message, so that we get notified if we messed up a typename and thus find errors! We can still use a different property if we want to store some random type string...

JFYI we also use prefab replacements on Tiles, which leaves the patch different than it would be if one supports only Prefab Replacements on Object Layers. The patch is certainly not ideal, but perhaps it can help someone:

commit e284656d687f59fe9fe9b26d2b4d9b22e02d37c4
Author: Alexander Heinsius <[email protected]>
Date:   Tue Mar 9 15:57:08 2021 +0100

    SuperTiled2Unity: Do not create sprites for prefab replacements.
    
    Prefab Replacements do not reference ST2U sprites.
    396 sprites less.
    This helps with creating a correct and minimal Sprite Atlas and improves tilesheet import times.
    Without this commit, the redundant sprites makes it hard to find out which of the sprites we need to include in a SpriteAtlase.

diff --git a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/AtlasBuilder.cs b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/AtlasBuilder.cs
index 2b868d97..a5cbacdc 100644
--- a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/AtlasBuilder.cs
+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/AtlasBuilder.cs
@@ -18,6 +18,7 @@ namespace SuperTiled2Unity.Editor
             public int Index;
             public Texture2D SourceTexture;
             public Rect SourceRectangle;
+            public bool IsPrefabReplacement;
 
             public Texture2D AtlasTexture;
             public Rect AtlasRectangle;
@@ -62,9 +63,9 @@ namespace SuperTiled2Unity.Editor
             m_TilesetScript = tilesetScript;
         }
 
-        public void AddTile(int index, Texture2D texSource, Rect rcSource)
+        public void AddTile(int index, Texture2D texSource, Rect rcSource, bool isPrefabReplacement)
         {
-            var atlasTile = new AtlasTile() { Index = index, SourceTexture = texSource, SourceRectangle = rcSource };
+            var atlasTile = new AtlasTile() { Index = index, SourceTexture = texSource, SourceRectangle = rcSource, IsPrefabReplacement = isPrefabReplacement };
             m_AtlasTiles.Add(atlasTile);
         }
 
@@ -217,13 +218,18 @@ namespace SuperTiled2Unity.Editor
                 string spriteName = string.Format("Sprite_{0}_{1}", m_TilesetScript.name, t.Index + 1);
                 string tileName = string.Format("Tile_{0}_{1}", m_TilesetScript.name, t.Index + 1);
 
-                // Create the sprite with the anchor at (0, 0)
-                var sprite = Sprite.Create(t.PreferredTexture2D, t.PreferredRectangle, Vector2.zero, m_TiledAssetImporter.SuperImportContext.Settings.PixelsPerUnit);
+                Sprite sprite = null;
+                if (!t.IsPrefabReplacement)
+                {
+                    // Create the sprite with the anchor at (0, 0)
+                    sprite = Sprite.Create(t.PreferredTexture2D, t.PreferredRectangle, Vector2.zero, m_TiledAssetImporter.SuperImportContext.Settings.PixelsPerUnit);
+
+                    sprite.name = spriteName;
 
-                sprite.name = spriteName;
-                /// Don't hide these Sprites, because we want to be able to create a custom Sprite Atlas with it!
-                /// sprite.hideFlags = HideFlags.HideInHierarchy;
-                m_TiledAssetImporter.SuperImportContext.AddObjectToAsset(spriteName, sprite);
+                    /// Don't hide these Sprites, because we want to be able to create a custom Sprite Atlas with it!
+                    /// sprite.hideFlags = HideFlags.HideInHierarchy;
+                    m_TiledAssetImporter.SuperImportContext.AddObjectToAsset(spriteName, sprite);
+                }
 
                 // Create the tile that uses the sprite
                 var tile = ScriptableObject.CreateInstance<SuperTile>();
diff --git a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs
index abe5762a..c9dbcfb5 100644
--- a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs
+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Importers/TmxAssetImporter.TileLayer.cs
@@ -294,8 +294,12 @@ namespace SuperTiled2Unity.Editor
                 }
             }
 
-            Vector3 translate, rotate, scale;
-            tile.GetTRS(tileId.FlipFlags, m_MapComponent.m_Orientation, out translate, out rotate, out scale);
+            Vector3 translate = Vector3.zero;
+            Vector3 rotate = Vector3.zero;
+            Vector3 scale = Vector3.one;
+
+            if (tile.m_Sprite != null)
+                tile.GetTRS(tileId.FlipFlags, m_MapComponent.m_Orientation, out translate, out rotate, out scale);
 
             var cellPos = superMap.CellPositionToLocalPosition(pos3.x, pos3.y, SuperImportContext);
             translate.x += cellPos.x;
@@ -306,6 +310,9 @@ namespace SuperTiled2Unity.Editor
             goTRS.transform.localRotation = Quaternion.Euler(rotate);
             goTRS.transform.localScale = scale;
 
+            if (tile.m_Sprite == null)
+                return;
+
             // Add the sprite renderer component
             var renderer = goTRS.AddComponent<SpriteRenderer>();
             renderer.sprite = tile.m_Sprite;
diff --git a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs
index 2d9abdcb..d6113f18 100644
--- a/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs
+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/Editor/Loaders/TilesetLoader.cs
@@ -82,7 +82,6 @@ namespace SuperTiled2Unity.Editor
             // There are two ways that our collection of tiles can be created from images
             // 1) From one image broken down into parts (many tiles in one image)
             // 2) From a collection of images (one tile per image)
-
             var atlas = new AtlasBuilder(m_Importer, m_UseSpriteAtlas, (int)m_AtlasWidth, (int)m_AtlasHeight, m_TilesetScript);
 
             if (xTileset.Element("image") != null)
@@ -171,8 +170,18 @@ namespace SuperTiled2Unity.Editor
                 int pixel_count = Get_Visible_Pixel_Count(tex2d_tmp, ref rcSource);
 
                 /// TODO: Add option to filter tiles with less than N visible pixels
-                if (pixel_count > 0)
-                    atlas.AddTile(i, tex2d, rcSource);
+                if (pixel_count == 0)
+                    continue;
+
+                bool isPrefabReplacement = false;
+                foreach (var xTile in xTileset.Elements("tile"))
+                    if (xTile.GetAttributeAs<int>("id") == i)
+                    {
+                        isPrefabReplacement = IsPrefabReplacement(xTile);
+                        break;
+                    }
+
+                atlas.AddTile(i, tex2d, rcSource, isPrefabReplacement);
             }
 
             UnityEngine.Object.DestroyImmediate(tex2d_tmp);
@@ -246,11 +255,16 @@ namespace SuperTiled2Unity.Editor
                     }
 
                     var rcSource = new Rect(0, 0, tex2d.width, tex2d.height);
-                    atlas.AddTile(tileIndex, tex2d, rcSource);
+                    atlas.AddTile(tileIndex, tex2d, rcSource, IsPrefabReplacement(xTile));
                 }
             }
         }
 
+        private bool IsPrefabReplacement(XElement xTile)
+        {
+            return !System.String.IsNullOrEmpty(xTile.GetAttributeAs("type", ""));
+        }
+
         private void ProcessTileElements(XElement xTileset)
         {
             // Additional processing on tiles
diff --git a/Assets/Others/Extras/SuperTiled2Unity/Scripts/SuperTile.cs b/Assets/Others/Extras/SuperTiled2Unity/Scripts/SuperTile.cs
index 088a04c0..349d6f15 100644
--- a/Assets/Others/Extras/SuperTiled2Unity/Scripts/SuperTile.cs
+++ b/Assets/Others/Extras/SuperTiled2Unity/Scripts/SuperTile.cs
@@ -46,6 +46,9 @@ namespace SuperTiled2Unity
 
         public Matrix4x4 GetTransformMatrix(FlipFlags ff, MapOrientation orientation)
         {
+            if (m_Sprite == null)
+                return Matrix4x4.identity;
+
             var inversePPU = 1.0f / m_Sprite.pixelsPerUnit;
             var offset = new Vector2(m_TileOffsetX * inversePPU, -m_TileOffsetY * inversePPU);
             var matOffset = Matrix4x4.Translate(offset);

elexis1 avatar Mar 12 '21 22:03 elexis1

Regarding the python script hack from the first uploaded patch:

+# 1. Save the ST2U errors to the meta file by for example:
+# - Changing edgesPerEllipse from 33 to 32 so that the Apply button becomes enabled, and clicking Apply.

We have implemented a "Reserialize" button in #172 to avoid having to change the setting all the time only to save the meta file again.

elexis1 avatar Mar 13 '21 00:03 elexis1