Search Unity

Question Problem about multiple Tilemaps, Ruletiles and tilemap.SetColor

Discussion in '2D' started by Lahiter, Oct 10, 2021.

  1. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    Hello! I'm making a procedural map using Perlin Noise. Each value of the perlin noise map is replaced by a Rule Tile.

    I tried changing the color of the Rule Tile a bit to add depth to the map, but this happens:


    Color applying only on the bottom of the tilemap

    I don't know why this is happening, maybe the tilemap is too large? It's not a glitch on the editor, as if I move the camera this weird effect happens on the tiles (random tiles are darkened)


    Noticeable change on color in the tilemap

    As a workaround, I made a chunk system and it seems to work correctly, but the problem is that I'm using Rule Tiles and the rules don't apply between different tilemaps. Here's a image of that effect:


    Rule tile is broken at the end of the tilemap

    With the chunk system the color is correctly applied, but I have the rule tile problem. Here's the code for the tile placement and color correction part of the map generation:

    Code (CSharp):
    1. private void GenerateTileMap(bool useChunks = false) {
    2.  
    3. float[,] noiseMap = Noise.GenerateNoiseMap(mapWidth, mapHeight, seed, noiseScale, octaves, persistance, lacunarity, offset);
    4.  
    5.  
    6.  
    7. int neededChunks = (mapWidth / tilesPerChunk) * (mapHeight / tilesPerChunk);
    8.  
    9. if(neededChunks == 0 || !useChunks) {
    10.  
    11. neededChunks = 1;
    12.  
    13. }
    14.  
    15.  
    16.  
    17. Tilemap[] tilemapChunks = new Tilemap[neededChunks];
    18.  
    19.  
    20.  
    21. for(int i = 0;i < neededChunks; i++) {
    22.  
    23. GameObject newChunk = Instantiate(tileMapPrefab, this.transform);
    24.  
    25. newChunk.transform.position = transform.position/* + (new Vector3(1f, 1f, 0f) * tilesPerChunk * i)*/;
    26.  
    27. tilemapChunks[i] = newChunk.GetComponentInChildren<Tilemap>();
    28.  
    29. }
    30.  
    31.  
    32.  
    33. for (int y = 0; y < mapHeight; y++) {
    34.  
    35. for (int x = 0; x < mapWidth; x++) {
    36.  
    37. float currentHeight = noiseMap[x, y];
    38.  
    39. int currentChunkX = 0;
    40.  
    41. if(useChunks)
    42.  
    43. currentChunkX = Mathf.FloorToInt(x / tilesPerChunk);
    44.  
    45. int currentChunkY = 0;
    46.  
    47. if(useChunks)
    48.  
    49. currentChunkY = Mathf.FloorToInt(y / tilesPerChunk) * (mapHeight / tilesPerChunk);
    50.  
    51. int currentChunk = currentChunkX + currentChunkY;
    52.  
    53.  
    54.  
    55. for (int i = 0; i < regions.Length; i++) {
    56.  
    57. if (currentHeight <= regions[i].height) {
    58.  
    59. Vector3Int pos = new Vector3Int(x, y, Mathf.RoundToInt(transform.position.z));
    60.  
    61. tilemapChunks[currentChunk].SetTileFlags(pos, TileFlags.None);
    62.  
    63. tilemapChunks[currentChunk].SetTile(pos,regions[i].tile);
    64.  
    65. float heightColor = Mathf.InverseLerp(0f, 1f, currentHeight) + 0.4f;
    66.  
    67. tilemapChunks[currentChunk].SetColor(pos, new Color(heightColor , heightColor, heightColor));
    68.  
    69.  
    70.  
    71. break;
    72.  
    73. }
    74.  
    75. }
    76.  
    77.  
    78.  
    79. }
    80.  
    81. }
    82.  
    83. }


    Have you ever found a problem like this? I was thinking about modifying the Rule Tile to search neighbour tiles in other tilemaps, but I didn't managed to find a way to do it. The chunk system is nice to have, but it doesn't seem to have a great impact in the performance of the game.
     
  2. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,514
    For the rule tiles breaking across different tilemaps, there are two options:
    1. condense tilemap chunks into 1 tilemap
      • when loading chunks (here: tiles) onto the same tilemap, use tilemap.SetTiles / SetTilesBlock
      • with 1 tilemap, there won't be any seams
    2. don't use RuleTiles and instead apply neighbor-checking rules yourself using simple tiles instead
      • create your own class to store neighbor rules and apply those rules to select different tiles to set to the tilemap
      • check other tilemaps for neighbors too
      • by controlling what tiles are set to the tilemap yourself instead of relying on specialized tiles, you can check neighbors, including in other chunks
    For why the color is wrong, I'm not sure, but I suspect it's a code error.
     
  3. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    Hi Lo-renzo! Thanks for answering.

    Do you think that having chunks but only 1 tilemap will help with performance? I could draw the needed tiles around the player and remove the tiles offscreen.

    The second option would be great. I tried something similar with the built in Unity's Rule Tile but I can't find the part of the code where the tilemap of each tilebase is applied (and pass the rest of the tilemaps). I'll probably try the first option and see how it affects performance, and if I'm not convinced I'll work on this second option.

    The color being wrong I don't think is because a code error, as if I use chunks the color applies correctly. Maybe is a memory related issue?

    Thanks!
     
  4. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    UPDATE

    I've tried making tiles appear as the player moves around, but it's by far worst in performance. I think I'll stick with one single tilemap as it seems the best way to do this.

    The thing is, when using the Tilemap.SetColor everything works fine until reaching a large number of tiles (more than 256x256 tiles). Here's a screenshot of my map consisting in 512x512 tiles
    Captura de pantalla 2021-10-11 191116.png

    And here is a 256x256 tile map using the exact same method for coloring the tiles:

    Captura de pantalla 2021-10-11 191457.png

    Is this because some limitation of the function Tilemap.SetColor? The weird thing is that if I set the color to a default Color (Color.blue, for example) it works as intended with large maps.

    Captura de pantalla 2021-10-11 191827.png

    Here's my code for coloring the tiles:


    Code (CSharp):
    1. public void DisplayMap() {
    2.         foreach(WorldData.TileData tileData in WorldData.tileData.Values) {
    3.             tilemap.SetTileFlags(tileData.position, TileFlags.None);
    4.             tilemap.SetTile(tileData.position, tileData.tile);
    5.             tilemap.SetColor(tileData.position, tileData.color);
    6.         }
    7.     }
    TileData is a struc I made to store each tile info:

    Code (CSharp):
    1. public struct TileData
    2.     {
    3.         public Vector3Int position;
    4.         public TileBase tile;
    5.         public float height;
    6.         public Color color;
    7.  
    8.         public TileData(Vector3Int position, TileBase tile, float height, Color color) {
    9.             this.position = position;
    10.             this.tile = tile;
    11.             this.height = height;
    12.             this.color = color;
    13.         }
    14.     }
    And here is how I set the Color variable of TileData. Height is just the value of the Perlin Noise map I use for the world generation:

    Code (CSharp):
    1. public static void AddTile(Vector3Int pos, TileBase tile, float height) {
    2.         if (!tileData.ContainsKey(pos)) {
    3.             float colorValue = 0.3f + height;
    4.             tileData.Add(pos, new TileData(pos, tile, height, new Color(colorValue, colorValue, colorValue)));
    5.         }
    6.     }
    Does anyone know what could be happening? I think that the height values are correct in the places where the color is badly placed, as it puts the correct tile in that position (tiles are placed depending of this height value). But what bugs me is that if I color with a default color, it does it correctly.

    Thanks for reading!
     
  5. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    New update, I tested the limits in which the tilemap was drawn correctly (using the SetColor and the proper values) and anything higher than 372 by 372 tiles will produce the weird lines in the previous post's images.

    372 x 372 tiles map
    Captura de pantalla 2021-10-11 195915.png


    373 x 373 tiles map. In the game window below you can see the weird lines happening.
    Captura de pantalla 2021-10-11 200029.png
     
  6. Epic_Baguette

    Epic_Baguette

    Joined:
    May 31, 2017
    Posts:
    3
    Hey I'm not sure but it happened to me that this wayt of getting an index:

    currentChunkX = Mathf.FloorToInt(x / tilesPerChunk);



    is messing up the index you intended to have. Maybe try a modulo solution ?

    I haven't gone in depth of your code or tried anything but maybe it helps
     
  7. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,514
    You could try to speed this up. For example, keep track of the areas you have drawn on the tilemap, say 40x40 cell blocks. When the player gets near a block that isn't yet drawn to the tilemap, SetTiles those tiles. When far away, clear those tiles. This way, it is rare to load a chunk on a frame. You could increase/decrease the size of the block to not create frame hitches.

    For the color problem, are you sure the noise isn't at fault? Is this procedurally-generated perlin or from a texture? I ask b/c the artifact you're seeing almost looks like texture compression.
     
  8. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    Thanks for the replies!

    I've tried different ways of doing this chunking system, but taking a look at the profiler is way worst to have it that only draw the tilemap once at the start of the game. I tried it setting a max distance to start drawing the new tiles, don't do anything on the ones that are already drawn and to clear only the needed ones. But as I said, this results in huge lag spikes if you move the character too quickly. So I prefer to do it without chunks (although Unity's tilemap uses chunking in some way, I'm guessing that's why the performance is better without using custom chunking).


    I made a little debug function, where you can click the screen and get the height of the selected tile. Everything seems fine, but if I add painting the tile I clicked again using the color generated, the color is wrong. I mean, the height is correct, the color is generated using new Color(height, height, height) but in those parts I always get the same color.

    Maybe is a memory problem? I'm generating and storing 138.384 (372x372) colors, heights and positions for this tilemap. Anything higher that that will result on the artifact we see in the images.
     
  9. SXtheOne

    SXtheOne

    Joined:
    Sep 5, 2018
    Posts:
    21
    Any update on this? I'm curious.
     
  10. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    I didn't manage to solve it. I'm just sticking with smaller maps for now.
     
  11. SXtheOne

    SXtheOne

    Joined:
    Sep 5, 2018
    Posts:
    21
    I can't promise a solution but if you upload a working example which produces the bug I'll check it.
     
  12. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,514
  13. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
  14. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    Btw the Unity version I used for this example is 2020.3.4f1. You can replicate the problem by simply opening SampleScene scene and hitting the play button. The top tiles in the map will appear with strange shadows.
     
  15. SXtheOne

    SXtheOne

    Joined:
    Sep 5, 2018
    Posts:
    21
    Ok, I have a solution for you but it's more like avoiding this problem than solving it.
    I've split the displaying of the map so it's not done in one go:
    Code (CSharp):
    1.     public void DisplayMap(Tilemap map, Vector2 from, Vector2 to)
    2.     {
    3.         foreach(WorldData.TileData tileData in WorldData.tileData.Values) {
    4.  
    5.             if (tileData.position.x >= from.x && tileData.position.y >= from.y
    6.                 && tileData.position.x < to.x && tileData.position.y < to.y)
    7.             {
    8.                 map.SetTileFlags(tileData.position, TileFlags.None);
    9.                 map.SetTile(tileData.position, tileData.tile);
    10.                 map.SetColor(tileData.position, tileData.color);
    11.             }
    12.         }
    13.     }
    And this is how I call it:
    Code (CSharp):
    1.         display.DisplayMap(display.tilemap, new Vector2(256, 0), new Vector2(513, 256));
    2.         display.DisplayMap(display.tilemap, new Vector2(256, 256), new Vector2(513, 513));
    3.         display.DisplayMap(display.tilemap2, new Vector2(0, 0), new Vector2(256, 256));
    4.         display.DisplayMap(display.tilemap2, new Vector2(0, 256), new Vector2(256, 513));
    And don't forget to duplicate the tilemap gameobject and drag it into MapDisplay's new tilemap2 field.
    Sorry for making the tilemaps public, it's not clean code, wasn't it's purpose. :)

    So basically it's just pushing the tiles to an other tilemap so the problem won't appear.

    I strongly feel it's a bug in the Tilemap Unity component because the buggy outcome wandered around based on the order of the four DisplayMap() method calls. The last two were buggy, no matter which ones they were. I also tried to call DisplayMap methods far from each other in time (put them into an Update() method and delayed each call) but the problem was persistent.

    I hope this solution helps.
     
  16. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    Hey, thank you so much! It's a good solution, the downside is that the rule tiles will not apply rules between different tilemaps. But maybe I can find a workaround for that.

    I agree that it feels like a bug in the Tilemap Unity. I also tried to draw the shadows in batches but the problem still happens.

    Again, thank you for your help! :D
     
  17. SXtheOne

    SXtheOne

    Joined:
    Sep 5, 2018
    Posts:
    21
    You're welcome :) You may also try to make your noise map a texture and scale it up (setting the filter mode probably to 'point') to cover the whole tilemap and simply move that texture with the tilemap (or if only the camera moves that part is not needed). That way you won't need to slice your tilemap. I don't know how it impacts performance but if the noise map texture size is small (one pixel for each tile) and you scale that to 16x (your tile size) that may work.
     
    Lahiter likes this.
  18. Lahiter

    Lahiter

    Joined:
    Mar 1, 2018
    Posts:
    14
    Great idea! I'll try that and post the results here :)
     
    SXtheOne likes this.
  19. SundownStudio

    SundownStudio

    Joined:
    Apr 19, 2017
    Posts:
    14
    Hey did you ever find a solution to this?
    I was thinking - you can lay down the chunks you want to draw, then manually iterate over each edge of the tilemap and connect it manually in script with the corresponding tilemap on the adjacent side.. I haven't done it yet but I'm approaching this same issue so was just wondering if you ever managed to get that working? I'm scared of lag from adding Ruletile to tilemaps at runtime.. I read somewhere that Unity is considering making SetTile async but not sure if/when..