3
\$\begingroup\$

I want to change Tile's sprite during runtime, but it doesn't render.

class SwitchableTile : Tile
{
  // usable sprites
  public Sprite[] sprites;
  // index to the sprite
  private int spriteIndex = 0;

  // I don't know when it's called
  // but set sprite to current spriteIndex
  public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
  {
      // ..
      if (sprites != null && sprites.Length > 0)
      {
        tileData.sprite = sprites[spriteIndex];
      }
   }
  // called per Tilemap.RefreshTile
  public override void RefreshTile(Vector3Int position, ITilemap tilemap) 
  {
    tilemap.RefreshTile(position);
  }
}

Also tried to refresh the tile according to the position:

// in MonoBehaviour
BoundsInt bounds = tilemap.cellBounds;
bool updated = false;

foreach (Vector3Int pos in bounds.allPositionsWithin) 
{
  TileBase tile = tilemap.GetTile(pos);
  if (tile && tile is SwitchableTile)
  {
    if (!updated) 
    {
      // just loop index
      Sprite sprite = ((SwitchableTile)tile).GetNextSprite();
      // set sprite
      ((SwitchableTile)tile).sprite = sprite;

      updated = true;
    }
    // refresh
    tilemap.RefreshTile(pos);
  }
}

Since Tilemap.RefreshTile calls TileBase.RefreshTile per refresh, the problem's solved after I implemented TileBase.RefreshTile.

I'm not using 2d-extra's animation tile because I want to change sprites only under certain condition. Also not using Tilemap.SwapTile because in this case I'm solving it using customized Tile, instead of creating a new Tile.


Update: Well...It proves that I need to implement RefreshTile for a Tile. Now the sprite refreshes. But I'm still not sure about GetTileData, do I need to implement it? When it is called?

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for adding this detail! It looks like you found a working solution — can you share it as an Answer? \$\endgroup\$
    – DMGregory
    Apr 4, 2019 at 17:48
  • \$\begingroup\$ @DMGregory I'm still figuring out some remaining confusions about GetTileData, I would be also appreciate if anyone else also provides an answer. \$\endgroup\$
    – knh190
    Apr 4, 2019 at 17:50
  • \$\begingroup\$ That's OK, adding an answer yourself does not block others from also answering the question, or commenting with insights. \$\endgroup\$
    – DMGregory
    Apr 4, 2019 at 17:50

1 Answer 1

4
\$\begingroup\$

There are 2 ways to change a tile's sprite during runtime:

  1. Change tiles in a tilemap to a new tile (SetTiles or SwapTile)
  2. Change tile's sprite

Both solution requires re-rendering of tiles.

This is why simply setting tile.sprite to a new sprite is not enough. I'll explain it later.


Tile is only a reference to some attributes. Tiles implement some interfaces to be called in Tilemap, which the latter one stores a grid of information and renders them.

This is important because if you want to customize your tiles, you do not call them directly on CustomizedTile : Tile like MonoBehaviour classes. You need to implement the interfaces which is used by Tilemap, and call Tilemap's methods.

So, there are two important interfaces: GetTileData and RefreshTile.

As mentioned in docs, GetTileData sets all necessary data for rendering a tile. But the render process is also controlled by RefreshTile. It's a bit weird that TileBase doesn't implement them by itself, so you have to implement them in your own customized Tile. It's also a bit weird they are not interface - like Unity UI's callbacks.

But here finally comes the solution:

// Set sprite for rendering
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
{
  if (sprites != null && sprites.Length > 0)
  {
    tileData.sprite = sprites[spriteIndex];
  }
}

// Refresh yourself
public override void RefreshTile(Vector3Int position, ITilemap tilemap) 
{
  tilemap.RefreshTile(position);
}

Now you can change the sprite outside Tile and Tilemap and refresh them (as the question's code snippet provided).

Note the first two options (SetTiles and SwapTile) by Unity automatically calls refresh so you don't have to manually do this.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .