In this project a level or dungeon is randomly generated through the use of ‘Perlin Noise’. It’s a small 2D-project made by Wayne in Unity Engine and programmed by him in ‘C#’. First it generates a texture based off of the engine’s own Perlin Noise generation. Wayne was able to give it its proper scaling for use as a level. The pixels are enlarged to simulate actual chambers that can be connected to eachother through smaller bridges.
Wayne was able to implement the randomization element through the use of a random number and adding it to every pixel on the texture. The Unity Engine’s ‘grid’ features makes it easier to place tiles. To convert the texture to a grid of tiles, you first have to convert the texture’s resolution to the size of the grid, taking into account the size of each tile.
The example below (PerlinNoiseTexture.cs) shows what to look for when placing a tile at a certain position. In this case, the amount of red is used as a determening factor for placing the tiles. This way, the Perlin Noise generates a level for you.
The example below also shows how bridges between chambers are created using the center of each chamber. The position of the ‘center points’ are put in a list and they determine the possibility of them having the same axis by index. That is why the ‘Selection Sorting’ algorithm is used.
Wayne has yet to implement a ‘randomize button’ so the generation of a level happens in-game, instead of starting the build over and over.
void Awake()
{
rendr = GetComponent<Renderer>();
texture = new Texture2D(888, 500);
color = new Color[texture.width * texture.height];
rendr.material.mainTexture = texture;
_noiseScale = CheckNoiseScale(_noiseScale);
CalculateNoise();
}
void CalculateNoise()
{
for (float y = 0f; y < texture.height; y++) {
for (float x = 0f; x < texture.width; x++)
{
float xCoord = originPoint.x + x / (texture.width / _noiseScale) *
transform.localScale.x;
float yCoord = originPoint.y + y / (texture.height / _noiseScale) *
transform.localScale.y;
//This Adds Randomization
int[] newNoise = { Random.Range(0, 1000), Random.Range(0, 1000) };
float sample = Mathf.PerlinNoise(xCoord + (newNoise[0] / 10f),
yCoord + (newNoise[1] / 10f));
color[(int)y * texture.width + (int)x] = new Color(sample,
sample, sample);
}
}
texture.SetPixels(color);
texture.Apply();
}
void PlaceGroundTiles(Tile aTile, Tile otherTile)
{
float intensityBar = (COLOR_INTENSITY_MAX –
_tileOccurrence)/ COLOR_INTENSITY_MAX;
gridSize = new Vector2Int(Mathf.CeilToInt(texture.Texture.width /
(GRID_GEN_OFFSET * grid.cellSize.x)),
Mathf.CeilToInt(texture.Texture.height /
(GRID_GEN_OFFSET * grid.cellSize.y)));
//Tile Placement Based on Texture Pixel Intensity Values
for (int j = 0; j < gridSize.y; j++) {
for (int i = 0; i < gridSize.x; i++)
{
//Look for the ‘r’-value per Pixel
if (texture.Texture.GetPixel(Mathf.FloorToInt((i / grid.cellSize.x) /
_pixelSize.x), Mathf.FloorToInt((j / grid.cellSize.y) /
_pixelSize.y)).r > intensityBar)
{
tileNeighbor[0] = new Vector3Int(i –
Mathf.RoundToInt(gridSize.x / 2f) – 1,
-(j – Mathf.RoundToInt(gridSize.y / 2f)), 0);
tileNeighbor[1] = new Vector3Int(i –
Mathf.RoundToInt(gridSize.x / 2f), -(j –
Mathf.RoundToInt(gridSize.y / 2f) – 1), 0);
tilePos = new Vector3Int(i –
Mathf.RoundToInt(gridSize.x / 2f),
-(j – Mathf.RoundToInt(gridSize.y / 2f)), 0);
//Find the Center of a Room
if (tileMap.GetTile(tileNeighbor[0]) == null &&
tileMap.GetTile(tileNeighbor[1]) == null)
{
tilePos = new Vector3Int(i –
Mathf.RoundToInt(gridSize.x / 2f) +
Mathf.FloorToInt(grid.cellSize.x *
_pixelSize.x * 0.5f),
-(j – Mathf.RoundToInt(gridSize.y / 2f) +
Mathf.FloorToInt(grid.cellSize.y *
_pixelSize.y * 0.5f)), 0);
//Highlight Center of Room
//tileMap.SetTile(tilePos, otherTile);
centerPoint[0].Add(new Vector2Int(tilePos.x,
tilePos.y));
}
tilePos = new Vector3Int(i – Mathf.RoundToInt(gridSize.x /
2f), -(j – Mathf.RoundToInt(gridSize.y / 2f)), 0);
//Place Tile if There is None Placed Yet
if (tileMap.GetTile(tilePos) == null) {
tileMap.SetTile(tilePos, aTile);
}
}
}
}
}
void PlaceHorizontalBridges(Vector2Int posA, Vector2Int posB, Tile aTile)
{
//Check for the Exact Same ‘y’-value
if (posA.y == posB.y)
{
int killSwitch = 0;
tileNeighbor[0] = new Vector3Int(posA.x + 1, posA.y, 0);
tileNeighbor[1] = new Vector3Int(posB.x, posB.y, 0);
//Walk Till You Get There
while (tileNeighbor[0] != tileNeighbor[1] &&
killSwitch < BRIDGE_LENGTH_MAX)
{
if (tileMap.GetTile(tileNeighbor[0]) == null) {
tileMap.SetTile(tileNeighbor[0], aTile);
}
tileNeighbor[0].x++;
killSwitch++;
}
}
}
List<Vector2Int> SortOnYValues(List<Vector2Int> currentList)
{
Vector2Int[] sorter = currentList.ToArray();
//Selection Sorting
for (int i = 0; i < sorter.Length; i++)
{
int minimumIndex = i;
for (var j = i + 1; j < sorter.Length; j++)
{
if (sorter[minimumIndex].y > sorter[j].y ||
(sorter[minimumIndex].y == sorter[j].y &&
sorter[minimumIndex].x > sorter[j].x))
{
minimumIndex = j;
}
}
if (minimumIndex != i)
{
Vector2Int temp = sorter[minimumIndex];
sorter[minimumIndex] = sorter[i];
sorter[i] = temp;
}
}
return sorter.ToList<Vector2Int>();
}