Created
July 8, 2018 06:32
-
-
Save iamgabrielma/8b49d8547579c8dcf3c5c483997fc042 to your computer and use it in GitHub Desktop.
Example of procedural level generator using the Random Walker Algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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