Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Prefabs and Tiles with tilemaps - leaves sprite ghost behind? Rule Tiles, Custom Tiles

Discussion in '2D' started by GameDevSA, Sep 5, 2023.

  1. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
    I have been having a lot of issues with trying to get prefabs working with tiles. Also, I'm using Unity 2019.4, as I don't want to upgrade and break my game.



    This is the issue I am currently having. If I use Rule Tiles, this happens The game object leaves a ghost sprite behind.

    I got a custom tile script working where I could place prefabs on the tilemap, by setting tileData.gameObject, but there was no preview in the tile palette. To get a preview in the tile palette, I had to set tileDate.sprite. But, that seems to then place both a sprite and a game object in the tilemap, which is why you get the weird behaviour in the image above.

    Code (CSharp):
    1.     public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
    2.     {
    3.         base.GetTileData(position, tilemap, ref tileData);
    4.  
    5.         // Set the tile's game object to your tilePrefab.
    6.         tileData.gameObject = tilePrefab;
    7.  
    8.         // Set the tile's sprite for the Tile Palette to the preview sprite.
    9.         tileData.sprite = previewSprite;
    10.     }
     

    Attached Files:

    Last edited: Sep 6, 2023
  2. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
    I had this idea to try and show the sprite in the editor, and hide it at runtime, but modifying it’s colour:

    tileData.color = new Color(0,0,0,0);

    That will hide the sprite, but it hides it in the tilemap and the tilepalette. So, I really only want that code to run at runtime, but not in the editor. I tried doing this:

    Code (CSharp):
    1.     public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
    2.     {
    3.         base.GetTileData(position, tilemap, ref tileData);
    4.  
    5.         tileData.gameObject = tilePrefab;
    6.  
    7.         // Set the tile's sprite for the Tile Palette to the preview sprite
    8.         tileData.sprite = previewSprite;
    9.         //tileData.color = new Color(0f, 0f, 0f, 0f);
    10.  
    11.         // Check if the game is running (not in the editor)
    12.         if (!Application.isPlaying)
    13.         {
    14.             tileData.color = new Color(1,1,1,1); // show the sprite in the editor
    15.         }
    16.         else
    17.         {
    18.             tileData.color = new Color(0,0,0,0); // hide the sprite during gameplay
    19.         }
    20.     }
    21.  
    22.     public override void RefreshTile(Vector3Int position, ITilemap tilemap)
    23.     {
    24.         tilemap.RefreshTile(position);
    25.     }
    But, it doesn’t seem to work. Does anyone see a better way to handle this? I don’t fully understand how this Unity class works, or the RefreshTile method. Application.Playing seems like a good idea, but it appears not to function as I would expect in this class. Thanks.
     
    Last edited: Sep 6, 2023
  3. sildeflask

    sildeflask

    Joined:
    Aug 16, 2023
    Posts:
    142
    make the game object set a null tile on its position on Awake()
     
  4. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
    So, the idea of a separate script to trigger some code on Awake() or Start() makes sense. But, how do I get a reference to that tile from another script? I got a bit stuck there. I'm not really sure how to get to the tile data, I'm really unfamiliar with how their Tilemap related classes are set up.

    It looks like there's a GetTile() function in TileMap, would I use that? https://docs.unity3d.com/ScriptReference/Tilemaps.Tilemap.GetTile.html

    But not really sure how to even get a reference to the tilemap, short of manually dragging it onto a public variable which, I don't want to do that for every object on every level. Below is an example of my starting point, assuming this script is attached to my hero or any other game object placed on a tilemap. Although even then, that returns a "TileBase", not sure how I'd get from that to "TileData" which is where the sprite is stored that I want to hide. Any further help appreciated, thanks.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Tilemaps;
    3.  
    4. public class HideTilemapSprite : MonoBehaviour
    5. {
    6.     Tilemap tilemap; // Reference to the Tilemap containing PrefabTiles
    7.  
    8.     void Start()
    9.     {
    10.    
    11.     }
    12.  
    13.     void HideSprite()
    14.     {
    15.         tilemap.GetSprite(Vector3Int.FloorToInt(gameObject.transform.position));
    16.     }
    17. }
     
  5. sildeflask

    sildeflask

    Joined:
    Aug 16, 2023
    Posts:
    142
    i think that you have a fundamental misunderstanding of how tilemaps are working, ill try to explain

    When you set a tile, the sprite of that tile is drawn on a mesh. At that point, it will never change, even if you change the tile asset, change sprite, color, even delete it, the tilemap will still display that sprite.

    The only way to change the sprite on that tilemap is to either refresh the position, by refreshing or setting a new tile there.

    Therefore, you dont need any kind of reference to the original tile because the original tile doesnt contain any info about where it is set anyway.

    You only need to know the position that you want to redraw, and I assume thats the position where your gameobject currently is.

    So you should make a script for your game objects that get spawned by tiles, to access the tilemap and use, tilemap.settile( position, null);

    this will essentially "erase a tile" from the tilemap
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
    https://docs.unity3d.com/ScriptReference/Tilemaps.TileBase.GetTileData.html
    https://docs.unity3d.com/ScriptReference/Tilemaps.TileData.html

    If you have a TileBase reference then, let's assume it's a Tile type, you just cast it i.e.
    Code (CSharp):
    1. var myTile = myTileBase as Tile;
    2. if (myTile != null)
    3. {
    4.    var sprite = myTile.sprite;
    5.    ...
    6. }
    https://docs.unity3d.com/ScriptReference/Tilemaps.Tile-sprite.html
     
  7. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
    Thanks that's helpful to know, I haven't done much casting.
     
  8. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
    Right, I hadn't understood that it puts the sprite on a mesh. I had assumed they were separate tile objects that maintained their Tile information like position, etc.

    Your solution of:

    Code (CSharp):
    1. tilemap.settile( position, null);
    Sounds like it makes sense, but, I am still not sure how I get a reference to my tilemap. Could you please show me an example of how I could get a reference to the tilemap, and the correct position, from my separate game object? If you could please look at my coding example and tell me how I can get a reference to the correct tile map? I've put in comments about the parts I'm not sure about.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Tilemaps;
    3. using UnityEngine.UIElements;
    4.  
    5. public class HideTilemapSprite : MonoBehaviour
    6. {
    7.     Tilemap tilemap; // Reference to the Tilemap containing PrefabTiles
    8.  
    9.     void Start()
    10.     {
    11.        // tilemap = "I am not sure how to set this variable"
    12.     }
    13.  
    14.     void HideSprite()
    15.     {
    16.         // Have I set the position correctly?
    17.         tilemap.SetTile(Vector3Int.FloorToInt(gameObject.transform.position), null);      
    18.     }
    19. }
    20.  
    Also, for the sake of clarity, my scene has multiple tile maps, so I will be needing to get the right one, in this case, "Pieces":

    upload_2023-9-8_12-57-45.png
     
    Last edited: Sep 8, 2023
  9. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104


    Are you aware of a way to stop this happening, aside from me re-writing Unity's code and doing work arounds?

    Because it seems very odd that, to get a tile palette preview of your tile, you have to set the Tiles sprite value, but then if you set the game object value on that same tile, it ends up creating this scenario, where it places both a sprite and a game object on the tilemap. There should be some catch where, if the "Game Object" value is set, "Sprite" is only used to display a preview in the tile palette, but not to place a sprite on the tilemap.

    Because this is what was happening with Rule tiles, which is why I started trying to create my own custom workaround script.
     
  10. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
  11. sildeflask

    sildeflask

    Joined:
    Aug 16, 2023
    Posts:
    142
    there are multiple ways, for example you can just do

    Code (CSharp):
    1. public Tilemap tilemap;
    and drag it in the inspector

    or you can do
    Code (CSharp):
    1. void Start()
    2.     {
    3.        tilemap = GameObject.Find("Pieces").GetComponent<Tilemap>();
    4.     }

    also depending on your pixels per unit

    you probably have to used WorldToCell, on your transform, unless 1 world unit = 1 tilemap cell on your project

    make sure that the "z" of your position is also correct
     
  12. vonchor

    vonchor

    Joined:
    Jun 30, 2009
    Posts:
    238
    I had to handle this problem previously. BTW this sort of issue with the Palette isn't really a bug IMO: the Palette is a Tilemap so it acts exactly like a Tilemap.

    So in your GetTileData you can show the sprite in the Palette but if it's not the palette then do not show the sprite. Makes sense?

    You might notice that Animated tiles will sometimes animate in the Palette when the Editor is in Play mode. I use this same technique to affect GetTileAnimationData.

    What you need is something to detect if your tile is in the Palette or not. Here's some static class code from my TilePlus Toolkit project (free on asset store). Hope this helps you a bit.



    Code (CSharp):
    1.  
    2.  
    3. public const int PaletteTilemapLayer = 31;
    4.  
    5. /// <summary>
    6.         /// Overload for IsTilemapFromPalette(Tilemap)
    7.         /// </summary>
    8.         /// <param name="itilemap">An ITilemap instance</param>
    9.         /// <returns>true if tilemap is from the palette</returns>
    10.         public static bool IsTilemapFromPalette(ITilemap itilemap)
    11.         {
    12.             var component = itilemap.GetComponent<Tilemap>();
    13.             return component != null && IsTilemapFromPalette(component);
    14.         }
    15.      
    16.         /// <summary>
    17.         /// Is this tilemap actually the palette?
    18.         /// </summary>
    19.         /// <param name="tilemap">tilemap ref</param>
    20.         /// <returns>true if is from palette</returns>
    21.         /// <remarks>It shouldn't be this obtuse... </remarks>
    22.         public static bool IsTilemapFromPalette(Tilemap tilemap)
    23.         {
    24.             /*although it's tempting to use PrefabUtility.IsPartOfAnyPrefab() here
    25.               because the palette is actually a Prefab, it won't work because any
    26.               tilemap can be validly part of a prefab.
    27.  
    28.             aside from a Palette's name being Layer1, which could change,
    29.             the hideflags are different. A normal tilemap should never
    30.             have DontSave for flags.
    31.             Also, the tilemap's transform.parent's layer is set to 31. So check for that too.
    32.             Note: the tilemap within the Palette is inside a prefab. It's hide flags
    33.             are set to HideAndDontSave which is DontSave | NotEditable | HideInHierarchy
    34.             but when a palette prefab is opened in a prefab context, the tilemap created
    35.             for the prefab stage is set to DontSave. Since HideAndDontSave includes DontSave
    36.             its easier to use that. It's unlikely that a tilemap in a scene would have DontSave
    37.             as a flag. (Note that in 2021.2 this may not be true, so the "Layer1" check is still used)
    38.             */
    39.             //so the tilemap is a palette if the hideflags DontSave bit is set
    40.          
    41.             if (tilemap.name == "Layer1" ||
    42.                 (tilemap.hideFlags & HideFlags.DontSave) == HideFlags.DontSave)
    43.                 return true;
    44.  
    45.             var parent = tilemap.transform.parent;
    46.             if (parent == null) //should never happen since a tilemap always has a Grid as parent.
    47.                 return false;
    48.  
    49.             //or the tilemap's parent GO layer is 31
    50.             return parent.gameObject.layer == PaletteTilemapLayer;
    51.          
    52.         }
     
    GameDevSA likes this.
  13. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
    That worked great, thanks! That's actually exactly what I was trying to do, only show the sprite in the palette, but wasn't sure how to get there. I really think that's how Rule Tiles should work by default. I got used to things working that way back when I was using Super Tilemap Editor.

    I've got a nice little custom prefab tile script written out now. I was hoping to share my finished script on my blog, but of course a fairly key part of the code is stuff you wrote. Are you happy for me to share my custom script on my blog with credit to you for that part?
     
  14. vonchor

    vonchor

    Joined:
    Jun 30, 2009
    Posts:
    238
    It is perfectly OK - however you want to attribute, please include a link to the free asset this comes from:

    TilePlus Painter and TilePlus Toolkit: https://u3d.as/2EJR

    Glad it could work for you. Incidentally, the tiles in the toolkit have even more control over sprite visibility in scene vs Game views, although nothing specific for rule tiles.
     
  15. GameDevSA

    GameDevSA

    Joined:
    Dec 12, 2016
    Posts:
    104
    Thanks. I'll work on this soon. Although at this moment my head is spinning with the pricing announcement...