From 1035373558ecce3b592493808726ef97af1f73e9 Mon Sep 17 00:00:00 2001 From: Feinte75 Date: Sat, 24 Oct 2015 15:11:48 -0400 Subject: [PATCH] Story #3 Enhanced level generation - Now more stable - Added level abstraction level class composed of rooms - The path from spawn to end is now open (no walls) --- Assets/Prefab/Environment/CubicWall.prefab | Bin 8216 -> 9232 bytes Assets/Scene/SceneGenerator.unity | Bin 9828 -> 10004 bytes Assets/Scripts/ProceduralGeneration/Facing.cs | 33 +++ .../ProceduralGeneration/Facing.cs.meta | 8 + Assets/Scripts/ProceduralGeneration/Level.cs | 36 +++ .../ProceduralGeneration/Level.cs.meta | 8 + .../LevelGenerationStrategy.cs | 11 + .../LevelGenerationStrategy.cs.meta | 8 + .../ProceduralGeneration/LevelGenerator.cs | 225 ++++++++---------- .../RecursiveGeneration.cs | 204 ++++++++++++++++ .../RecursiveGeneration.cs.meta | 8 + Assets/Scripts/ProceduralGeneration/Room.cs | 61 +++-- 12 files changed, 450 insertions(+), 152 deletions(-) create mode 100644 Assets/Scripts/ProceduralGeneration/Facing.cs create mode 100644 Assets/Scripts/ProceduralGeneration/Facing.cs.meta create mode 100644 Assets/Scripts/ProceduralGeneration/Level.cs create mode 100644 Assets/Scripts/ProceduralGeneration/Level.cs.meta create mode 100644 Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs create mode 100644 Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs.meta create mode 100644 Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs create mode 100644 Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs.meta diff --git a/Assets/Prefab/Environment/CubicWall.prefab b/Assets/Prefab/Environment/CubicWall.prefab index 2c188516384e033b40c77a9c21af8e0e35ac6345..c176e4a16609bd77fd06f709cc205ff6a27cadce 100644 GIT binary patch delta 565 zcmbQ?Fu_BSfkE*(1A~eH0|Nsm1A`KfVqh@QGt;w3Gi3Pp|NnpXjfy_PDz-pTr~C@% z{G6Q3l++?47Y3)q;#3ARuyP2&xVc~WJR@tUXI_cLPmQD$O}k&7KeZaj>~zyVarGg*^Gg^_piLn)QX9ZWovdH6XdKM)nQ@_R9Peo<6an=^!ebJnv11%(8ez*4P15@b6Fe_F!E z&;g`FfEZ*12wz&H%F~dQTfkDoKfk8onfq{V&$PNI~3?_PJdKPJh4FCTB|IfBj(MNbQhxj+nTsEMT zkRTIC1w$neGeYsFC2R~0Kz0Zavq15sMam2}fb0Yy7KdV?r$!7GKusz@KG0q;7TTH2 zkN^~O0P-E7ctQGi)&-O66(>vCNEKXF!_kG4*&}5Di{C& diff --git a/Assets/Scene/SceneGenerator.unity b/Assets/Scene/SceneGenerator.unity index 55ca777cff49062b9ab999daa1c131eacc3ac296..0a4352ba24a2f578f807fef30a6990df4e418f08 100644 GIT binary patch delta 211 zcmaFjGsRDUfk9;f1B1E<0|NsmkbPjHfIXwY6Y{Zx;E< z*JXK`I20!DVUc5001GREgc+44OS7smDo=J|RcBP$oW&X~ClmwJ`5y?1fb{;xE)E8! o$=8*mHyfy&VU%S8itz(60xWP~U|8V51r*?#ET|$sSwqbR0KpG6-T(jq delta 135 zcmbQ@_rynlfkB0dfk7>Wfq{V&$X1vrV9&_6F`$rT@>>?p$r-Eyj53pXSmhaICpYrR zGs*!;;mK!Mg&E~1JBkWV{>&=Er~qPZmS*#i6AA#D@*fC_fb{;xE{+e3lS@^iH(ydY c#VE)H6k}jG-~hssK$hg>jVjWUZ>ZS-0HzWnX#fBK diff --git a/Assets/Scripts/ProceduralGeneration/Facing.cs b/Assets/Scripts/ProceduralGeneration/Facing.cs new file mode 100644 index 0000000..3f11303 --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/Facing.cs @@ -0,0 +1,33 @@ +using System; +using UnityEngine; + +namespace Procedural +{ + public enum Facing + { + NORTH, + SOUTH, + EAST, + WEST + } + + public static class Extension { + public static Facing Opposite(this Facing facing) { + switch (facing) { + case Facing.EAST: + return Facing.WEST; + case Facing.WEST: + return Facing.EAST; + case Facing.NORTH: + return Facing.SOUTH; + case Facing.SOUTH: + return Facing.NORTH; + + default: + return Facing.NORTH; + } + } + } + +} + diff --git a/Assets/Scripts/ProceduralGeneration/Facing.cs.meta b/Assets/Scripts/ProceduralGeneration/Facing.cs.meta new file mode 100644 index 0000000..c9a86a4 --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/Facing.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cbd906117adcb354c8f654246b1583cd +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Scripts/ProceduralGeneration/Level.cs b/Assets/Scripts/ProceduralGeneration/Level.cs new file mode 100644 index 0000000..585c8a2 --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/Level.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Procedural +{ + public class Level + { + private int levelWidth = 10; + private int levelHeight = 10; + private List layout; + private Room[,] rooms; + private Vector2 endPosition = new Vector2 (6, 6); + + public Level (int levelWidth, int levelHeight, List layout, Room[,] rooms) + { + this.levelWidth = levelWidth; + this.levelHeight = levelHeight; + this.layout = layout; + this.rooms = rooms; + } + + public List Layout { + get { + return this.layout; + } + } + + public Room[,] Rooms { + get { + return this.rooms; + } + } + } +} + diff --git a/Assets/Scripts/ProceduralGeneration/Level.cs.meta b/Assets/Scripts/ProceduralGeneration/Level.cs.meta new file mode 100644 index 0000000..1f089ee --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/Level.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9a762d16008f44b48b78a054da19ffa3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs b/Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs new file mode 100644 index 0000000..73cb442 --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs @@ -0,0 +1,11 @@ +using System; +using UnityEngine; + +namespace Procedural +{ + public interface LevelGenerationStrategy + { + Level generateLevel(); + } +} + diff --git a/Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs.meta b/Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs.meta new file mode 100644 index 0000000..83f98d9 --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/LevelGenerationStrategy.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 75e08237e61e1434c9631c8f2f738355 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Scripts/ProceduralGeneration/LevelGenerator.cs b/Assets/Scripts/ProceduralGeneration/LevelGenerator.cs index 8f34f91..dd0f814 100644 --- a/Assets/Scripts/ProceduralGeneration/LevelGenerator.cs +++ b/Assets/Scripts/ProceduralGeneration/LevelGenerator.cs @@ -1,149 +1,110 @@ using UnityEngine; +using System; using System.Collections.Generic; -public class LevelGenerator : MonoBehaviour +namespace Procedural { - [Tooltip("Put here Wall Prefab ! ")] - public Transform wall; - public int minimumDistance = 6; - public int maximumDistance = 10; - public Vector2 endPosition = new Vector2(6, 6); - public int levelWidth = 10; - public int levelHeight = 10; - - private Room[,] level; - private List layout; - - private Transform environment; // Stores the roo object for the environment - - enum Facing - { - NORTH, - SOUTH, - EAST, - WEST - } - - // Use this for initialization - void Start() - { - int nbIter = 0; - float elapsedTime = Time.realtimeSinceStartup; - - GameObject obj = new GameObject ("Environment"); - environment = obj.GetComponent (); - - level = new Room[levelWidth, levelHeight]; - layout = new List (10); - while (findPath(0, 0, minimumDistance) == false) { - nbIter ++; - if(nbIter > 2) { - Debug.Log("Path not found"); - break; - - } + public class LevelGenerator : MonoBehaviour + { + [Tooltip("Put here Wall Prefab ! ")] + public Transform wall; + public int minimumDistance = 6; + public int maximumDistance = 10; + public Vector2 startingPosition = new Vector2 (0, 0); + public Vector2 endPosition = new Vector2 (6, 6); + public int levelWidth = 10; + public int levelHeight = 10; + public LevelGenerationStrategy levelGenerationStrategy; + + private Transform environment; // Stores the roo object for the environment + // Use this for initialization + void Start () + { + int nbIter = 0; + float elapsedTime = Time.realtimeSinceStartup; + + GameObject obj = new GameObject ("Environment"); + environment = obj.GetComponent (); + + levelGenerationStrategy = new RecursiveGeneration (levelWidth, levelHeight, startingPosition, endPosition, minimumDistance, maximumDistance); + Level level = levelGenerationStrategy.generateLevel (); + + foreach (Vector2 v in level.Layout) { + generateRoom (level.Rooms[(int)v.x, (int)v.y]); + } } - elapsedTime = Time.realtimeSinceStartup - elapsedTime; - Debug.Log ("Number of iteration : " + nbIter); - Debug.Log ("Generation time : " + elapsedTime); - Debug.Log ("Number of Rooms : " + layout.Count); - Debug.Log ("Rooms Layout: "); - foreach(Vector2 v in layout) { - Debug.Log(v); - generateSquare(10, 10, level[(int)v.x, (int)v.y].getPosition() * 10); + public void instanciateWall (Vector2 position) + { + Transform wallInstance = (Transform)Instantiate (wall, new Vector3 (position.x, position.y, 0), Quaternion.identity); + wallInstance.localScale = new Vector2 (0.5f, 0.5f); + wallInstance.SetParent (environment); } - - } - - private bool findPath(int x, int y, int minDistance) - { - Debug.Log("Starting find Path : x = " + x +" y = " + y + " distance = " + minDistance); - - if ((x == endPosition.x && y == endPosition.y) && (minDistance <= 0) && (minDistance > -(maximumDistance - minimumDistance))) { - addRoomToLayout (x, y); - return true; - } - - if (!validPosition(x, y)) - return false; - - addRoomToLayout (x, y); - - Facing r = (Facing)Random.Range(0, 4); - Debug.Log("r : " + r); - switch (r) - { - case Facing.NORTH: - if (findPath(x, y + 1, minDistance - 1) == true) return true; - goto case Facing.SOUTH; - - case Facing.SOUTH: - if (findPath(x, y - 1, minDistance - 1) == true) return true; - goto case Facing.EAST; - - case Facing.EAST: - if (findPath(x + 1, y, minDistance - 1) == true) return true; - goto case Facing.WEST; - - case Facing.WEST: - if (findPath(x - 1, y, minDistance - 1) == true) return true; - break; - default: - Debug.Log("Error"); - break; - } - layout.RemoveAt(layout.Count - 1); - level [x, y] = null; - return false; - } - - private void addRoomToLayout(int x, int y) { - layout.Add(new Vector2(x, y)); - level [x, y] = new Room (new Vector2 (x, y)); - } - - private bool validPosition(int x, int y) - { - foreach(Vector2 position in layout) { - if(position.x == x && position.y == y) - return false; + /** + * Generate a bordered square (not filled) beginning at position + */ + private void generateSquare (int width, int height, Vector2 position) + { + + for (int i = 0; i <= width; i++) { + for (int j = 0; j <= height; j++) { + if (i == 0 || i == width || j == 0 || j == height) { + + Transform wallInstance = (Transform)Instantiate (wall, new Vector3 (i + position.x, j + position.y, 0), Quaternion.identity); + wallInstance.SetParent (environment); + } + } + } } - return (((x >= 0) && (x < levelWidth)) && ((y >= 0) && (y < levelHeight))); - } + private void generateRoom (Room room) + { + int width = room.Width; + int height = room.Height; + Vector2 position = room.getPosition (); + + foreach (Facing facing in room.getFacings()) { + int i, j; + i = j = 0; + + switch (facing) { + case Facing.EAST: + i = width; + goto case Facing.WEST; + + case Facing.WEST: + for (j = 0; j < height; j++) { + Transform wallInstance = (Transform)Instantiate (wall, new Vector3 (i + position.x, j + position.y, 0), Quaternion.identity); + wallInstance.SetParent (environment); + } + break; + + case Facing.NORTH: + j = height; + goto case Facing.SOUTH; + + case Facing.SOUTH: + for (i = 0; i < width; i++) { + Transform wallInstance = (Transform)Instantiate (wall, new Vector3 (i + position.x, j + position.y, 0), Quaternion.identity); + wallInstance.SetParent (environment); + } + break; - private void instanciateWall(Vector2 position) { - Transform wallInstance = (Transform) Instantiate(wall, new Vector3(position.x, position.y, 0), Quaternion.identity); - wallInstance.localScale = new Vector2 (0.5f, 0.5f); - wallInstance.SetParent(environment); - } - - /** - * Generate a bordered square (not filled) beginning at position - */ - private void generateSquare(int width, int height, Vector2 position) { - - for (int i = 0; i <= width; i++) { - for (int j = 0; j <= height; j++) { - if(i == 0 || i == width || j == 0 || j == height) { - - Transform wallInstance = (Transform) Instantiate(wall, new Vector3(i + position.x, j + position.y, 0), Quaternion.identity); - wallInstance.SetParent(environment); } } } - } - - /** - * Generate Horizontal platform beginning at position - */ - private void generatePlatform(int width, Vector2 position) { + + /** + * Generate Horizontal platform beginning at position + */ + private void generatePlatform (int width, Vector2 position) + { - for (int i = 0; i <= width; i++) { - Transform wallInstance = (Transform) Instantiate(wall, new Vector3(i + position.x, position.y, 0), Quaternion.identity); - wallInstance.SetParent(environment); + for (int i = 0; i <= width; i++) { + Transform wallInstance = (Transform)Instantiate (wall, new Vector3 (i + position.x, position.y, 0), Quaternion.identity); + wallInstance.SetParent (environment); + } } } -} +} \ No newline at end of file diff --git a/Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs b/Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs new file mode 100644 index 0000000..0fe1061 --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Procedural +{ + public class RecursiveGeneration : LevelGenerationStrategy + { + private List> remainingDirections; + private Room[,] rooms; + private List layout; + private Vector2 startingPosition; + private Vector2 endPosition; + private int levelWidth, levelHeight; + private int minimumDistance, maximumDistance; + private int currentGenIteration = -1; + private Vector2 comingFrom; + + public RecursiveGeneration (int levelWidth, int levelHeight, Vector2 startingPosition, Vector2 endPosition, int minimumDistance, int maximumDistance) + { + float elapsedTime = Time.realtimeSinceStartup; + this.endPosition = endPosition; + this.levelWidth = levelWidth; + this.levelHeight = levelHeight; + this.minimumDistance = minimumDistance; + this.maximumDistance = maximumDistance; + comingFrom = new Vector2 (startingPosition.x, startingPosition.y - 1); + } + + #region LevelGenerationStrategy implementation + public Level generateLevel () + { + float elapsedTime = 0; + /* Generate direction set */ + remainingDirections = new List>(maximumDistance); + + rooms = new Room[levelWidth, levelHeight]; + layout = new List (10); + findPath ((int)startingPosition.x, (int)startingPosition.y, minimumDistance); + + elapsedTime = Time.realtimeSinceStartup - elapsedTime; + Debug.Log ("Generation time : " + elapsedTime); + Debug.Log ("Number of Rooms : " + layout.Count); + + RemoveBlockingWall (); + + return new Level (levelWidth, levelHeight, layout, rooms); + } + #endregion + + private bool findPath (int x, int y, int minDistance) + { + currentGenIteration ++; + Debug.Log ("Starting find Path : x = " + x + " y = " + y + " distance = " + minDistance + " currentGenIteration : " + currentGenIteration); + + if (currentGenIteration >= (maximumDistance )) { + currentGenIteration --; + return false; + } + + if ((x == endPosition.x && y == endPosition.y) && (minDistance <= 0)) { + addRoomToLayout (x, y); + return true; + } + + if (!validPosition (x, y)) { + currentGenIteration--; + return false; + } + + List facings = new List (); + foreach (Facing facing in Enum.GetValues(typeof(Facing))) { + facings.Add (facing); + } + + remainingDirections.Add(facings); + Debug.Log ("Remain Dir Coun" + remainingDirections.Count); + remainingDirections[currentGenIteration].Remove(GetHeadingDirection(comingFrom, new Vector2(x, y)).Opposite()); + + addRoomToLayout (x, y); + comingFrom = layout [layout.Count - 1]; + + while (remainingDirections[currentGenIteration].Count > 0) { + int r = UnityEngine.Random.Range (0, remainingDirections[currentGenIteration].Count); + Facing direction = remainingDirections[currentGenIteration][r]; + + Debug.Log("Chosen : "+ direction + " at Iter : "+ currentGenIteration); + remainingDirections[currentGenIteration].Remove(direction); + switch (direction) { + case Facing.NORTH: + if (findPath (x, y + 1, minDistance - 1) == true) + return true; + break; + + case Facing.SOUTH: + if (findPath (x, y - 1, minDistance - 1) == true) + return true; + break; + + case Facing.EAST: + if (findPath (x + 1, y, minDistance - 1) == true) + return true; + break; + + case Facing.WEST: + if (findPath (x - 1, y, minDistance - 1) == true) + return true; + break; + } + + Debug.Log("Fail : "+ direction); + } + Debug.Log ("Number of Rooms : " + layout.Count); + + remainingDirections.RemoveAt (remainingDirections.Count - 1); + currentGenIteration--; + + layout.RemoveAt (layout.Count - 1); + rooms [x, y] = null; + return false; + } + + private void addRoomToLayout (int x, int y) + { + layout.Add (new Vector2 (x, y)); + rooms [x, y] = new Room (new Vector2 (x * 10, y * 10)); + } + + /** + * Determine if the position is valid to generate a room + * Check if position is already filled + * Check if position is within boundaries + */ + private bool validPosition (int x, int y) + { + if (x == endPosition.x && y == endPosition.y) + return false; + + foreach (Vector2 position in layout) { + if (position.x == x && position.y == y) + return false; + } + + return (((x >= 0) && (x < levelWidth)) && ((y >= 0) && (y < levelHeight))); + } + + public void RemoveBlockingWall() { + + foreach (Vector2 lastRoomPos in layout) { + + int currentIndex = layout.IndexOf(lastRoomPos); + + if ( currentIndex == (layout.Count -1)) { + return; + } + + Room lastRoom = rooms[(int)lastRoomPos.x, (int)lastRoomPos.y]; + Room headingRoom = rooms[(int)layout[currentIndex + 1].x, (int)layout[currentIndex + 1].y]; + + Facing goingTo = GetHeadingDirection(lastRoomPos, layout[currentIndex + 1]); + + switch (goingTo) { + case Facing.NORTH: + headingRoom.removeFacing (Facing.SOUTH); + if (lastRoom != null) + lastRoom.removeFacing (Facing.NORTH); + break; + + case Facing.SOUTH: + headingRoom.removeFacing (Facing.NORTH); + if (lastRoom != null) + lastRoom.removeFacing (Facing.SOUTH); + break; + + case Facing.EAST: + headingRoom.removeFacing (Facing.WEST); + if (lastRoom != null) + lastRoom.removeFacing (Facing.EAST); + break; + + case Facing.WEST: + headingRoom.removeFacing (Facing.EAST); + if (lastRoom != null) + lastRoom.removeFacing (Facing.WEST); + break; + } + } + } + + public Facing GetHeadingDirection(Vector2 from, Vector2 to) { + if (from.x < to.x && from.y == to.y) + return Facing.EAST; + else if (from.x > to.x && from.y == to.y) + return Facing.WEST; + else if (from.y < to.y && from.x == to.x) + return Facing.NORTH; + else if (from.y > to.y && from.x == to.x) + return Facing.SOUTH; + + return Facing.EAST; + } + } +} + diff --git a/Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs.meta b/Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs.meta new file mode 100644 index 0000000..107680e --- /dev/null +++ b/Assets/Scripts/ProceduralGeneration/RecursiveGeneration.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e814cf7fde2719f4e8b8b678a15e30d1 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Scripts/ProceduralGeneration/Room.cs b/Assets/Scripts/ProceduralGeneration/Room.cs index 9c9ab04..fb0f995 100644 --- a/Assets/Scripts/ProceduralGeneration/Room.cs +++ b/Assets/Scripts/ProceduralGeneration/Room.cs @@ -1,30 +1,51 @@ using UnityEngine; +using System; using System.Collections; using System.Collections.Generic; -public class Room +namespace Procedural { - enum Facing - { - NORTH, - SOUTH, - EAST, - WEST - } + public class Room + { + private Vector2 position; + private int width = 10; + private int height = 10; + private HashSet facings; - private Vector2 position; - private int width = 40; - private int height = 40; - private HashSet facings; + public Room (Vector2 position) + { + this.position = position; + facings = new HashSet (); + foreach (Facing facing in Enum.GetValues(typeof(Facing))) { + facings.Add (facing); + } + } - public Room(Vector2 position) - { - this.position = position; - } + public Vector2 getPosition () + { + return position; + } - public Vector2 getPosition() { - return position; - } + public void removeFacing (Facing facing) + { + facings.Remove (facing); + } + + public HashSet getFacings () + { + return facings; + } + public int Width { + get { + return this.width; + } + } -} + public int Height { + get { + return this.height; + } + } + } +} \ No newline at end of file