0
\$\begingroup\$

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!

\$\endgroup\$

0

You must log in to answer this question.

Browse other questions tagged .