├── .gitignore ├── 1vs10.gif ├── 1vs10_2.gif ├── AgentBasedLevelGenerator_Tutorial.zip ├── AgentBasedLevelGenerator_Tutorial_Solution.zip ├── AgentBasedTwoLayerLevelGeneration.unitypackage ├── LICENSE ├── LevelGeneratorAgent.unitypackage ├── README.md ├── Screenshots ├── navmesh.PNG ├── snapshot1.PNG ├── snapshot2.PNG ├── snapshot3.PNG └── snapshot4_minimap.PNG └── Scripts ├── DiggingAgent.cs ├── GameManagerLD.cs ├── GridDirection.cs ├── IntVector.cs ├── LDCell.cs ├── LDRoom.cs ├── LDRoomSettings.cs ├── LevelDigger.cs └── MultiAgentDigger.cs /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /Assets/AssetStoreTools* 7 | 8 | # Visual Studio 2015 cache directory 9 | /.vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | 26 | # Unity3D generated meta files 27 | *.pidb.meta 28 | 29 | # Unity3D Generated File On Crash Reports 30 | sysinfo.txt 31 | 32 | # Builds 33 | *.apk 34 | *.unitypackage 35 | -------------------------------------------------------------------------------- /1vs10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/1vs10.gif -------------------------------------------------------------------------------- /1vs10_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/1vs10_2.gif -------------------------------------------------------------------------------- /AgentBasedLevelGenerator_Tutorial.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/AgentBasedLevelGenerator_Tutorial.zip -------------------------------------------------------------------------------- /AgentBasedLevelGenerator_Tutorial_Solution.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/AgentBasedLevelGenerator_Tutorial_Solution.zip -------------------------------------------------------------------------------- /AgentBasedTwoLayerLevelGeneration.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/AgentBasedTwoLayerLevelGeneration.unitypackage -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 DKaravolos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LevelGeneratorAgent.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/LevelGeneratorAgent.unitypackage -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AgentBasedLevelGenerator 2 | A Unity implementation of a procedural level generator based on one or more agents moving in a grid. If there are one or more agents creating the level, a simple Depth-First Search is performed to check whether the level is connected and a simple corridor is generated between the agents if necessary. The size of the map, the sizes and probabilities of the rooms and whether or not rooms can overlap are all changeable parameters of the algorithm (part of the MultiAgentDigger prefab). The generator also automatically builds a navmesh of the generated map. New maps can be generated (with different parameters if you wish) while the scene is running. 3 | 4 | # Tutorial 5 | For the Institute of Digital Games at the University of Malta, I created a tutorial in which students need to fill in the most important parts of the code. The tutorial can be found here: [link](https://github.com/DKaravolos/AgentBasedLevelGenerator/blob/master/AgentBasedLevelGenerator_Tutorial.zip). 6 | The solution can be found here: [link](https://github.com/DKaravolos/AgentBasedLevelGenerator/blob/master/AgentBasedLevelGenerator_Tutorial_Solution.zip) and is probably better documented than the main code in this repo. Sorry for that. 7 | 8 | # Hierarchical Generator 9 | A more elaborate generator, used for creating the data set of my PhD thesis can be found here: [link](https://drive.google.com/open?id=1B4m5XwY5fk6RjYIgG7YFPonKj0v3nc9c). This generator assures that there are two paths between the two bases and has two walkable floor levels. For more information, check out [my personal website](https://danielkaravolos.nl/publications/dissertation/) 10 | 11 | # ASCI Map 12 | The solution to the tutorial contains code for exporting the map to a csv-file of ASCI characters (basically 0,1 and 2 for the different floor levels). 13 | 14 | # Screenshots 15 | ![Gif 1v10](https://github.com/DKaravolos/AgentBasedLevelGenerator/blob/master/1vs10.gif) 16 | 17 | ![Gif 1vs10](https://github.com/DKaravolos/AgentBasedLevelGenerator/blob/master/1vs10_2.gif) 18 | 19 | ![Screenshot 1](/Screenshots/snapshot1.PNG?raw=true "Screenshot 1") 20 | 21 | ![Screenshot 2](/Screenshots/snapshot2.PNG?raw=true "Screenshot 2") 22 | 23 | ![Screenshot 3](/Screenshots/snapshot3.PNG?raw=true "Screenshot 3") 24 | 25 | # Minimap 26 | The code creates a render of the level as a minimap 27 | ![It has a minimap!](/Screenshots/snapshot4_minimap.PNG?raw=true "Minimap") 28 | 29 | # Navmesh 30 | The navmesh is automatically generated based on: https://github.com/Unity-Technologies/NavMeshComponents 31 | ![NavMesh](/Screenshots/navmesh.PNG?raw=true "Navmesh Example") 32 | -------------------------------------------------------------------------------- /Screenshots/navmesh.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/Screenshots/navmesh.PNG -------------------------------------------------------------------------------- /Screenshots/snapshot1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/Screenshots/snapshot1.PNG -------------------------------------------------------------------------------- /Screenshots/snapshot2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/Screenshots/snapshot2.PNG -------------------------------------------------------------------------------- /Screenshots/snapshot3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/Screenshots/snapshot3.PNG -------------------------------------------------------------------------------- /Screenshots/snapshot4_minimap.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DKaravolos/AgentBasedLevelGenerator/14e38f58c95fb4a4a37d2dd9af40ed92b0d88128/Screenshots/snapshot4_minimap.PNG -------------------------------------------------------------------------------- /Scripts/DiggingAgent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections; 3 | 4 | public class DiggingAgent 5 | { 6 | public IntVector2 pos; 7 | public GridDirection direction; 8 | public float turnProb; 9 | public float roomProb; 10 | public LDCell CurrentCell { get { return level.GetCell(pos); } } 11 | public int stepsDone; 12 | 13 | protected float base_changeProb; 14 | protected float base_roomprob; 15 | protected LevelDigger level; 16 | 17 | //Material indicatorColor; 18 | 19 | public DiggingAgent(LevelDigger _level, IntVector2 position, GridDirection init_direction, float init_changeProb, float init_roomProb) 20 | { 21 | level = _level; 22 | pos = position; 23 | direction = init_direction; 24 | base_changeProb = turnProb = init_changeProb; 25 | base_roomprob = roomProb = init_roomProb; 26 | stepsDone = 0; 27 | } 28 | 29 | public void OpenCurrentCell() 30 | { 31 | if(!CurrentCell.IsOpen) 32 | CurrentCell.SetOpen(true); 33 | } 34 | 35 | public void Highlight() 36 | { 37 | CurrentCell.Highlight(); 38 | } 39 | 40 | public void UnHighlight() 41 | { 42 | CurrentCell.UnHighlight(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Scripts/GameManagerLD.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine.AI; 4 | using UnityEngine; 5 | 6 | public class GameManagerLD : MonoBehaviour 7 | { 8 | public LevelDigger diggerPrefab; 9 | public NavMeshSurface navmeshPrefab; 10 | 11 | //Privates 12 | private LevelDigger diggerInstance; 13 | private NavMeshSurface navmesh; 14 | 15 | private void Start() 16 | { 17 | StartCoroutine(BeginGame()); 18 | } 19 | 20 | private void Update() 21 | { 22 | if (Input.GetKeyDown(KeyCode.Space)) 23 | { 24 | RestartGame(); 25 | } 26 | if (Input.GetKeyDown(KeyCode.B)) 27 | { 28 | navmesh.BuildNavMesh(); 29 | } 30 | } 31 | 32 | private IEnumerator BeginGame() 33 | { 34 | //Setup main camera 35 | Camera.main.rect = new Rect(0f, 0f, 1f, 1f); 36 | Camera.main.clearFlags = CameraClearFlags.Skybox; 37 | 38 | //Create the level 39 | diggerInstance = Instantiate(diggerPrefab) as LevelDigger; 40 | diggerInstance.transform.position = Vector3.zero; 41 | yield return StartCoroutine(diggerInstance.Generate()); 42 | 43 | //Setup minimap camera 44 | Camera.main.rect = new Rect(0f, 0f, 0.4f, 0.4f); 45 | Camera.main.clearFlags = CameraClearFlags.Depth; 46 | 47 | //Build Navmesh 48 | navmesh = Instantiate(navmeshPrefab) as NavMeshSurface; 49 | navmesh.BuildNavMesh(); 50 | } 51 | 52 | private void RestartGame() 53 | { 54 | StopAllCoroutines(); 55 | Destroy(diggerInstance.gameObject); 56 | Destroy(navmesh.gameObject); 57 | StartCoroutine(BeginGame()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Scripts/GridDirection.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public enum GridDirection 4 | { 5 | North, 6 | East, 7 | South, 8 | West 9 | } 10 | 11 | public static class GridDirections 12 | { 13 | public const int Count = 4; 14 | public static GridDirection RandomValue 15 | { 16 | get 17 | { 18 | return (GridDirection)Random.Range(0, Count); 19 | } 20 | } 21 | 22 | private static IntVector2[] vectors = { 23 | new IntVector2(0, 1), 24 | new IntVector2(1, 0), 25 | new IntVector2(0, -1), 26 | new IntVector2(-1, 0) 27 | }; 28 | 29 | private static GridDirection[] opposites = { 30 | GridDirection.South, 31 | GridDirection.West, 32 | GridDirection.North, 33 | GridDirection.East 34 | }; 35 | 36 | public static GridDirection GetOpposite(this GridDirection direction) 37 | { 38 | return opposites[(int)direction]; 39 | } 40 | 41 | public static IntVector2 ToIntVector2(this GridDirection direction) 42 | { 43 | return vectors[(int)direction]; 44 | } 45 | 46 | private static Quaternion[] rotations = { 47 | Quaternion.identity, 48 | Quaternion.Euler(0f, 90f, 0f), 49 | Quaternion.Euler(0f, 180f, 0f), 50 | Quaternion.Euler(0f, 270f, 0f) 51 | }; 52 | 53 | public static Quaternion ToRotation(this GridDirection direction) 54 | { 55 | return rotations[(int)direction]; 56 | } 57 | 58 | public static GridDirection GetNextClockwise(this GridDirection direction) 59 | { 60 | return (GridDirection)(((int)direction + 1) % Count); 61 | } 62 | 63 | public static GridDirection GetNextCounterclockwise(this GridDirection direction) 64 | { 65 | return (GridDirection)(((int)direction + Count - 1) % Count); 66 | } 67 | } -------------------------------------------------------------------------------- /Scripts/IntVector.cs: -------------------------------------------------------------------------------- 1 | [System.Serializable] 2 | public struct IntVector2 3 | { 4 | public int x, z; 5 | 6 | public IntVector2(int x, int z) 7 | { 8 | this.x = x; 9 | this.z = z; 10 | } 11 | 12 | public static IntVector2 operator +(IntVector2 a, IntVector2 b) 13 | { 14 | a.x += b.x; 15 | a.z += b.z; 16 | return a; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Scripts/LDCell.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | public class LDCell : MonoBehaviour 4 | { 5 | public IntVector2 coordinates; 6 | public bool IsOpen { get { return !closedForm.activeInHierarchy; } } 7 | public bool CanChangeColor { get; set; } 8 | [HideInInspector] 9 | public LDRoom room; 10 | [SerializeField] 11 | private GameObject closedForm; 12 | [SerializeField] 13 | private GameObject openForm; 14 | [SerializeField] 15 | private GameObject indicator; 16 | 17 | public LDCell() 18 | { 19 | ////we should be able to find these automatically, but I get a weird bug. 20 | //closedForm = transform.GetChild(0).gameObject; 21 | //openForm = transform.GetChild(1).gameObject; 22 | //indicator = transform.GetChild(2).gameObject; 23 | room = null; 24 | CanChangeColor = true; 25 | } 26 | 27 | public void SetOpen(bool open) 28 | { 29 | closedForm.SetActive(!open); 30 | openForm.SetActive(open); 31 | } 32 | 33 | public void AddToRoom(LDRoom _room, bool fixColor) 34 | { 35 | room = _room; 36 | room.Add(this); 37 | openForm.SetActive(true); 38 | closedForm.SetActive(false); 39 | if(CanChangeColor) 40 | { 41 | openForm.GetComponent().material = room.settings.floorMaterial; // We only have a floor 42 | CanChangeColor = fixColor; 43 | } 44 | } 45 | 46 | public void Highlight() 47 | { 48 | indicator.SetActive(true); 49 | } 50 | public void UnHighlight() 51 | { 52 | indicator.SetActive(false); 53 | } 54 | 55 | //We might want to not show a cell at all if it is not a usable part of the map 56 | public void Show() 57 | { 58 | gameObject.SetActive(true); 59 | } 60 | 61 | public void Hide() 62 | { 63 | gameObject.SetActive(false); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Scripts/LDRoom.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | public class LDRoom : ScriptableObject 5 | { 6 | 7 | public int settingsIndex; 8 | 9 | public LDRoomSettings settings; 10 | 11 | private List cells = new List(); 12 | public List ReadOnlyCells 13 | { 14 | get { return cells; } 15 | } 16 | 17 | public void Add(LDCell cell) 18 | { 19 | cell.room = this; 20 | cells.Add(cell); 21 | } 22 | 23 | //We might want to not show a room at all if it is not a usable part of the map or for other reasons... 24 | public void Hide() 25 | { 26 | for (int i = 0; i < cells.Count; i++) 27 | { 28 | cells[i].Hide(); 29 | } 30 | } 31 | 32 | public void Show() 33 | { 34 | for (int i = 0; i < cells.Count; i++) 35 | { 36 | cells[i].Show(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Scripts/LDRoomSettings.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | 4 | [Serializable] 5 | public class LDRoomSettings 6 | { 7 | 8 | public Material floorMaterial; //, wallMaterial; 9 | } -------------------------------------------------------------------------------- /Scripts/LevelDigger.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class LevelDigger : MonoBehaviour 6 | { 7 | //Public 8 | [Header("Prefab stuff")] 9 | public LDCell cellPrefab; 10 | public GameObject undergroundPrefab; 11 | public LDRoomSettings[] roomSettings; 12 | public bool onlyFirstColor = true; 13 | 14 | [Header("Display Properties")] 15 | public float generationStepDelay = 0.01f; 16 | 17 | [Header("Level Size Properties")] 18 | public Vector3 cellScale = new Vector3(1, 1, 1); 19 | public IntVector2 size; 20 | 21 | [Header("Digging Agent Properties")] 22 | public int maxNrSteps = 50; 23 | 24 | [Range(0f, 1f)] 25 | public float changeDirectionProb; 26 | [Range(0f, 1f)] 27 | public float makeRoomProb; 28 | public bool dynamicProbabilities = true; 29 | public float changeDirDelta = 0.05f; 30 | public float makeRoomDelta = 0.05f; 31 | 32 | public int minRoomSize = 3; 33 | public int maxRoomSize = 9; 34 | 35 | public bool startWithRoom = true; 36 | public bool endWithRoom = false; 37 | public bool fixStartPosition; 38 | public IntVector2 startPosition = new IntVector2(0, 0); 39 | 40 | 41 | //Privates 42 | protected LDCell[,] cells; 43 | protected GameObject underground; 44 | protected List rooms = new List(); 45 | 46 | public IntVector2 RandomCoordinates 47 | { 48 | get 49 | { 50 | return new IntVector2(Random.Range(0, size.x), Random.Range(0, size.z)); 51 | } 52 | } 53 | 54 | public int[] RandomRoomSize 55 | { 56 | get 57 | { 58 | int x = Random.Range(minRoomSize, maxRoomSize); 59 | int z = Random.Range(minRoomSize, maxRoomSize); 60 | int[] array = { x, z }; 61 | return array; 62 | } 63 | } 64 | 65 | public bool ContainsCoordinates(IntVector2 coordinate) 66 | { 67 | return coordinate.x >= 0 && coordinate.x < size.x && coordinate.z >= 0 && coordinate.z < size.z; 68 | } 69 | 70 | public LDCell GetCell(IntVector2 coordinates) 71 | { 72 | return cells[coordinates.x, coordinates.z]; 73 | } 74 | 75 | virtual protected void Init() 76 | { 77 | cells = new LDCell[size.x, size.z]; 78 | for (int i = 0; i < size.x; i++) 79 | { 80 | for (int j = 0; j < size.z; j++) 81 | { 82 | IntVector2 coordinates = new IntVector2(i, j); 83 | CreateCell(coordinates, cellScale, false); 84 | } 85 | } 86 | //This creates an object below our level. Mainly for debugging size/position problems. 87 | underground = Instantiate(undergroundPrefab); 88 | underground.transform.parent = transform; 89 | underground.transform.localScale = new Vector3(size.x * cellScale.x, 1, size.z * cellScale.z); 90 | } 91 | 92 | //This is the mainloop called by the GameManager 93 | virtual public IEnumerator Generate() 94 | { 95 | WaitForSeconds delay = new WaitForSeconds(generationStepDelay); 96 | Init(); 97 | 98 | //Note that the single-agent level digger does not actually have an agent. 99 | //It has separate variables for position, direction, etc. 100 | IntVector2 agentPosition; 101 | GridDirection agentDirection = GridDirections.RandomValue; 102 | if (fixStartPosition) 103 | agentPosition = startPosition; 104 | else 105 | agentPosition = RandomCoordinates; 106 | LDCell currCell = GetCell(agentPosition); 107 | 108 | int steps = 0; 109 | //Poor man's "Create First Spawn Room" 110 | if (startWithRoom) 111 | { 112 | rooms.Add(CreateRoom(agentPosition, RandomRoomSize)); 113 | currCell.Highlight(); 114 | steps++; 115 | yield return delay; 116 | } 117 | 118 | //Main Loop 119 | float turnProb = changeDirectionProb; 120 | float roomProb = makeRoomProb; 121 | while (steps < maxNrSteps) 122 | { 123 | Debug.Log("Step " + steps); 124 | currCell.Highlight(); 125 | yield return delay; 126 | currCell.UnHighlight(); 127 | DoNextGenerationStep(ref agentPosition, ref agentDirection, ref currCell, ref turnProb, ref roomProb); 128 | steps++; 129 | } 130 | 131 | //Poor man's "Create Second Spawn Room" 132 | if (endWithRoom) 133 | { 134 | rooms.Add(CreateRoom(agentPosition, RandomRoomSize)); 135 | } 136 | 137 | //Make everything static for navmesh generation 138 | foreach (LDCell cell in cells) 139 | { 140 | cell.gameObject.isStatic = true; 141 | } 142 | 143 | yield return delay; 144 | } 145 | 146 | virtual protected void DoNextGenerationStep(ref IntVector2 currentPosition, ref GridDirection currentDirection, ref LDCell currentCell, 147 | ref float turnProb, ref float roomProb) 148 | { 149 | //First we move the agent. 150 | //We change direction by chance or if the next position in the current direction is not in the level. 151 | if (Random.value < changeDirectionProb || !ContainsCoordinates(currentPosition + currentDirection.ToIntVector2())) 152 | { 153 | //Randomly move the agent to a new valid position in the level. 154 | GridDirection newDir = ChangeDirection(currentPosition, currentDirection); 155 | currentDirection = newDir; 156 | turnProb = changeDirectionProb; 157 | } 158 | else 159 | if(dynamicProbabilities) 160 | turnProb += changeDirDelta; 161 | //Now we now the next position! 162 | currentPosition += currentDirection.ToIntVector2(); 163 | 164 | //Make a room? 165 | if(Random.value < roomProb) 166 | { 167 | rooms.Add(CreateRoom(currentPosition, RandomRoomSize)); 168 | roomProb = makeRoomProb; 169 | } 170 | else 171 | { 172 | //else just open current cell 173 | currentCell = GetCell(currentPosition); 174 | if (!currentCell.IsOpen) 175 | currentCell.SetOpen(true); 176 | 177 | if (dynamicProbabilities) 178 | roomProb += makeRoomDelta; 179 | } 180 | 181 | } 182 | 183 | protected GridDirection ChangeDirection(IntVector2 currentPosition, GridDirection currentDirection) 184 | { 185 | IntVector2 newPos; 186 | GridDirection newDir; 187 | do 188 | { 189 | newDir = GridDirections.RandomValue; 190 | newPos = currentPosition + newDir.ToIntVector2(); 191 | } while (newDir == currentDirection || !ContainsCoordinates(newPos)); 192 | return newDir; 193 | } 194 | 195 | //The CreateCell is only used at the start to setup the complete grid. 196 | //After that, you can just access the cell and open or close it 197 | //Special cell content should probably be added when creating "rooms". 198 | protected LDCell CreateCell(IntVector2 coordinates, Vector3 cellScale, bool open) 199 | { 200 | LDCell newCell = Instantiate(cellPrefab) as LDCell; 201 | newCell.SetOpen(open); 202 | newCell.transform.localScale = cellScale; 203 | cells[coordinates.x, coordinates.z] = newCell; 204 | newCell.name = "LD Cell " + coordinates.x + ", " + coordinates.z; 205 | newCell.coordinates = coordinates; 206 | newCell.transform.parent = transform; 207 | newCell.transform.localPosition = new Vector3(cellScale.x * (coordinates.x - size.x * 0.5f + 0.5f), cellScale.y * 0.5f, cellScale.z * (coordinates.z - size.z * 0.5f + 0.5f)); 208 | return newCell; 209 | } 210 | 211 | //This function creates rooms. Currently it is only used to give tiles a color. 212 | //fixColor indicates whether the color of the cell can change after it is assigned to this room. 213 | protected LDRoom CreateRoom(IntVector2 currentPos, int[] roomSize, bool fixColor=false) 214 | { 215 | LDRoomSettings setting; 216 | if (onlyFirstColor) 217 | setting = roomSettings[0]; 218 | else 219 | setting = roomSettings[Random.Range(0, roomSettings.Length)]; 220 | return CreateRoom(currentPos, roomSize, setting, fixColor); 221 | } 222 | 223 | //This function creates rooms with a specified setting. Currently it is only used to give tiles a color. 224 | protected LDRoom CreateRoom(IntVector2 currentPos, int[] roomSize, LDRoomSettings setting, bool fixColor) 225 | { 226 | 227 | LDRoom newRoom = ScriptableObject.CreateInstance(); 228 | //Check if there already is a room here. In that case, we want to add the new cells to that room 229 | //If there is a room, it is stored in newRoom 230 | if (!OverlapsRoom(currentPos, roomSize, ref newRoom)) 231 | { 232 | Debug.Log(string.Format("Creating Room of [{0}, {1}], color: {2}", roomSize[0], roomSize[1], setting.floorMaterial.name)); 233 | newRoom.settings = setting; 234 | } 235 | else 236 | Debug.Log("Expanding Room."); 237 | 238 | //Add cells to the room, whether it is new or not 239 | for (int x = currentPos.x - roomSize[0]/ 2; x < currentPos.x + roomSize[0] / 2; x++) 240 | for (int z = currentPos.z - roomSize[1] / 2; z < currentPos.z + roomSize[1] / 2; z++) 241 | { 242 | if (ContainsCoordinates(new IntVector2(x,z))) 243 | cells[x, z].AddToRoom(newRoom, fixColor); 244 | } 245 | return newRoom; 246 | } 247 | 248 | protected bool OverlapsRoom(IntVector2 currentPos, int[] roomSize, ref LDRoom room) 249 | { 250 | for (int x = currentPos.x - roomSize[0] / 2; x < currentPos.x + roomSize[0] / 2; x++) 251 | for (int z = currentPos.z - roomSize[1] / 2; z < currentPos.z + roomSize[1] / 2; z++) 252 | { 253 | if (ContainsCoordinates(new IntVector2(x, z))) 254 | if (cells[x, z].room != null) 255 | { 256 | room = cells[x, z].room; 257 | return true; 258 | } 259 | } 260 | return false; 261 | } 262 | 263 | //Use a simple Depth-First Search to check if there is a path between two points 264 | protected bool HasPathBetween(IntVector2 firstPos, IntVector2 secondPos, List visited) 265 | { 266 | if (!ContainsCoordinates(firstPos)) 267 | return false; 268 | 269 | if (!GetCell(firstPos).IsOpen || visited.Contains(firstPos)) 270 | return false; 271 | 272 | visited.Add(firstPos); 273 | if (firstPos.x == secondPos.x && firstPos.z == secondPos.z) 274 | { 275 | return true; 276 | } 277 | if (HasPathBetween(firstPos + GridDirection.North.ToIntVector2(), secondPos, visited)) {return true; } 278 | if (HasPathBetween(firstPos + GridDirection.East.ToIntVector2(), secondPos, visited)) { return true; } 279 | if (HasPathBetween(firstPos + GridDirection.South.ToIntVector2(), secondPos, visited)) { return true; } 280 | if (HasPathBetween(firstPos + GridDirection.West.ToIntVector2(), secondPos, visited)) { return true; } 281 | return false; 282 | } 283 | 284 | //Opens all cells between two points, based on a simple greedy algorithm: first move in X direction, then move in Z direction 285 | protected void ConnectPositions(IntVector2 pos1, IntVector2 pos2) 286 | { 287 | int xDiff = pos2.x - pos1.x; 288 | if(xDiff > 0) 289 | { 290 | for (int i = 0; i < xDiff; i++) 291 | { 292 | pos1 += GridDirection.East.ToIntVector2(); 293 | GetCell(pos1).SetOpen(true); 294 | } 295 | } 296 | if (xDiff < 0) 297 | { 298 | for (int i = 0; i > xDiff; i--) 299 | { 300 | pos1 += GridDirection.West.ToIntVector2(); 301 | GetCell(pos1).SetOpen(true); 302 | } 303 | } 304 | 305 | int zDiff = pos2.z - pos1.z; 306 | if (zDiff > 0) 307 | { 308 | for (int i = 0; i < zDiff; i++) 309 | { 310 | pos1 += GridDirection.North.ToIntVector2(); 311 | GetCell(pos1).SetOpen(true); 312 | } 313 | } 314 | if (zDiff < 0) 315 | { 316 | for (int i = 0; i > zDiff; i--) 317 | { 318 | pos1 += GridDirection.South.ToIntVector2(); 319 | GetCell(pos1).SetOpen(true); 320 | } 321 | } 322 | 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /Scripts/MultiAgentDigger.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections; 3 | using UnityEngine; 4 | 5 | public class MultiAgentDigger : LevelDigger { 6 | public List agentPositions; //You specify both the nr of agents and their start positions in the editor. 7 | public List startRoomSizes; 8 | public List startDirections; 9 | public bool randomStartRooms = false; 10 | public bool colorStartRooms = true; 11 | 12 | protected List agents; //This will keep track of the agents. 13 | 14 | protected override void Init() 15 | { 16 | base.Init(); 17 | agents = new List(agentPositions.Count); 18 | for (int agent = 0; agent < agentPositions.Count; agent++) 19 | { 20 | GridDirection dir; 21 | if (startDirections != null) 22 | dir = startDirections[agent]; //We are assuming that if startDirections exists, its size is equal to nr of agents. 23 | else 24 | dir = GridDirections.RandomValue; 25 | DiggingAgent da = new DiggingAgent(this, agentPositions[agent], dir, changeDirectionProb, makeRoomProb); 26 | agents.Add(da); 27 | } 28 | } 29 | 30 | public override IEnumerator Generate() 31 | { 32 | WaitForSeconds delay = new WaitForSeconds(generationStepDelay); 33 | Init(); 34 | 35 | //Create spawn rooms 36 | if (startWithRoom) 37 | { 38 | for (int agent = 0; agent < agents.Count; agent++) 39 | { 40 | int[] roomSize; 41 | if (randomStartRooms) 42 | roomSize = RandomRoomSize; 43 | else 44 | roomSize = new int[]{ startRoomSizes[agent].x, startRoomSizes[agent].z}; 45 | 46 | rooms.Add(CreateRoom(agents[agent].pos, roomSize, roomSettings[agent+1], false)); 47 | agents[agent].CurrentCell.Highlight(); 48 | agents[agent].stepsDone++; 49 | } 50 | yield return delay; 51 | } 52 | 53 | //This is the main loop that digs the level. 54 | int steps = 0; 55 | while (steps < maxNrSteps) 56 | { 57 | Debug.Log("Step " + steps); 58 | //Show where the agents are 59 | foreach (var agent in agents) 60 | agent.Highlight(); 61 | //pauze this process for visualization in the editor 62 | yield return delay; 63 | //Stop showing where the agents are and have each agent perform one step 64 | foreach (var agent in agents) 65 | { 66 | agent.UnHighlight(); 67 | DoNextGenerationStep(agent); 68 | } 69 | steps++; 70 | } 71 | 72 | //Stand in for create second spawn room // probably should not do this with MultiAgentDigging 73 | if (endWithRoom) 74 | { 75 | for (int agent = 0; agent < agents.Count; agent++) 76 | { 77 | rooms.Add(CreateRoom(agents[agent].pos, RandomRoomSize)); 78 | } 79 | } 80 | 81 | //Check whether paths are connected and if not, make a path 82 | CheckConnectedness(); 83 | 84 | //Make everything static for navmesh generation 85 | foreach (LDCell cell in cells) 86 | { 87 | cell.gameObject.isStatic = true; 88 | } 89 | 90 | yield return delay; 91 | } 92 | 93 | protected void DoNextGenerationStep(DiggingAgent agent) 94 | { 95 | //First we move the agent. 96 | //We change direction by chance or if the next position in the current direction is not in the level. 97 | if (Random.value < changeDirectionProb || !ContainsCoordinates(agent.pos + agent.direction.ToIntVector2())) 98 | { 99 | //Randomly move the agent to a new valid position in the level. 100 | GridDirection newDir = ChangeDirection(agent.pos, agent.direction); 101 | agent.direction = newDir; 102 | agent.turnProb = changeDirectionProb; 103 | } 104 | else 105 | if (dynamicProbabilities) 106 | agent.turnProb += changeDirDelta; 107 | //Now we now the next position! 108 | agent.pos += agent.direction.ToIntVector2(); 109 | 110 | //Make a room? 111 | if (Random.value < agent.roomProb) 112 | { 113 | rooms.Add(CreateRoom(agent.pos, RandomRoomSize)); 114 | agent.roomProb = makeRoomProb; 115 | } 116 | else 117 | { 118 | //else just open current cell 119 | agent.OpenCurrentCell(); 120 | if (dynamicProbabilities) 121 | agent.roomProb += makeRoomDelta; 122 | } 123 | 124 | } 125 | 126 | protected void CheckConnectedness() 127 | { 128 | if(agents.Count == 1) 129 | return; 130 | if(agents.Count > 2) 131 | { 132 | Debug.LogWarning("Connectedness checking is not implemented for more than two agents"); 133 | return; 134 | } 135 | List visited = new List(); 136 | if(HasPathBetween(agents[0].pos, agents[1].pos, visited)) 137 | { 138 | Debug.Log("The map is connected."); 139 | } 140 | else 141 | { 142 | Debug.Log("Connecting the map..."); 143 | ConnectPositions(agents[0].pos, agents[1].pos); 144 | Debug.Log("Connecting map is done!"); 145 | } 146 | } 147 | } 148 | --------------------------------------------------------------------------------