1
\$\begingroup\$

I am new to Unity3d's Tilemap system and found a problem I couldn't figure out at the time of writing this question. What I want to do is to create a bunch of GameObject overlaid in each of the tile's supposed location in the Tilemap grid. The GameObjects created are supposed to represent nodes for pathfinding purpose.

The problem shows when I get the position of the tile using Tilemap.GetCellCenterWorld(), the returned Vector3 is displaced by some value so it did not exactly represent the center position of the tile's cell.

Below is the screenshot of the problem: shown in the editor:

The problem on the editor

The instantiated gameobject have gizmos drawn on its position to ease visual debugging of the process, hence the array of small sphere mimicking the shape of the placed tiles.

The state of the gameobject hierarchy when the problem happened:

Hierarchy of the gameobject

The process of instantiating the node is done in the Awake lifecycle method of PathNodeGenerator script attached as a component to PathfindingTile GameObject shown in the Hierarchy.

Below are the code written in PathNodeGenerator.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

/// <summary>
/// PathNodeGenerator is responsible for generating nodes used 
/// for pathfinding algorithm in the game.
/// </summary>
public class PathNodeGenerator : MonoBehaviour
{
    /**
     * containerGrid is the Grid component
     * that holds the tilemaps in the scene
     */
    public Grid containerGrid;
    public string nameOfTheBaseTilemap = "Floor";

    [System.Serializable]
    public class NodePrefabEntry {
        /**
         * Name refers to the tilemap name the prefab
         * supposed to represent in the code
         */
        public string name;
        public GameObject prefab;
    };

    public List<NodePrefabEntry> namedNodePrefabs = new List<NodePrefabEntry>();

    Dictionary<string, GameObject> environmentNodePrefabs = new Dictionary<string, GameObject>();
    Dictionary<string, Tilemap> environmentTilemaps = new Dictionary<string, Tilemap>();

    public List<GameObject> unsortedNodes;
    public GameObject[,] nodes;

    /// <summary>
    /// Populate data in Awake
    /// </summary>
    void Awake()
    {
        this.PopulatePrefabDictionary();
        this.PopulateTilemapDictionary();

        Tilemap baseTilemap;
        if (!this.environmentTilemaps.TryGetValue(
            this.nameOfTheBaseTilemap.ToLower(), out baseTilemap))
        {
            //TODO: handle error when the tilemap does not exist;
            Debug.Log("Does not exist yet in awake!");
        }
        this.transform.position = containerGrid.transform.position;
        this.GenerateNodes();
    }

    /// <summary>
    /// PopulatePrefabDictionary() populates the dictionary that holds name-prefab pair
    /// from the workaround list
    /// </summary>
    void PopulatePrefabDictionary()
    {
        for(int i=0; i < this.namedNodePrefabs.Count; i++)
        {
            this.environmentNodePrefabs.Add(
                this.namedNodePrefabs[i].name.ToLower(), 
                this.namedNodePrefabs[i].prefab);
        }
        Debug.Log(String.Format("PopulatePrefabDictionary result: {0}",this.environmentNodePrefabs.Count));
    }

    /// <summary>
    /// PopulateTilemapDictionary() populates the tilemap dictionary
    /// in this object by iterating through the container grid's
    /// children an getting the tilemap component and its name as
    /// the value and key pair respectively in the dictionary.
    /// </summary>
    void PopulateTilemapDictionary()
    {
        Component[] tilemaps = this.containerGrid.GetComponentsInChildren<Tilemap>();
        foreach(Tilemap tilemap in tilemaps)
        {
            string name = tilemap.gameObject.name.ToLower();
            this.environmentTilemaps.Add(name, tilemap);
        }
        Debug.Log(String.Format("PopulateTilemapDictionary result: {0}", this.environmentTilemaps.Count));
    }

    /// <summary>
    /// Generate pathfinding nodes and interactive nodes from the
    /// tilemaps registered on this object.
    /// </summary>
    void GenerateNodes()
    {
        Tilemap baseTilemap;
        if(!this.environmentTilemaps.TryGetValue(
            this.nameOfTheBaseTilemap.ToLower(),out baseTilemap))
        {
            //TODO: handle error when the tilemap does not exist;
            Debug.Log(string.Format("{0} tilemap does not exist yet!",this.nameOfTheBaseTilemap.ToLower()));
        }

        //Prepare the tiles in Tilemap grid to be iterated through
        baseTilemap.CompressBounds();
        BoundsInt bounds = baseTilemap.cellBounds;
        TileBase[] allTiles = baseTilemap.GetTilesBlock(bounds);

        //Loop through the tiles on the Tilemap grid and
        //create PathNode prefab object on the location
        //of each available tiles
        for (int x = 0; x < bounds.size.x; x++)
        {
            for(int y = 0; y < bounds.size.y; y++)
            {
                TileBase tile = allTiles[x + y * bounds.size.x];
                if (tile != null)
                {
                    GameObject nodePrefab;
                    if (!this.environmentNodePrefabs.TryGetValue(this.nameOfTheBaseTilemap.ToLower(), out nodePrefab))
                    {
                        //TODO: handle error when the default node prefab does not exist
                        Debug.Log(string.Format("{0} prefab does not exist!",this.nameOfTheBaseTilemap.ToLower()));
                    }
                    Vector3 pos = baseTilemap.GetCellCenterWorld(new Vector3Int(x,y,0));
                    Debug.Log(string.Format("Node_{0}x{1}: ({2},{3},{4})",x,y,pos.x,pos.y,pos.z));
                    GameObject currentNode = Instantiate(nodePrefab, this.transform);
                    currentNode.name = string.Format("Node_{0}x{1}", x, y);
                    currentNode.transform.position = pos;
                }
            }
        }
    }
}

This behaviour persist in Unity3D 2019.2.0b7 , 2019.1.3f1, & 2018.3.11f1.

If anyone know what causing the displacement of the returned position or anything related to it, your inputs are very much appreciated. Thanks!

\$\endgroup\$
1
\$\begingroup\$

After a while a found an answer. So the Tilemap instances have its own origin position in their parent grid object which can be accessed with <Tilemap instance>.origin. The origin position is equal to its minimum bound point after you compress the tilemap's cell bound.

So you can get the exact center world position of each tile in a tilemap by iterating each cell position got from the origin point coordinate to the origin point offsetted by the size of the cellBounds, and calling <Tilemap instance>.getCellCenterWorld(cell position).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.