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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Change a tile's sprite during runtime

Discussion in '2D' started by spryx, Apr 16, 2018.

  1. spryx

    spryx

    Joined:
    Jul 23, 2013
    Posts:
    556
    Is it possible to change the sprite of a single tile during play ?

    I have not found a way to do this without affecting other tiles or replacing the tile. Does anyone know if this is possible?
     
  2. petarpejovic

    petarpejovic

    Joined:
    Mar 17, 2016
    Posts:
    6
    Hello :)
    You can write something like this and attach script to desired tile.
    Code (CSharp):
    1.     public Sprite newSprite;
    2.     private SpriteRenderer sr;
    3.     private float timer;
    4.  
    5.     private void Start()
    6.     {
    7.         sr = gameObject.GetComponent<SpriteRenderer>();
    8.     }
    9.     void Update ()
    10.     {
    11.         timer += Time.deltaTime;
    12.         if (timer > 10) // change tile after 10 seconds
    13.         {
    14.            
    15.             sr.sprite = newSprite;
    16.             timer = 0;
    17.         }
    18.     }
     
  3. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
  4. spryx

    spryx

    Joined:
    Jul 23, 2013
    Posts:
    556
    Seems like the tile has to be replaced with SetTile... what a mess.
     
  5. furroy

    furroy

    Joined:
    Feb 24, 2017
    Posts:
    93
    I haven't tried it, but I don't see why you can't do Tilemap.GetTile() to get a TileBase. Cast to Tile and if successful, set tile.sprite = yourSprite.
     
  6. spryx

    spryx

    Joined:
    Jul 23, 2013
    Posts:
    556
    because tile.sprite does not actually modify the sprite. It simply changes the reference to the sprite that is assigned before the tile is rendered. There is not a single render component for tiles. I think this is what makes them fast and ideal for use. This, unfortunately, makes assigning behavior to them rather difficult.
     
  7. ovunctuzel

    ovunctuzel

    Joined:
    Oct 31, 2015
    Posts:
    2
    Sorry for the necro, but were you able to find a workaround for this? I was trying to update sprite of a tile based on the sprites on adjacent tiles. Not sure if this is possible now.
     
    CheekySparrow78 likes this.
  8. spryx

    spryx

    Joined:
    Jul 23, 2013
    Posts:
    556
    Unfortunately not currently... Without some rather hacky workarounds(i.e. prefab tiles). Your use case sounds ideal for the RuleTile. Have you checked into this?
     
  9. FergJoe

    FergJoe

    Joined:
    Dec 28, 2016
    Posts:
    19
    Sorry for the necro post, but I stumbled on this thread while trying to work through a similar problem today. If you haven't already solved this, Scriptable Tiles is definitely the way to go.

    My project needs the tilemap to change with the seasons - I made a "Seasonal Tile" that chooses from 4 sprites based on the current value of an in-game variable. It defaults to my "Spring" tile so that I can see something while I'm painting in the editor. This definitely needs some more refinement, but it works - I based it off of Unity's "Random Tile" script:

    Code (CSharp):
    1. [CreateAssetMenu(fileName = "New Seasonal Tile", menuName = "Tiles/Seasonal Tile")]
    2.     public class SeasonalTile : Tile
    3.     {
    4.         public Sprite Spring;
    5.         public Sprite Summer;
    6.         public Sprite Fall;
    7.         public Sprite Winter;
    8.  
    9.         private Season season = Season.Spring;
    10.         private Sprite newSprite;
    11.      
    12.         public override void GetTileData(Vector3Int location, ITilemap tileMap, ref TileData tileData)
    13.         {
    14.             base.GetTileData(location, tileMap, ref tileData);
    15.  
    16.             if (SeasonChanger.instance != null)
    17.             {
    18.                 //    Get season
    19.                 season = SeasonChanger.instance.GetSeason();
    20.  
    21.                 switch (season)
    22.                 {
    23.                     case Season.Spring:
    24.                         newSprite = Spring;
    25.                         break;
    26.                     case Season.Summer:
    27.                         newSprite = Summer;
    28.                         break;
    29.                     case Season.Fall:
    30.                         newSprite = Fall;
    31.                         break;
    32.                     case Season.Winter:
    33.                         newSprite = Winter;
    34.                         break;
    35.                 }
    36.             }
    37.          
    38.             //    Change Sprite
    39.             tileData.sprite = newSprite;
    40.         }
    41.     }
    It means setting up A LOT of these tiles into my final Tile Pallette, but that will take much less time than painting 4 exact copies of every single area.

    The trick to get them to change lies on the tilemaps themselves. I put a simple script on each tilemap that calls a function to refresh each tilemap at level start (or whenever the player wakes up to a new season).

    Code (CSharp):
    1.  
    2. public class TilemapRefresh : MonoBehaviour
    3. {
    4.     private Tilemap tilemap;
    5.  
    6.     void Start()
    7.     {
    8.         tilemap = GetComponent<Tilemap>();
    9.         RefreshMap();
    10.     }
    11.  
    12.     public Void RefreshMap()
    13.     {
    14.         if (tilemap != null)
    15.         {
    16.             tilemap.RefreshAllTiles();
    17.         }
    18.     }
    19. }
    You could easily adapt this to make your tile (or any other) addressable in a similar way!
     
    Jerep94, RiotDX, Zek23 and 3 others like this.
  10. Wecica

    Wecica

    Joined:
    Jul 20, 2016
    Posts:
    27
    Hi, there, thanks a lot that you share your way of solving the problem. I have encountered the same problem as the post starter. But your script does not change only one single tile of the tilemap. It will change all the tiles of this tilemap. Have you got any other way that can change a single tile of the tilemap?
     
  11. FergJoe

    FergJoe

    Joined:
    Dec 28, 2016
    Posts:
    19
    No worries - without reviving necro posts, I wouldn't know HALF of what I do about Unity!

    The script will change any tile painted with that particular tile - that was what I needed for my particular use case. You can adapt the script to change just one tile, but that tile has to be painted with this scriptable tile, then addressed via script. If you've worked with scriptable objects, they have a lot in common.

    It would be so much easier to have a function like ChangeTileSprite(x,y, newSprite), wouldn't it? For 1 tile, you could always just place the sprite as a separate gameobject in the layer ABOVE your default tilemap, and change it / make it appear via script.

    There are some 3rd party assets for tilemaps that may do what you're looking to do. "Super Tilemap Maker" is a pretty great tool with a lot more scripting capability.

    Hopefully, we'll get additional features as Unity's Tilemap system matures.
     
  12. Wecica

    Wecica

    Joined:
    Jul 20, 2016
    Posts:
    27

    Thanks a lot, dude, I have already done it with scriptable tile. That works like a charm. ; )
     
  13. foxskrs

    foxskrs

    Joined:
    Jun 6, 2021
    Posts:
    3
    I found a solution. Corrected the Rule Tile script a little. Corrected the RuleMatches functions. In these functions, the program bypassed adjacent tiles and checked for identity. I changed the check for fullness.
    upload_2021-6-7_2-41-42.png
     
  14. unity_N-t0J4eGyNP5qg

    unity_N-t0J4eGyNP5qg

    Joined:
    Feb 1, 2023
    Posts:
    1
    For a simpler script, use the following:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.Tilemaps;
    4.  
    5. [CreateAssetMenu(fileName = "New Tile Plus", menuName = "Tiles/Tile Plus")]
    6. public class TilePlus : Tile
    7. {
    8.     public Sprite newSprite;
    9.     public override void GetTileData(Vector3Int location, ITilemap tileMap, ref TileData tileData) {
    10.         base.GetTileData(location, tileMap, ref tileData);
    11.  
    12.         tileData.sprite = newSprite;
    13.     }
    14. }
    15.  
    Creating a `Tile Plus` will allow you to change the sprite like follows:
    Code (CSharp):
    1.  
    2. tile.newSprite = spritteWanted;
    3. tileMap.RefreshTile(tilePosition); // As Vector3Int
    4.  
    The way unity handles tiles to save computation is to essentially set and forget. Once they are "refreshed", unity does not care about them and any changes made to a tile will not get shown until they are refreshed. This is both annoying and handy, it means the computation is a lot faster but also that you need to manually refresh in this case. It's a kind of [Lazy Evaluation](https://en.wikipedia.org/wiki/Lazy_evaluation)

    In fact, this example I gave actually exploits this mechanic. I have only a single tile asset in my entire game. I set the general sprite of this tile asset. If unity updated tiles eagerly (as in, the moment a change was made), this code would actually change every single tile at once. Instead, you restrict the update to only a single tile by specifying which one is updated, so the new sprite is only applied to the desired tile.

    This could mean that if you have other code that updates tiles, you would start getting unexpected sprite updates. So keep that in mind