Missing tiles when rendering an isometric Staggered tilemap in Unity

37 views Asked by At

I'm trying to show tiles on an isometric staggered tilemap in Unity, but some tiles are missing. I think the problem might be with where I'm placing the tiles or some floating-point precision issues.

The Main Issue:

I read tiles from a JSON file and try to put them on a Unity Tilemap. But, some tiles just don't show up. I've double-checked my math and even tried some small adjustments, but I still have missing tiles.

Here's the code I'm using:

MapLoader.cs - Responsible for loading the map data from a file path.

using System.IO;
using UnityEngine;

public static class MapLoader
{
    public static MapDataStructure.MapData LoadMapFromPath(string path)
    {
        if (!IsValidPath(path)) return null;
        return DeserializeMap(File.ReadAllText(path));
    }

    private static bool IsValidPath(string path)
    {
        if (File.Exists(path)) return true;
        Debug.LogError($"Cannot find or access map file at: {path}");
        return false;
    }

    private static MapDataStructure.MapData DeserializeMap(string jsonData)
    {
        return JsonUtility.FromJson<MapDataStructure.MapData>(jsonData);
    }
}

TilePlacer.cs - Contains logic for determining which tile to place and its world position.

using UnityEngine;
using UnityEngine.Tilemaps;

public class TilePlacer
{
    private readonly Tilemap _tilemap;
    private readonly Tile[] _tileAssets;

    public TilePlacer(Tilemap tilemap, Tile[] tileAssets)
    {
        _tilemap = tilemap;
        _tileAssets = tileAssets;
    }

    public void PlaceTileAtCoordinates(int[] tileData, int x, int y, MapDataStructure.MapData mapData)
    {
        int tileIndex = GetTileIndex(tileData, x, y, mapData);

        if (IsValidTileIndex(tileIndex))
        {
            PlaceTile(tileIndex, x, y, mapData);
        }
        else
        {
            Debug.LogWarning($"Invalid tile index {tileIndex} at position {y},{x}");
        }
    }

    private int GetTileIndex(int[] tileData, int x, int y, MapDataStructure.MapData mapData)
    {
        return tileData[y * mapData.width + x] - 1;
    }

    private bool IsValidTileIndex(int tileIndex)
    {
        return tileIndex >= 0 && tileIndex < _tileAssets.Length;
    }

    private void PlaceTile(int tileIndex, int x, int y, MapDataStructure.MapData mapData)
    {
        Vector3 tileWorldPosition = ComputeTileWorldPosition(x, y, mapData);
        Vector3Int cellPosition = _tilemap.WorldToCell(tileWorldPosition);
        _tilemap.SetTile(cellPosition, _tileAssets[tileIndex]);
    }

    private Vector3 ComputeTileWorldPosition(int x, int y, MapDataStructure.MapData mapData)
    {
        float worldX = x * mapData.tileWidth;
        float worldY = y * mapData.tileHeight / 2;

        // Adjust x for odd rows
        if ((y & 1) == 1)
        {
            worldX += mapData.tileWidth / 2;
        }

        return new Vector3(worldX, worldY, 0);
    }
}

TilemapRenderer.cs - Manages the process of rendering the tiles to the Tilemap.

using System.IO;
using UnityEngine;
using UnityEngine.Tilemaps;

public class TilemapRenderer : MonoBehaviour
{
    [SerializeField]
    private string mapFileName = "WorldMap.json";

    [SerializeField]
    private Tile[] tileAssets;

    private Tilemap tilemap;
    private TilePlacer tilePlacer;

    private const int DEFAULT_LAYER_INDEX = 0;

    private void Start()
    {
        tilemap = GetComponent<Tilemap>();
        tilePlacer = new TilePlacer(tilemap, tileAssets);
        LoadAndRenderMap();
    }

    private void LoadAndRenderMap()
    {
        string filePath = BuildFilePath();
        MapDataStructure.MapData mapData = MapLoader.LoadMapFromPath(filePath);

        if (mapData != null)
        {
            RenderTilesFromLayer(mapData, DEFAULT_LAYER_INDEX);
        }
        else
        {
            Debug.LogError("Failed to deserialize map data from the file.");
        }
    }

    private string BuildFilePath()
    {
        return Path.Combine(Application.streamingAssetsPath, mapFileName);
    }

    private void RenderTilesFromLayer(MapDataStructure.MapData mapData, int layerIndex)
    {
        if (mapData.layers == null || layerIndex >= mapData.layers.Length)
        {
            Debug.LogError("Invalid map layer.");
            return;
        }

        int[] tileData = mapData.layers[layerIndex].data;
        for (int y = 0; y < mapData.height; y++)
        {
            for (int x = 0; x < mapData.width; x++)
            {
                tilePlacer.PlaceTileAtCoordinates(tileData, x, y, mapData);
            }
        }
    }
}

Important Parts of the Code:

The ComputeTileWorldPosition function in TilePlacer.cs calculates the world position for a given tile based on its coordinates x and y. After calculating the world position, the function _tilemap.WorldToCell is used to convert this world position to a cell position.

Screenshots:

https://imgur.com/a/WVFLuF0

What I've Tried:

If I add a small offset (like 0.1) to where I place each tile, or if I change the grid's position to -0.5, it seems to fix it. But it feels like I'm just patching the problem instead of really fixing it.

Here's the adjusted ComputeTileWorldPosition function:

private Vector3 ComputeTileWorldPosition(int x, int y, MapDataStructure.MapData mapData)
    {
        float worldX = x * mapData.tileWidth;
        float worldY = y * mapData.tileHeight / 2;

        // Adjust x for odd rows
        if ((y & 1) == 1)
        {
            worldX += mapData.tileWidth / 2;
        }

        worldX += 0.1f;
        worldY += 0.1f;

        return new Vector3(worldX, worldY, 0);
    }

Has anyone had this problem before or see what I might be doing wrong? Any help would be great! Thanks!

0

There are 0 answers