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

Change texture a tilemap renders from?

Discussion in '2D' started by SolitaryGaming, Nov 21, 2020.

  1. SolitaryGaming

    SolitaryGaming

    Joined:
    Nov 21, 2020
    Posts:
    10
    Hi

    I'm working on a season system for my game, which is a 2D RPG based on unity's built in tilemap system. Part of my season system is to change the tiles so they are recolored to match the season. I have over 1000 different tiles so it isn't feasible to attach a script to each one individually to handle this-- it would simply be too much effort and probably too many scripts running. There's two main ways I can think of to handle this:

    One is to simply have different tilemaps for each season, and enable/disable them as neccessary. While this is super simple on the scripting end and allows a bit more creative freedom, I'd like to avoid this as I have a very big game world and am a one person team, so having to redo the entire world for each season is a lot of work and room for error.

    The way I'd like to achieve this is to simply change the texture the tilemaps themselves render from. I've already finished the spritesheets for them and they're essentially recolors of eachother, with a couple other tiny changes. All of the tiles are in the same location on the sheet, so I don't see why this wouldn't work.
    I tried to do this by using terrainTilemap.sharedMaterial.SetTexture as well as .mainTexture, and while this caused a visual change to the material in the editor, it didn't change anything in the game window. I guess I can see why it didn't work as you can draw from multiple images on each tilemap. I also tried to see if I could somehow attatch a script to a Tile Pallette but unless I'm missing something that's not possible.

    Is there some other easy way I can achieve this? I was thinking maybe having seperate spritesheets for each season, then having a fifth spritesheet that the Tile Pallette is based off, which I then modify the image directly during runtime. I have no experience with this though and am unsure of if it would work.

    If it's relevant, the changes to the tilemaps will happen while my player is in a bed and the screen is black, so if there's any visual weirdness or slowdowns while it happens it really wouldn't affect the gameplay experience.

    Thank you for any help!
     
  2. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
  3. SolitaryGaming

    SolitaryGaming

    Joined:
    Nov 21, 2020
    Posts:
    10
    Tilemaps.SwapTile will not work in my current project unfortunately. I have close to 1000 different tiles already and plan on adding more as the project progresses (It's an open world RPG so the variety is important) This would simply be way too much work and room for error as I'm a one person team, so need to be mindful of scope.

    I did manage to find a workaround though, for anybody interested. Instead of swapping the texture the tilemap references from, I simply am updating the texture itself. For the size of each of my game maps I'm not having any performance hit whatsoever, and I'm not sure if I'd ever get one as my game grows as I'm simply modifying a texture instead of modifying all the tiles individually.

    Code (CSharp):
    1.     private void ApplyTextures()
    2.     {
    3.         Color[] pixels = seasonTextures[index].GetPixels();
    4.         updatedTexture.SetPixels(pixels);
    5.         updatedTexture.Apply();
    6.     }
    with index being a reference to what season it is (seasonTextures is an array with the textures organized in order of season.)

    I have no idea why I didn't think of this before as I'm already doing the same thing to handle customizing the color of my players clothes, but reading your reply made me realize it's totally possible.
     
  4. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,319
    Another way to do this is to customize a tile class.

    Basically, you have a static bool (or if >2 seasons then use an enum) that you can flip to decide which version of a sprite to display. After flipping it, call tilemap.RefreshAllTiles().

    The below example shows a Summer Sprite by default and if there's a winter version of the sprite it flips to that, otherwise it remains in the summer sprite.
    Code (CSharp):
    1.     public class SummerWinterTile : TileBase
    2.     {
    3.         public static bool showSummer = true; // set externally, then tilemap.RefreshAllTiles()
    4.  
    5.         public Sprite m_summerSprite;
    6.  
    7.         public Sprite m_winterSprite;
    8.  
    9.         public override void GetTileData(Vector3Int cell, ITilemap tilemap, ref TileData tileData)
    10.         {
    11.             tileData.sprite = ShouldShowSummer() ? this.m_summerSprite : m_winterSprite;
    12.         }
    13.  
    14.         protected bool ShouldShowSummer() => m_winterSprite == null || showSummer;
    15.     }
     
  5. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    sometimes you need to do stuff by hand

    if you added 1000 tiles once you can do it twice or thrice

    anyway you can ease your pain by automating your workflow, you need to gather all your tiles and set them in an array

    then you can create a script that changes all the Tile.sprite to the new season sprite, this avoids you creating extra tiles and redrawing tilemaps, however you need to be organized in your sprite naming so you can easily automate the switch by script.

    For big projects like these ( open world rpg ) you have to plan ahead and you cant get out of ordering the 1000 tiles at least once, if you had thought of this in the beggining it would have saved you a lot of pain.

    This is a pattern that will repeat itself for other aspects of your game if you dont think about them in advance and it will happen to you again on characters, items, monsters, customization, spells, etc
     
  6. SolitaryGaming

    SolitaryGaming

    Joined:
    Nov 21, 2020
    Posts:
    10
    I understand this now that I've made the tiles, but it's easy to tell a beginner stuff after it's already happened. While I could simply delete all my tiles and start from scratch, creating a system where I place them all in a collection and have a reference to it, this would require me to be 100% on point 1000 times, and if i make a mistake it'll be a nightmare to find. ("Is it grasstile1, or grasstile15 that is misnamed/arranged/etc?") On top of this, after spending a few months working on my game maps and not realizing I've made this mistake until now, I think it would be a mistake to go back and redo everything.

    While I appreciate the input your only advice has been the same thing I said I'm trying to avoid doing in both posts, so neither have answered my question. A simply "I don't think that's possible but you are correct that you can use swaptile to do it") would have been much more helpful. Rather than answering the question you're simply pointing out the mistakes I already know I made. This is literally my very first project that isn't an arcade game, so of course I'm going to learn some hard lessons. This is hardly the first major mistake I've made, and I don't believe it will be my last.

    If there were a useful comendium of every single mistake to avoid I'm sure I wouldn't have made this mistake, but sometimes we have to live and learn. Sometimes, it also leads to even better solutions, which I believe mine to be for the following reasons:

    I think my system of simply swapping out the images is much more elegant than having to manually input information. Without sacrificing functionality, speed, or otherwise good practices, it's best to always do what's easiest. I have setup a system where I can create a Scriptable object (SeasonalTextureSwapSO.) This contains a reference to each season texture, and a final output texture. I then run all of these through a loop that copy/pastes the seasonal texture over the output texture.

    Just for fun I stresstested this. I duplicated my terrain textures and ran this script over them 30 times. This is much more editing than I will have in my completed game. I was able to run this without there being any hiccup in my gameplay.

    I then went through and tried the swaptile method. I don't have my stuff named like you want so I just had it swap every single tile out with a blank one, just for the sake of testing speed. I immediately ran into the issue that not all of my scenes are loaded all the time (obviously,) so I had to run this loop everytime I load a scene in a new season. On top of this, it added .25 seconds to the load time of the scene, just to do this. While this is small, it's infinite more than the way I went with, as the way I went with can run whether or not the scenes are loaded as it changes the texture itself. Even if it did have a noticable hiccup, I'm running this script when the player is in a bed and the screen is black, rather than on scene load which I'm always mindful of keeping the time down for.
     
  7. raarc

    raarc

    Joined:
    Jun 15, 2020
    Posts:
    535
    if you want the true ez way solution is just make a shader that replaces the colors of your tiles, no processing time at all, just change material and its done, also you dont have to photoshop your whole sprite sheet so no need to have dupicates