From 9c02b11299f9304e1583cd039ca712ae05e0d02a Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 3 Feb 2025 17:46:12 +0100 Subject: [PATCH] Implemented randomly dispersing monster rooms. --- PuzzleGameProject/Assets/Scenes/Level_0.unity | 13 ++ .../Assets/Scripts/DungeonGenerator.meta | 3 + .../DungeonGenerator/DungeonGenerator.cs | 218 ++++++++++++++++++ .../DungeonGenerator/DungeonGenerator.cs.meta | 3 + .../Scripts/DungeonGenerator/EvenDisperser.cs | 84 +++++++ .../DungeonGenerator/EvenDisperser.cs.meta | 3 + .../Assets/Scripts/DungeonGenerator/Point.cs | 25 ++ .../Scripts/DungeonGenerator/Point.cs.meta | 3 + .../Assets/Scripts/DungeonGenerator/Room.cs | 24 ++ .../Scripts/DungeonGenerator/Room.cs.meta | 3 + .../Scripts/DungeonGenerator/TestRun.cs | 13 ++ .../Scripts/DungeonGenerator/TestRun.cs.meta | 2 + 12 files changed, 394 insertions(+) create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator.meta create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs.meta create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs.meta create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs.meta create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs.meta create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs create mode 100644 PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs.meta diff --git a/PuzzleGameProject/Assets/Scenes/Level_0.unity b/PuzzleGameProject/Assets/Scenes/Level_0.unity index 54ebe69..af9d73e 100644 --- a/PuzzleGameProject/Assets/Scenes/Level_0.unity +++ b/PuzzleGameProject/Assets/Scenes/Level_0.unity @@ -656,6 +656,7 @@ GameObject: m_Component: - component: {fileID: 126339302} - component: {fileID: 126339304} + - component: {fileID: 126339305} m_Layer: 0 m_Name: GameManager m_TagString: Untagged @@ -694,6 +695,18 @@ MonoBehaviour: diceRoller: {fileID: 150116548} passManager: {fileID: 895359724} player: {fileID: 1476127625} +--- !u!114 &126339305 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 126339301} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 05a951debbad424aada4449064592d39, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &150116543 GameObject: m_ObjectHideFlags: 0 diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator.meta b/PuzzleGameProject/Assets/Scripts/DungeonGenerator.meta new file mode 100644 index 0000000..49ac4b7 --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b2976da120e9418ab6437c973c915dd7 +timeCreated: 1738336012 \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs new file mode 100644 index 0000000..fc6ea5e --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Unity.VisualScripting; +using UnityEngine; +using Random = System.Random; + +namespace DungeonGenerator +{ + public class DungeonGenerator + { + private const int SIDE_LENGTH_OF_MONSTER = 4; + private const int SIDE_LENGTH_OF_NORMAL = 2; + + private List _monsterRooms = new List(); + private int _xLength = 40; + private int _yLength = 28; + + public void GenerateDungeon(int length, float monsterRoomRatio) + { + _xLength = 40; + _yLength = 28; + + Random random = new Random(); + List entrancePoints = GenerateEntrancePoints(_xLength, _yLength, random.Next(1,4)); + + EvenDisperser disperser = new EvenDisperser(_xLength, _yLength, entrancePoints); //TODO calculate L and W from length + int numberOfMonsterRooms = 7; // TODO: Calculate from ratio + for (var i = 0; i < numberOfMonsterRooms; i ++) + { + Point newSpot = disperser.GetPoint(SIDE_LENGTH_OF_MONSTER, SIDE_LENGTH_OF_MONSTER); + _monsterRooms.Add(new Room( + RoomType.Monster, + SIDE_LENGTH_OF_MONSTER, + SIDE_LENGTH_OF_MONSTER, + newSpot)); + } + } + + public void PrintMap() + { + char[,] mapMatrix = new Char[_xLength, _yLength]; + for (int x = 0; x < _xLength; x++) + { + for (int y = 0; y < _yLength; y++) + { + if (_monsterRooms.Any(room => room.PositionOfTopLeft.Equals(new Point(x,y)))) + { + mapMatrix[x, y] = 'X'; + } + else + { + mapMatrix[x, y] = 'O'; + } + } + } + + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < mapMatrix.GetLength(1); j++) + { + for (int i = 0; i < mapMatrix.GetLength(0); i++) + { + sb.Append(mapMatrix[i, j] + " "); + } + sb.Append(Environment.NewLine); + } + + Debug.Log(sb.ToString()); + } + + private enum Side + { + Top, + Right, + Bottom, + Left + } + + private List GenerateEntrancePoints(int xLengthOfDungeon, int yLengthOfDungeon, int numberOfEntranceLines) + { + if (numberOfEntranceLines > 4) + { + throw new Exception("Number of entrance lines cannot be greater than 4"); + } + + const int entranceSpaces = 12; + List entrancePoints = new List(); + Random random = new Random(); + Dictionary sidesToEnterOn = new() + { + { Side.Top, 0}, + { Side.Right, 0}, + { Side.Bottom, 0}, + { Side.Left, 0} + }; + + // Randomly assign starting lines to sides of dungeon + for (var i = 0; i < numberOfEntranceLines; i++) + { + while (true) + { + Side side = (Side)random.Next(0, 4); + if (sidesToEnterOn[side] < 2) + { + sidesToEnterOn[side] += 1; + break; + } + } + } + + int pointsPerLine = entranceSpaces / numberOfEntranceLines; + // Generate entrance lines on each side + switch (sidesToEnterOn[Side.Top]) + { + case 2: + // Add half of points for this entrance line to the top left side + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(i,0)); + } + + // Add the rest of the points for this entrance line to the top right side. + int pointsRemaining = pointsPerLine; + for (var i = 0; i < pointsRemaining; i++) + { + entrancePoints.Add(new Point((xLengthOfDungeon - 1) - i,0)); + } + break; + case 1: + int midpoint = xLengthOfDungeon / 2; + int startOfLine = midpoint - (pointsPerLine / 2); + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(startOfLine + i, 0)); + } + break; + } + + switch (sidesToEnterOn[Side.Right]) + { + case 2: + // Add points for this entrance line to the top right side + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(xLengthOfDungeon - 1, i + 1)); + } + + // Add the rest of the points for this entrance line to the bottom right side. + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(xLengthOfDungeon - 1, (yLengthOfDungeon - 1) - i)); + } + break; + case 1: + int midpoint = yLengthOfDungeon / 2; + int startOfLine = midpoint - (pointsPerLine / 2); + for (int i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(xLengthOfDungeon - 1, startOfLine + i)); + } + break; + } + + switch (sidesToEnterOn[Side.Bottom]) + { + case 2: + // Add half of points for this entrance line to the bottom left side + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(i, yLengthOfDungeon - 1)); + } + + // Add the rest of the points for this entrance line to the bottom right side. + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point((xLengthOfDungeon - 1) - i, yLengthOfDungeon - 1)); + } + break; + case 1: + int midpoint = xLengthOfDungeon / 2; + int startOfLine = midpoint - (pointsPerLine / 2); + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(startOfLine + i, yLengthOfDungeon - 1)); + } + break; + } + + switch (sidesToEnterOn[Side.Left]) + { + case 2: + // Add half of points for this entrance line to the top left side + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(0, i + 1)); + } + + // Add the rest of the points for this entrance line to the bottom left side. + for (var i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(0, (yLengthOfDungeon - 1) - i)); + } + break; + case 1: + int midpoint = yLengthOfDungeon / 2; + int startOfLine = midpoint - (pointsPerLine / 2); + for (int i = 0; i < pointsPerLine; i++) + { + entrancePoints.Add(new Point(0, startOfLine + i)); + } + break; + } + + return entrancePoints; + } + } +} \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs.meta b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs.meta new file mode 100644 index 0000000..1d2b08a --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/DungeonGenerator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e9db8a8571ad45d9a8d7c8d22ebe0d05 +timeCreated: 1738336090 \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs new file mode 100644 index 0000000..d528706 --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using UnityEngine.UIElements; +using Random = System.Random; + +namespace DungeonGenerator +{ + public class EvenDisperser + { + private List _samples = new List(){new Point(1,1)}; + private List _availablePoints; + + public EvenDisperser(int xLength, int yLength, List excludedPoints = null) + { + _availablePoints = GenerateAvailablePoints(xLength, yLength, excludedPoints ?? new List()); + } + + private List GenerateAvailablePoints(int xLength, int yLength, List excludedPoints) + { + List availablePoints = new List(); + for (var x = 0; x < xLength; x++) + { + for (var y = 0; y < yLength; y++) + { + if (!excludedPoints.Contains(new Point(x, y))) + { + availablePoints.Add(new Point(x, y)); + } + } + } + + return availablePoints; + } + + // Can place point so that the x lengt hand y length go out of bounds to the right and down. + public Point GetPoint(int xLength, int yLength) + { + int numCandidates = 50; // Increasing improves results but greatly effects performance. + Random rnd = new Random(); + int randomIndex = rnd.Next(_availablePoints.Count); + Point bestCandidate = new Point(Int32.MaxValue, Int32.MaxValue); + int bestDistance = 0; + for (var i = 0; i < numCandidates; i++) + { + var candidate = _availablePoints[randomIndex]; + var distance = candidate.ManhattanDistance(FindClosest(_samples, candidate)); + if (distance > bestDistance) + { + RemoveArea(candidate, xLength, yLength, _availablePoints); + bestCandidate = candidate; + bestDistance = distance; + } + } + + _samples.Add(bestCandidate); + return bestCandidate; + } + + private Point FindClosest(List options, Point reference) + { + Point closest = options[0]; + foreach (var option in options) + { + if (reference.ManhattanDistance(option) < reference.ManhattanDistance(closest)) + { + closest = option; + } + } + + return closest; + } + + private void RemoveArea(Point topLeft, int xLength, int yLength, List availablePoints) + { + for (var x = topLeft.X; x < topLeft.X + xLength; x++) + { + for (var y = topLeft.Y; y < topLeft.Y + yLength; y++) + { + availablePoints.Remove(new Point(x, y)); + } + } + } + } +} \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs.meta b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs.meta new file mode 100644 index 0000000..63352a4 --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/EvenDisperser.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3ce67840c99f4761af7805355099b495 +timeCreated: 1738577856 \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs new file mode 100644 index 0000000..60bc354 --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs @@ -0,0 +1,25 @@ +using System; + +namespace DungeonGenerator +{ + public struct Point + { + public int X { get; } + public int Y { get; } + + public Point(int x, int y) { + X = x; + Y = y; + } + + public override bool Equals(object obj) => obj is Point p && X == p.X && Y == p.Y; + public override int GetHashCode() => HashCode.Combine(X, Y); + + public override string ToString() => $"({X}, {Y})"; + + public static Point operator +(Point a, Point b) => new Point(a.X + b.X, a.Y + b.Y); + public static Point operator -(Point a, Point b) => new Point(a.X - b.X, a.Y - b.Y); + + public int ManhattanDistance(Point other) => Math.Abs(X - other.X) + Math.Abs(Y - other.Y); + } +} \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs.meta b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs.meta new file mode 100644 index 0000000..116b56a --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Point.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5c2b1ba54c4043b19dd204ab61f465c3 +timeCreated: 1738579679 \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs new file mode 100644 index 0000000..e61944d --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs @@ -0,0 +1,24 @@ +namespace DungeonGenerator +{ + public enum RoomType + { + Normal, + Monster, + } + + public class Room + { + public Room(RoomType roomType, int width, int height, Point positionOfTopLeft) + { + TypeOfRoom = roomType; + Width = width; + Height = height; + PositionOfTopLeft = positionOfTopLeft; + } + + public RoomType TypeOfRoom { get; set; } + public int Height { get; set; } + public int Width { get; set; } + public Point PositionOfTopLeft { get; set; } + } +} \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs.meta b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs.meta new file mode 100644 index 0000000..e57f115 --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/Room.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2dd79ebcc11d4938a8d116e711842b89 +timeCreated: 1738337240 \ No newline at end of file diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs new file mode 100644 index 0000000..e5c4afe --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs @@ -0,0 +1,13 @@ +using UnityEngine; + +public class TestRun : MonoBehaviour +{ + // Start is called once before the first execution of Update after the MonoBehaviour is created + void Start() + { + DungeonGenerator.DungeonGenerator gen = new DungeonGenerator.DungeonGenerator(); + gen.GenerateDungeon(0 , 0.5f); + gen.PrintMap(); + } + +} diff --git a/PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs.meta b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs.meta new file mode 100644 index 0000000..2731336 --- /dev/null +++ b/PuzzleGameProject/Assets/Scripts/DungeonGenerator/TestRun.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 05a951debbad424aada4449064592d39 \ No newline at end of file