Search code examples
c#unity-game-enginegridneighbours

Unity - Grid generation and Tile neighbours, How do I read them?


So currently I am building a small game and I am using Cellular Automata to generate the world.

So my problem comes here. I am tryig to find a way on how to read and assign neighbours of Tiles.

Like a Tile will be created and its 4 neighbours (Above,To the right,To the left and under it) will be assigned to it and it can access these Tiles through code. (Needed for world generation)

I don't want to use Rayscast or Spheres to detect neighbours as it slows down performance and gets quickly messy

I have been trying to find a way for weeks, but I don't understand how should I approach this problem

Here is my code for the world Generation.

public class WorldGenerator : MonoBehaviour
{
    public int mapSize;
    public string worldSeed;
    public bool useRandomSeed;

    [Range(0, 100)]
    public int percentOfMapIsLand;
    public GameObject tile;
    public List<GameObject> tiles;

    public int[,] mapFilled;

    // Start is called before the first frame update
    void Start()
    {
        RandomlyFillMap();
        GenerateStartingGrid();
        AssignTileNeighbors();
    }

    void RandomlyFillMap()
    {
        mapFilled = new int[mapSize, mapSize];

        if (useRandomSeed) { worldSeed = Time.time.ToString(); }

        System.Random randomNumber = new System.Random(worldSeed.GetHashCode());

        for (int x = 0; x < mapSize; x++)
        {
            for (int y = 0; y < mapSize; y++)
            {
                mapFilled[x, y] = (randomNumber.Next(0, 100) < percentOfMapIsLand) ? 1 : 0;
            }
        }
    }

    void GenerateStartingGrid() 
    {
        for (int x = 0; x < mapSize; x++)
        {
            for (int y = 0; y < mapSize; y++)
            {
                if( mapFilled[x,y] == 0) 
                {
                    Vector3 tilePosition = new Vector3(-mapSize / 2 + x, 0, -mapSize / 2 + y);

                    GameObject newTile = Instantiate(tile, tilePosition, Quaternion.Euler(Vector3.right * 90f)) as GameObject;
                    newTile.transform.SetParent(transform, false);
                    tiles.Add(newTile);        
                }
                else { continue; }
                
            }
        }
    }

    void AssignTileNeighbors() 
    {
        for (int x = 0; x < mapSize; x++)
        {
            for (int y = 0; y < mapSize; y++)
            {
                //ASIGN NEIGHBOURS HERE//


            }
        }
    }
}

and here is my code for the Tile object

 public class Tile : MonoBehaviour
{
    public GameObject neighbor_UP;
    public GameObject neighbor_RIGHT;
    public GameObject neighbor_LEFT;
    public GameObject neighbor_DOWN;
}

Solution

  • First of all you would rather need to store not only int values but rather your tiles so I would store the value along with the tiles.

    public class Tile : MonoBehaviour
    {
        public bool IsLand;
    
        // What you have you have ;)
        public Vector2Int GridPosition;
    
        public Tile neighbor_UP;
        public Tile neighbor_RIGHT;
        public Tile neighbor_LEFT;
        public Tile neighbor_DOWN;
    }
    

    Then let's assume a grid looks like

    Y
    ^ 03 13 23 33
    | 02 12 22 32
    | 01 11 21 31  
    | 00 10 20 30
      ----------> X
    

    where first value is x second value is y and mapSize = 4 and as you see you can easily go through the Tile by doing (example current tile is 21)

    • Up : y + 1, example: 2122
    • Down : y - 1, example: 2120
    • Left : x - 1, example: 2112
    • Right: x + 1, example: 2132

    then you could do

    public class WorldGenerator : MonoBehaviour
    {
        public int mapSize;
        public string worldSeed;
        public bool useRandomSeed;
    
        [Range(0, 100)]
        public int percentOfMapIsLand;
        // Make your prefabs of type Tile instead
        public Tile tile;
    
        // instead store Tile instances
        public Tile[,] mapFilled;
    
        void Start()
        {
            RandomlyFillMap();
            AssignTileNeighbors();
        }
    
        void RandomlyFillMap()
        {
            // In one go cretae the random values and all tile instances
            mapFilled = new Tile[mapSize, mapSize];
    
            if (useRandomSeed) 
            { 
                worldSeed = Time.time.ToString(); 
            }
    
            var randomNumber = new System.Random(worldSeed.GetHashCode());
    
            for (int x = 0; x < mapSize; x++)
            {
                for (int y = 0; y < mapSize; y++)
                {
                    // To make things easier I would rather create all the tiles
                    // Even though this means a bit more overhead ONCE
                    // That's up to you ofcourse .. you could as well just not create them and then 
                    // the according values in the neighbors would be not assigned
                    var isLand = randomNumber.Next(0, 100) < percentOfMapIsLand;
    
                    var tilePosition = new Vector3(-mapSize / 2 + x, 0, -mapSize / 2 + y);
                    var newTile = Instantiate(tile, tilePosition, Quaternion.Euler(Vector3.right * 90f));
                    newTile.transform.SetParent(transform, false);
                    newTile.IsLand = isLand; 
                    newTile.GridPosition = new Vector2Int(x, y);
                    
                    // Then simply disable the tiles you don't need
                    newTile.SetActive(isLand); 
                    // And store them in the map
                    mapFilled[x, y] = newTile;
                }
            }
        }
    
        void AssignTileNeighbors() 
        {
            for (int x = 0; x < mapSize; x++)
            {
                for (int y = 0; y < mapSize; y++)
                {
                    // And now we just use the rules from before to get the neighbors
                    // for the edges the neighbors will stay unassigned
                    var tile = mapFilled[x,y];
                    // For each assignment we check whether the neighbor would still be within the map bounds
                    // if not, that means we are an edge tile and there exists no further neighbor
                    tile.neighbor_UP = y < mapSize - 1 ? mapFilled[x, y + 1];
                    tile.neighbor_DOWN = y > 0 ? mapFilled[x, y - 1];
                    tile.neighbor_LEFT = x > 0 ? mapFilled[x - 1, y];
                    tile.neighbor_RIGHT = x < mapSize - 1 ? mapFilled[x + 1, y];
                }
            }
        }