Skip to content

Instantly share code, notes, and snippets.

@iamgabrielma
Created July 8, 2018 06:32
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iamgabrielma/8b49d8547579c8dcf3c5c483997fc042 to your computer and use it in GitHub Desktop.
Save iamgabrielma/8b49d8547579c8dcf3c5c483997fc042 to your computer and use it in GitHub Desktop.
Example of procedural level generator using the Random Walker Algorithm
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ProceduralLevelGenerator : MonoBehaviour
{
enum gridSpace { empty, floor, wall };
gridSpace[,] grid; // 2D array of grid spaces that holds information about our map
int roomHeight, roomWidth;
//Vector2 roomSizeWorldUnits = new Vector2(30, 30);
Vector2 roomSizeWorldUnits = new Vector2(100, 100); // total size of our grid
float worldUnitsInOneGridCell = 1; // conversion factor
// walkers are set on a struct so we can set 2 V2 together with dir and pos
struct walker
{
public Vector2 dir;
public Vector2 pos;
}
List<walker> walkers;
float chanceWalkerChangeDir = 0.5f, chanceWalkerSpawn = 0.05f;
float chanceWalkerDestoy = 0.05f;
//int maxWalkers = 10;
int maxWalkers = 5;
float percentToFill = 0.4f;
public GameObject wallObj, floorObj;
void Start()
{
Setup();
CreateFloors();
CreateWalls();
RemoveSingleWalls();
SpawnLevel();
}
void Setup()
{
//find grid size: How tall and width our grid should be
roomHeight = Mathf.RoundToInt(roomSizeWorldUnits.x / worldUnitsInOneGridCell);
roomWidth = Mathf.RoundToInt(roomSizeWorldUnits.y / worldUnitsInOneGridCell);
//create grid
grid = new gridSpace[roomWidth, roomHeight];
//set grid's default state
for (int x = 0; x < roomWidth - 1; x++)
{
for (int y = 0; y < roomHeight - 1; y++)
{
//make every cell empty by on building it
grid[x, y] = gridSpace.empty;
}
}
//set first walker
//init list
walkers = new List<walker>();
//create a walker
walker newWalker = new walker();
newWalker.dir = RandomDirection();
//find center of grid
Vector2 spawnPos = new Vector2(Mathf.RoundToInt(roomWidth / 2.0f),
Mathf.RoundToInt(roomHeight / 2.0f));
newWalker.pos = spawnPos;
//add walker to list
walkers.Add(newWalker);
}
void CreateFloors()
{
int iterations = 0;//loop will not run forever, we keep track of the number of iterations that are happening
do
{
//create floor at position of every walker are, so in the first loop we will only be placing a floor tile in the center of the grid, but as walkers go, we'll place floors all over
foreach (walker myWalker in walkers)
{
grid[(int)myWalker.pos.x, (int)myWalker.pos.y] = gridSpace.floor;
}
//chance: destroy walker
int numberChecks = walkers.Count; //might modify count while in this loop
for (int i = 0; i < numberChecks; i++)
{
//only if its not the only one, and at a low chance
if (Random.value < chanceWalkerDestoy && walkers.Count > 1)
{
walkers.RemoveAt(i);
break; //only destroy one per iteration
}
}
//chance: walker pick new direction
for (int i = 0; i < walkers.Count; i++)
{
if (Random.value < chanceWalkerChangeDir)
{
walker thisWalker = walkers[i];
thisWalker.dir = RandomDirection();
walkers[i] = thisWalker;
}
}
//chance: spawn new walker
numberChecks = walkers.Count; //might modify count while in this loop
for (int i = 0; i < numberChecks; i++)
{
//only if # of walkers < max, and at a low chance. We have a max so things don't get out of hand
if (Random.value < chanceWalkerSpawn && walkers.Count < maxWalkers)
{
//create a walker in the position of one of our existing walkers
walker newWalker = new walker();
newWalker.dir = RandomDirection();
newWalker.pos = walkers[i].pos;
walkers.Add(newWalker);
}
}
//move walkers. we just loop through them and add a direction to their position
for (int i = 0; i < walkers.Count; i++)
{
walker thisWalker = walkers[i];
thisWalker.pos += thisWalker.dir;
walkers[i] = thisWalker;
}
// Be sure we don't move walkers where they shouldn't be: avoid border of grid
for (int i = 0; i < walkers.Count; i++)
{
walker thisWalker = walkers[i];
//clamp x,y to leave a 1 space boarder: leave room for walls
thisWalker.pos.x = Mathf.Clamp(thisWalker.pos.x, 1, roomWidth - 2);
thisWalker.pos.y = Mathf.Clamp(thisWalker.pos.y, 1, roomHeight - 2);
walkers[i] = thisWalker;
}
//check to exit loop, there's multiple ways but here checking the percentage of grid fill is what is used
if ((float)NumberOfFloors() / (float)grid.Length > percentToFill)
{
break;
}
iterations++;
} while (iterations < 100000);
Debug.Log("ProcLevGen - CreateFloors() Iterations: " + iterations.ToString());
}
void CreateWalls()
{
//loop though every grid space
for (int x = 0; x < roomWidth - 1; x++)
{
for (int y = 0; y < roomHeight - 1; y++)
{
//if theres a floor, check the spaces around it
if (grid[x, y] == gridSpace.floor)
{
//if any surrounding spaces are empty, place a wall
if (grid[x, y + 1] == gridSpace.empty) // dir y+1
{
grid[x, y + 1] = gridSpace.wall;
}
if (grid[x, y - 1] == gridSpace.empty) // dir y-1
{
grid[x, y - 1] = gridSpace.wall;
}
if (grid[x + 1, y] == gridSpace.empty) // dir x+1
{
grid[x + 1, y] = gridSpace.wall;
}
if (grid[x - 1, y] == gridSpace.empty) // dir x-1
{
grid[x - 1, y] = gridSpace.wall;
}
}
}
}
}
void RemoveSingleWalls()
{
//loop though every grid space
for (int x = 0; x < roomWidth - 1; x++)
{
for (int y = 0; y < roomHeight - 1; y++)
{
//if theres a wall, check the spaces around it
if (grid[x, y] == gridSpace.wall)
{
//assume all space around wall are floors
bool allFloors = true;
//check each side to see if they are all floors, with this we investigate spaces around each tile
for (int checkX = -1; checkX <= 1; checkX++)
{
for (int checkY = -1; checkY <= 1; checkY++)
{
// this checks we're not checking a gridpace that's out of our range
if (x + checkX < 0 || x + checkX > roomWidth - 1 ||
y + checkY < 0 || y + checkY > roomHeight - 1)
{
//skip checks that are out of range
continue;
}
// make sure that we don't check our own space
if ((checkX != 0 && checkY != 0) || (checkX == 0 && checkY == 0))
{
//skip corners and center
continue;
}
if (grid[x + checkX, y + checkY] != gridSpace.floor)
{
allFloors = false;
}
}
}
// if the check pass,turn this grid into a floor, instead of a wall.
if (allFloors)
{
grid[x, y] = gridSpace.floor;
}
}
}
}
}
// what places all the floors and walls in, uses a nested loop structure to check all tiles in our grid, and uses a switch statement to check which kind of tile is
void SpawnLevel()
{
for (int x = 0; x < roomWidth; x++)
{
for (int y = 0; y < roomHeight; y++)
{
switch (grid[x, y])
{
case gridSpace.empty:
break;
case gridSpace.floor:
Spawn(x, y, floorObj);
break;
case gridSpace.wall:
Spawn(x, y, wallObj);
break;
}
}
}
}
Vector2 RandomDirection()
{
//pick random int between 0 and 3
int choice = Mathf.FloorToInt(Random.value * 3.99f);
//use that int to chose a direction
switch (choice)
{
case 0:
return Vector2.down;
case 1:
return Vector2.left;
case 2:
return Vector2.up;
default:
return Vector2.right;
}
}
int NumberOfFloors()
{
int count = 0;
foreach (gridSpace space in grid)
{
if (space == gridSpace.floor)
{
count++;
}
}
return count;
}
void Spawn(float x, float y, GameObject toSpawn)
{
//find the position to spawn
Vector2 offset = roomSizeWorldUnits / 2.0f; // not strictly necessary but centers the grid on scene
Vector2 spawnPos = new Vector2(x, y) * worldUnitsInOneGridCell - offset;
//spawn object
Instantiate(toSpawn, spawnPos, Quaternion.identity);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment