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;
}
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
)
y + 1
, example: 21
→ 22
y - 1
, example: 21
→ 20
x - 1
, example: 21
→ 12
x + 1
, example: 21
→ 32
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];
}
}
}