Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Feedback Tilemap tool needs a major revamp! (kind of rant)

Discussion in '2D' started by Bizio, Sep 28, 2020.

  1. Bizio

    Bizio

    Joined:
    Apr 3, 2015
    Posts:
    4
    After 2 years of working on my 2D game and using this tool I've decided to create this post as my feedback to devs who 'work' on this tool. There's never been updates/fixes to many major things that are broken since release. Granted, there are tilemap extras as extension to default tilemap system that are being worked on but none of the added things adress any issues with the base tool that is still on version 1.0.0! It was NEVER updated since it released 3 years ago!

    1. Z position is completely broken
    Tiles placed on values below 0 should render behind tiles placed on Z 0, increasing should render them in front. What actually happens is totaly random, sometimes it works, sometimes it doesn't. Decreasing or increasing z value makes the tile render in front or back depending on a tile I chose, sometimes they are rendered based on what we placed first (?). Example: place tile on Z 0, then on Z 1 - it renders in front of Z 0, then on Z 2 - it renders in front of 0 and 1, go back to Z 1 and replace the tile - now tile placed on Z 1 renders in front of Z 0 and 2. Completely inconsistent behavior, just play around with it, it will sure break at some point.

    2. No way of telling on which Z you placed the tile
    If you want to erase a tile you placed on Z 4 for example and you forgot - you have to manually go throught every Z you could've used and just try and repeat (option 'remove on all Z positions' would be nice)

    3. Tiles mirrored vertically or horizontally (shift + [ or ]) don't get highlighed when focusing on tilemap
    It makes it difficult to find a tilemap which they are drawn on. (imagine placing it also on different Z, and trying to remove it later.. nightmare)

    4. No way to disable palletes in pallete dropdown...
    ...when you don't need them - making it difficut too switch between ones you actually use. I have 20 palletes but use no more than 3 at the same time and searching for them in this big list is kind of annoying.

    5. Unintuitive and lackluster API for making custom brushes and tiles
    Bad Method and Property naming and absence of essential references ex.: GetTileData method fires whenever you hover over tile with a mouse, no reference of currently chosen tilemap, no way of telling if tile is placed or not. No clear seperation between calls on Tilemap in the scene and Tilemap in pallete window (changing pattele 'reloads' tilemap in pallete window triggering unwanted behaviour on actual scene tilemap). Method StartUp fires then you place a tile (fair enough) but doesn't when you replace it, you have to remove the tile first and many more.

    6. Order of Tilemaps in active tilemap dropdown is completely random
    It's not alphabetical nor it's order from hierarchy, just random. I downloaded custom script from the internet for alphabetical order that implemented it as... custom brush. That's the kind of stretching you have to do to get things done.
    7. No way of saving presets on GameObject brush and Random brush
    No way of placing GameObjects in the pallete for easy access so you can pick and paint, you have to manualy add prefab to the field in Pallete window whenever you want to paint game objects. Yes there is collider tile that lets you add GameObject to the tile and paint it but it's obsolete and has no settings like GameObject Brush (offset, scale etc.). Same for random brush, no presets.

    8. Advanced Rule Override Tile is broken
    Picking one rule to override picks all of them, and trying to replace a sprite for one rule replaces the sprite in every rule. Unusable!

    9. No Tile for paiting tiles combined with colliders (basic functionality)
    No tile that would allow to attach a collider or GameObject to a sprite making it easy to paint world elements with premade collisions removing a need for manually collidering out your level after drawing it. (I actually custom implemented this kind of tile myself and it's mega usefull, but the way it works is super scuffed and you have to remember bunch of things to do/not do to make it work properly, again related to terrible API... and I just can't see any way to make it better)

    The are more things I could mention if I was more picky but those are the main things I'm currently struggling with. Tilemap tool has a very good potential to be an essential tool for every 2D game developer but seeing the low amount of focus it gets from unity team is really a shame. It very much feels like an abandoned extension that nobody really wants to work on cause of the way it's spaghetti coded. I might be totally wrong and someone is out there actually trying to fix the issues but it just doesn't really feel like (still on version 1.0.0 after 3 years). I hope to see an improvement in the near future.
     
    Last edited: Sep 28, 2020
  2. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    455
    Perhaps one thing that could help here would be shortcuts.
    You could use a ScriptableObject singleton that has references to the 3 palettes you're using.

    Something like this (untested):
    Code (CSharp):
    1.     [MenuItem("Shortcuts/Open Palette 1 &c")] // ALT+C
    2.     public static void OpenPalette1()
    3.     {
    4.         EditorApplication.ExecuteMenuItem("Window/2D/Tile Palette");
    5.         GridPaintingState.palette = Instance.palette1;
    6.     }
    There's also a GridPaintingState.palettes which only has a getter but you could search that by string/name instead of using a singleton to get a reference to the correct palette.

    Likewise, GridPaintingState.scenePaintTarget is likely (untested) the Tilemap, so you could set up more shortcuts for switching tilemaps. If that's correct, then not only would you be able to fix one headache you're experiencing, but have a better workflow than even if tilemaps were listed alphabetically by virtue of convenient shortcuts.
     
  3. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    595
    Thank you for your feedback!

    Do you have a example or a reproduction project for this which you could share with us? Some setup is required to make this work for TilemapRenderer Chunk mode rendering due to the batching process for Chunk mode rendering. For sorting to work correctly, all the Sprites in the Tilemap should belong to the same Texture or be atlassed together.

    This is fair and we need to find a good way to note the Z position of the Tile in the SceneView. We can work on the option to remove all Z positions as well.

    I am not sure what you would like to see for this as rotated or mirrored Tiles as treated normally like a Tile. Is there a particular use-case for highlighting these specific Tiles?

    We could add a way to prevent the Palette from showing up in the dropdown in the Palette Asset, but this results in the similar issue where searching for the Palette you want to enable/disable in the Project Window becomes annoying. Perhaps you could share the situations where you would only like to see particular Palettes in the dropdown, eg. you are using a particular brush or painting on a particular Tilemap?

    GetTileData is fired whenever a Tile is placed, changed or removed on the Tilemap and comes with the Tilemap and the position on which it is modified as arguments. The arguments can be used to identify whether the Palette, the target Tilemap or something else has changed if required, thus separating calls between the Tilemap in the scene and in the Tile Palette. This call should be used to supply the Tilemap with the required TileData to drive the Tilemap based on the Tile. Do you have a use case for doing something else that you could share with us? Could you share the situation where changing the Tile Palette causes "unwanted behaviour" in the Tilemap in the SceneView?

    The StartUp method fires when you place or change a Tile on the Tilemap. Changing the Tile to the same Tile does not count for this. Is that the situation for you?


    You can change the order for this in Preferences:
    upload_2020-9-29_11-34-9.png
    If you need a specific order, you can create an
    IComparer<GameObject>
    and add the
    GridPaintSorting
    attribute to have it show up in the Preferences window.

    You can pick Tiles from the Tile Palette with the Random Brush or create asset instances of the Random Brush to store specific random sets. With Unity 2020.1, you can add GameObjects/Prefab Instances to the Tile Palette window, which will allow you to pick GameObjects using the GameObject brush from the Tile Palette window. This also stores the offset, rotation and scale based on the transform of the GameObject when picked from the Tile Palette.

    I am not sure what a "collider tile" is, perhaps you can share that with us?

    This looks like a bug for the Advanced Rule Override Tile, we will check it out!

    I am not sure what you mean by this. The Tilemap is able to generate colliders with the TilemapCollider2D component based on the Sprite on each Tile of the Tilemap. If you are not using Unity's Physics2D system and some other system, do share and elaborate more about it and how it should tie in with the Tilemap system, and we will see how we can help with that.

    Please do not treat the lack of changes in the version number as validation that the Tilemap has not been changed or received updates. This is just a quirk for this feature, and the changes and updates come with each iteration of the Unity Editor (whose changes can be found in the changelog for the Editor).

    Also, if you have more things that you want to share, do let us know!
     
    Bizio and JoNax97 like this.
  4. Bizio

    Bizio

    Joined:
    Apr 3, 2015
    Posts:
    4
    Thank you for the response!

    After playing around with it more - yes, it turns out it works as intended, but only if I use tiles from the same sprite sheet which is a bummer. For more complex scenery you WILL use overlaping tiles to give a sense of depth. And since many tiles come from many artists and are scattered between many files to keep things organized that makes it harder. So is the right solution to keep ALL tiles in the same huge image file that you will use for scene builing?

    Another small problem I found (being kinda picky) The tile appears in front always if we just hover over. This is less of an issue but still would be nice for it to render properly before placing it so we can see easier how the end result is gonna look like.

    Good to hear <3


    I'm talking about Focus thing in the scene. It makes it hard to find on which Tilemap you placed certain mirrored tile.



    For example if I'm working on certain level. If I finished Forest level using my forest palettes and now I'm working on Graveyard level using my graveyard palettes I don't really need to see the forest palettes for the time being. To achieve this I just remove them from the project which works :D but it's certainly more annoying than pressing "hide pallete in pallete window" button. I could also place all graveyard tiles in the same palette and never change it but I like to keep things organised (ex. Graveyard_Foliage, Graveyard_Ground etc.). And if someone likes to see every palette all the time they would simply not press the button. More tools for customized workflow pls :).


    My sprites for collidable world objects have custom colliders for them. Auto generated colliders by TilemapCollider2D are innacurate if you want to give a sense of depth to your game (ex. if you are going for angled topdown style of game). Consider a tree, you don't want to generate collider on the whole tree right? only on the trunk part so you can walk around it and the tree appears in front while you are behind it (achieved with sorting).


    It would be nice if we had a tile that let's us create pairs of Sprite-GameObject and spawn both at the same time when we place it! so we don't have to collider out level manually after drawing it :D. I actually tried creating custom tile with that behaviour myself and it turned out... 'it could be better but it works'. That gets me to my next point.


    Here's my code of custom tile I talked about previously

    Code (CSharp):
    1. [CreateAssetMenu(menuName = "Tiles/Good Collider Tile")]
    2. public class ColliderTile : TileBase
    3. {
    4.     Sprite m_Sprite;
    5.     public ColTile[] tiles;
    6.  
    7.  
    8.     bool wasPlaced = false;
    9.     Vector3Int currentLocation;
    10.     int index = 0;
    11.  
    12.     public override void GetTileData(Vector3Int location, ITilemap tilemap, ref TileData tileData)
    13.     {
    14.         base.GetTileData(location, tilemap, ref tileData);
    15.  
    16.         if(tiles == null)
    17.             return;
    18.  
    19.         if(currentLocation != location)
    20.         {
    21.             currentLocation = location;
    22.  
    23.             if (TilemapHelper.instance != null)
    24.             {
    25.                 wasPlaced = TilemapHelper.instance.tilemap.HasTile(location);
    26.             }
    27.         }
    28.  
    29.         if(tiles.Length == 1)
    30.         {
    31.             //set first tile if only one element
    32.  
    33.             index = 0;
    34.             tileData.sprite = tiles[index].sprite;
    35.         }
    36.         else if ((tiles != null) && (tiles.Length > 0))
    37.         {
    38.             //randomize tile if more elements
    39.  
    40.             long hash = location.x;
    41.             hash = (hash + 0xabcd1234) + (hash << 15);
    42.             hash = (hash + 0x0987efab) ^ (hash >> 11);
    43.             hash ^= location.y;
    44.             hash = (hash + 0x46ac12fd) + (hash << 7);
    45.             hash = (hash + 0xbe9730af) ^ (hash << 11);
    46.             Random.InitState((int)hash);
    47.             index = (int)(tiles.Length * Random.value);
    48.  
    49.             tileData.sprite = tiles[index].sprite;
    50.         }
    51.     }
    52.  
    53. #if UNITY_EDITOR
    54.     public override bool StartUp(Vector3Int location, ITilemap tilemap, GameObject go)
    55.     {
    56.         if(!wasPlaced && TilemapHelper.instance != null && TilemapHelper.instance.allowCollidersPlace)
    57.         {      
    58.             if (tiles[index].prefab != null)
    59.             {
    60.                 GameObject GO = PrefabUtility.InstantiatePrefab(tiles[index].prefab, TilemapHelper.instance.colliderParent.transform) as GameObject;
    61.  
    62.                 GO.transform.localPosition = location;
    63.             }
    64.         }
    65.  
    66.         return true;
    67.     }
    68. #endif
    69.  
    70.     [System.Serializable]
    71.     public struct ColTile
    72.     {
    73.         public Sprite sprite;
    74.         public GameObject prefab;
    75.     }
    76. }
    First of all you said GetTileData call provided me the Tilemap I'm working with but it's not 'Tilemap', it's "ITilemap" interface that doesn't have much available in it. Also GetTileData is called every time I hover different tile with my mouse.

    Code (CSharp):
    1.         if(currentLocation != location)
    2.         {
    3.             currentLocation = location;
    4.  
    5.             if (TilemapHelper.instance != null)
    6.             {
    7.                 wasPlaced = TilemapHelper.instance.tilemap.HasTile(location);
    8.             }
    9.         }
    I'm accesing the Tilemap object throught external class that holds reference to it (that I have to change manually) cause ITilemap doesn't have HasTile method. Another problem is that HasTile doesn't return information about placed tiles but PREVIEW tiles so it means when I place a tile on the empty square - HasTile method returns true before I actually 'assign' tile to that square. To go around it I store this information in wasPlaced before I assing tileData.sprite when spawning preview and I make sure It doesn't override it by checking currentLocation because when I click and place the tile the GetTileData method fires again (it would override my wasPlaced to true, cause it already assinged preview tile). And then I use this information to decide if I should spawn GameObject in StartUp that fires when I actually place a tile.

    Also another BIG problem is that StartUp doesn't only fire when you place or remove a tile, it also fires when you hover over already placed tiles, so that led me into spawning multiple of the same prefabs in the same positions by just hovering over tiles with a mouse. Very random behaviour.

    Next thing is that StartUp Fires also for every placed tile in the Tile Palette window when you change palette causing it to spawn GameObjects on the Tilemap in the scene if im not carefull and don't disable it before. (Again related to getting reference to Tilemap from external class).

    What's bothering me the most is weirdness of not seperating calls from just hovering over tiles and placing them. From the code it looks like it's the same thing, I don't really know how it's done internally but I find it confusing.


    I should've known that. Thank you very much


    I'm using 2019 LTS, but It's great hearing it gets improved in 2020, well done.


    <3

    Thank you for all the replies. I appreciate it.
     
    Last edited: Sep 30, 2020
  5. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    595
    Well, I can't say it is the right solution for you. I would recommend that you check out the Sprite Atlas feature (https://docs.unity3d.com/Manual/class-SpriteAtlas.html). This helps to generate an image file that contains all the Sprites that you are using (as long as they can fit into the specified resolution of the Sprite Atlas). Hopefully that will help you with managing your Sprite assets for this! The plus points would be that it would help with the sorting and reduce the number of draw calls when rendering due to dynamic batching. The negative points would be that it only works in Play mode and there is a limit of Sprites you can place in a Texture of the Sprite Atlas.

    We will think about that! Currently, the Previews are always drawn on top, so that it is easier to see that something has changed.

    Thanks for clarifying! This is likely an error in the Z facing for the shader used for focusing the Renderer.

    Sounds good! We will check it out! It sounds like it would be helpful to add a toggle to the Palette asset to enable/disable it from showing up in the dropdown, and add a filter for the dropdown based on the Brush used.

    I assume you are using the Unity Physics2D system for collision (using Collider2Ds). Perhaps you could check out the Custom Physics Shape for Sprites (https://docs.unity3d.com/Manual/CustomPhysicsShape.html)? This will allow you to specify your own Physics Shape for the Tree trunk when used with the TilemapCollider2D (or PolygonCollider2D). If that works out for you, that may save you from requiring a separate GameObject for collision purposes.

    If not, for your custom Tile, I would suggest using
    ITilemap.GetComponent<TIlemap>()
    to access the actual Tilemap if necessary. This will allow you to access all the properties and methods of the Tilemap, which will help to identify the actions that you require.

    The reason why you are getting multiple calls for StartUp and GetTileData is due to the previewing system, which gets called for each preview every interacting on the SceneView. This can be a lot of handle. If this could be differentiated better using the ITilemap, perhaps that would help reduce the confusion?

    I have added a branch at https://github.com/Unity-Technologies/2d-extras/tree/advancedruleoverride, which should fix this issue.

    If you have more feedback or suggestions, do let us know!
     
  6. Bizio

    Bizio

    Joined:
    Apr 3, 2015
    Posts:
    4
    Thanks again, It's nice to see you adressing the issues, looking forward for fixes <3. Also There's been a lot of problems I had from just not knowing unity enough like the sprite atlases so thanks for clarifying ways to get things done.


    Yes, one simple boolean somewhere would make it 10 times easier. bool false -> This is a preview call, bool true -> this is a tile placed call, simple. it could be included in ITilemap or as parameter, whatever is more convienient!

    Also changing HasTile to check actual placed tiles not the preview ones or adding bool param like includePreview or something.

    And StartUp firing when you hover over placed tiles, I'm pretty sure its a bug. There is no method currently that fires only when we place or modify a tile - needed for some setup.

    I just tried Unity 2020 and it looks like there is no more Random Tile asset I'm widely using in Unity 2019LTS. Did that get replaced with Random Brush cause if that's the case it's a shame cause is seems like inferior system. Creating random tile presets and placing them in tile palette is so much more convenient than creating seperate brushes for every random tile preset. Why not have both? or even better!!! Add a button in random brush tool to generate Random Tile asset from currently selected tiles so we can place it in our Pallete for easy access. It just makes sense.

    Best regards.
     
    Last edited: Oct 1, 2020
  7. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    595
    Yeap, we will review this and check how better to identify previews!

    For this particular case, we have added the features that are more commonly used from 2020.1 onwards. This reduces the number of extraneous items you see in the Editor. That being said, you can find all of the features at https://github.com/Unity-Technologies/2d-extras, and pick and choose the ones that you need. Hope that helps!
     
  8. Bizio

    Bizio

    Joined:
    Apr 3, 2015
    Posts:
    4
    Thanks, waiting for update :) I think that's all from me
     
unityunity