Moved Dungeon Map Generator to it's own solution.
This commit is contained in:
parent
5c46281334
commit
fd2a28afe5
50 changed files with 86389 additions and 84 deletions
|
|
@ -1,607 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Unity.VisualScripting;
|
||||
using Unity.VisualScripting.Dependencies.NCalc;
|
||||
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 const int SIDE_LENGTH_OF_ENTRANCE = 2;
|
||||
private const int WIDTH_OF_BOSS = 10;
|
||||
private const int HEIGHT_OF_BOSS = 6;
|
||||
|
||||
private int _xLength = 40;
|
||||
private int _yLength = 28;
|
||||
|
||||
public DungeonMap GenerateDungeon(int length, float monsterRoomRatio)
|
||||
{
|
||||
_xLength = 40;
|
||||
_yLength = 28;
|
||||
|
||||
Random random = new Random();
|
||||
DungeonMap dungeonMap = new DungeonMap(_xLength, _yLength);
|
||||
|
||||
dungeonMap.AddRooms(GenerateEntranceRooms(_xLength, _yLength, random.Next(1,4)));
|
||||
dungeonMap.AddRoom(GenerateOnlyBossRoom(_xLength, _yLength, WIDTH_OF_BOSS, HEIGHT_OF_BOSS));
|
||||
|
||||
EvenDisperser disperser = new EvenDisperser(_xLength, _yLength, dungeonMap.GetUnoccupiedPoints()); //TODO calculate L and W from length
|
||||
int numberOfMonsterRooms = 7; // TODO: Calculate from ratio
|
||||
|
||||
for (var i = 0; i < numberOfMonsterRooms; i ++)
|
||||
{
|
||||
dungeonMap.AddRoom(disperser.GenerateAndPlaceRoom(
|
||||
SIDE_LENGTH_OF_MONSTER,
|
||||
SIDE_LENGTH_OF_MONSTER,
|
||||
RoomType.Monster));
|
||||
}
|
||||
|
||||
AddNormalRoomsAroundMonsterRooms(dungeonMap);
|
||||
AddNormalRoomsAroundBossRoom(dungeonMap);
|
||||
AddConnectionRooms(dungeonMap);
|
||||
|
||||
return dungeonMap;
|
||||
}
|
||||
|
||||
private void AddConnectionRooms(DungeonMap dungeon)
|
||||
{
|
||||
// For each room adjacent to a monster and boos room connect it with the closest thing (entrance room, or monster adjacent room)
|
||||
// unless there is something already going there.
|
||||
List<Room> rootRooms = new List<Room>(dungeon.GetMonsterRooms());
|
||||
rootRooms.Add(dungeon.GetBossRoom());
|
||||
|
||||
// Create list of connection rooms
|
||||
Dictionary<RoomSide, List<Room>> nodeRoomAndSideItIsOn = dungeon.GetNodesWithSides();
|
||||
|
||||
List<Room> nodeRoomsWithEntrances = new List<Room>(dungeon.GetNodeRooms());
|
||||
nodeRoomsWithEntrances.AddRange(dungeon.GetEntranceRooms());
|
||||
|
||||
// For each connection room that is not an entrance room,
|
||||
// find the next closest uninterrupted connection room that is not on the same root room.
|
||||
|
||||
foreach (var roomSideWithRoom in nodeRoomAndSideItIsOn)
|
||||
{
|
||||
foreach (Room room in roomSideWithRoom.Value)
|
||||
{
|
||||
Room closestSeenRoom = FindClosestSeenConnection(room,
|
||||
roomSideWithRoom.Key,
|
||||
nodeRoomsWithEntrances,
|
||||
rootRooms);
|
||||
if (closestSeenRoom != null)
|
||||
{
|
||||
//Place rooms along the line until they are connected
|
||||
dungeon.AddRooms(PlaceRoomsOrganicallyTowardRoom(room, closestSeenRoom, RoomType.Normal,
|
||||
dungeon.GetOccupiedPoints(), dungeon.GetUnoccupiedPoints()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddNormalRoomsAroundBossRoom(DungeonMap dungeon)
|
||||
{
|
||||
AddNormalRoomsAroundRoom(dungeon.GetBossRoom(), dungeon);
|
||||
}
|
||||
|
||||
private void AddNormalRoomsAroundMonsterRooms(DungeonMap dungeon)
|
||||
{
|
||||
foreach (Room monsterRoom in dungeon.GetMonsterRooms())
|
||||
{
|
||||
AddNormalRoomsAroundRoom(monsterRoom, dungeon);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddNormalRoomsAroundRoom(Room room, DungeonMap dungeon)
|
||||
{
|
||||
Random random = new Random();
|
||||
|
||||
List<Point> topUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Top, dungeon.GetUnoccupiedPoints()));
|
||||
List<Point> bottomUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Bottom, dungeon.GetUnoccupiedPoints()));
|
||||
List<Point> leftUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Left, dungeon.GetUnoccupiedPoints()));
|
||||
List<Point> rightUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Right, dungeon.GetUnoccupiedPoints()));
|
||||
|
||||
int minNecessaryAvailablePoints = (SIDE_LENGTH_OF_MONSTER + SIDE_LENGTH_OF_NORMAL) / 2;
|
||||
|
||||
List<RoomSide> availableSides = new List<RoomSide>();
|
||||
if (topUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Top);}
|
||||
if (bottomUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Bottom);}
|
||||
if (leftUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Left);}
|
||||
if (rightUnoccupiedPoints.Count >= minNecessaryAvailablePoints){availableSides.Add(RoomSide.Right);}
|
||||
|
||||
// Ensure between 3 and 4 rooms are added
|
||||
int numRoomsToAdd = Math.Min(availableSides.Count, random.Next(3, 5));
|
||||
|
||||
// Randomly shuffle and take the required number of sides
|
||||
foreach (RoomSide side in availableSides.OrderBy(_ => random.Next()).Take(numRoomsToAdd))
|
||||
{
|
||||
List<Point> unoccupiedPointsOnSide = side switch
|
||||
{
|
||||
RoomSide.Top => topUnoccupiedPoints,
|
||||
RoomSide.Bottom => bottomUnoccupiedPoints,
|
||||
RoomSide.Left => leftUnoccupiedPoints,
|
||||
RoomSide.Right => rightUnoccupiedPoints,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(side), $"Unexpected RoomSide value: {side}")
|
||||
};
|
||||
|
||||
// Create room and add it if valid
|
||||
Room newRoom = CreateAdjacentRoom(RoomType.Normal, unoccupiedPointsOnSide, side, dungeon.GetOccupiedPoints());
|
||||
if (newRoom != null)
|
||||
{
|
||||
room.AddAdjacentRoom(newRoom, side);
|
||||
dungeon.AddRoom(newRoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Point> GetUnoccupiedPointsOnSide(Room room, RoomSide side, HashSet<Point> unoccupiedPoints)
|
||||
{
|
||||
List<Point> unoccupiedPointsOnSide = new List<Point>();
|
||||
var roomPoints = room.GetPointsInRoom();
|
||||
int minX = roomPoints.Min(p => p.X);
|
||||
int maxX = roomPoints.Max(p => p.X);
|
||||
int minY = roomPoints.Min(p => p.Y);
|
||||
int maxY = roomPoints.Max(p => p.Y);
|
||||
|
||||
switch (side)
|
||||
{
|
||||
case RoomSide.Top:
|
||||
for (int x = minX - SIDE_LENGTH_OF_NORMAL; x <= maxX + SIDE_LENGTH_OF_NORMAL; x++)
|
||||
{
|
||||
Point point = new Point(x, minY - SIDE_LENGTH_OF_NORMAL);
|
||||
if (unoccupiedPoints.Contains(point))
|
||||
{
|
||||
unoccupiedPointsOnSide.Add(point);
|
||||
}
|
||||
}
|
||||
return unoccupiedPointsOnSide;
|
||||
case RoomSide.Bottom:
|
||||
for (int x = minX - SIDE_LENGTH_OF_NORMAL; x <= maxX + SIDE_LENGTH_OF_NORMAL; x++)
|
||||
{
|
||||
Point point = new Point(x, maxY + SIDE_LENGTH_OF_NORMAL);
|
||||
if (unoccupiedPoints.Contains(point))
|
||||
{
|
||||
unoccupiedPointsOnSide.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
return unoccupiedPointsOnSide;
|
||||
case RoomSide.Left:
|
||||
for (int y = minY - SIDE_LENGTH_OF_NORMAL; y <= maxY + SIDE_LENGTH_OF_NORMAL; y++)
|
||||
{
|
||||
Point point = new Point(minX - SIDE_LENGTH_OF_NORMAL, y);
|
||||
if (unoccupiedPoints.Contains(point))
|
||||
{
|
||||
unoccupiedPointsOnSide.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
return unoccupiedPointsOnSide;
|
||||
case RoomSide.Right:
|
||||
for (int y = minY - SIDE_LENGTH_OF_NORMAL; y <= maxY + SIDE_LENGTH_OF_NORMAL; y++)
|
||||
{
|
||||
Point point = new Point(maxX + SIDE_LENGTH_OF_NORMAL, y);
|
||||
if (unoccupiedPoints.Contains(point))
|
||||
{
|
||||
unoccupiedPointsOnSide.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
return unoccupiedPointsOnSide;
|
||||
default:
|
||||
throw new ArgumentException("Side not recognized");
|
||||
}
|
||||
}
|
||||
|
||||
private Room CreateAdjacentRoom(RoomType type, List<Point> unoccupiedPointsOnSide, RoomSide side, HashSet<Point> occupiedPoints)
|
||||
{
|
||||
int sizeOfNewRoom = GetRoomSizeByType(type);
|
||||
Random random = new Random();
|
||||
|
||||
// Sort points by their coordinate based on the side of placement
|
||||
var orderedPoints = side == RoomSide.Left || side == RoomSide.Right
|
||||
? unoccupiedPointsOnSide.OrderBy(p => p.Y).ToList() // Sort by Y for vertical placement
|
||||
: unoccupiedPointsOnSide.OrderBy(p => p.X).ToList();
|
||||
|
||||
// List to store possible valid room placements (top-left points of the room)
|
||||
List<Point> validPlacements = new List<Point>();
|
||||
|
||||
// Iterate over the ordered points to find potential placements
|
||||
for (int i = 0; i < orderedPoints.Count - sizeOfNewRoom + 1; i++)
|
||||
{
|
||||
// Get a sequence of SIZE_OF_ROOM points starting from index i
|
||||
var potentialPoints = orderedPoints.Skip(i).Take(sizeOfNewRoom).ToList();
|
||||
|
||||
// Check if all points are consecutive and fit within the range
|
||||
if (IsRoomWideEnough(potentialPoints, side))
|
||||
{
|
||||
Point firstPoint = potentialPoints.First();
|
||||
|
||||
// Adjust the first point for different sides
|
||||
Point adjustedPoint = side switch
|
||||
{
|
||||
RoomSide.Top => new Point(firstPoint.X, firstPoint.Y), // Move up for top side
|
||||
RoomSide.Bottom => new Point(firstPoint.X, firstPoint.Y - 1), // Move down for bottom side
|
||||
RoomSide.Left => new Point(firstPoint.X, firstPoint.Y), // Move left for left side
|
||||
RoomSide.Right => new Point(firstPoint.X - 1, firstPoint.Y), // Move right for right side
|
||||
_ => firstPoint // Default case (shouldn't happen)
|
||||
};
|
||||
|
||||
if (!potentialPoints.Any(occupiedPoints.Contains))
|
||||
{
|
||||
validPlacements.Add(adjustedPoint); // First point is the top-left point of the room
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are valid placements, select a random one and create the room
|
||||
if (validPlacements.Any())
|
||||
{
|
||||
Point randomPlacement = validPlacements[random.Next(validPlacements.Count)];
|
||||
Room newRoom = new Room(type, sizeOfNewRoom, sizeOfNewRoom, randomPlacement);
|
||||
|
||||
return newRoom;
|
||||
}
|
||||
|
||||
// If no valid placements are found, return null or handle it as necessary
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper method to check if a sequence of points is wide enough (consecutive points)
|
||||
private bool IsRoomWideEnough(List<Point> points, RoomSide side)
|
||||
{
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
if (side == RoomSide.Left || side == RoomSide.Right)
|
||||
{
|
||||
// Check if points are consecutive along the Y-axis for left/right side
|
||||
if (points[i].Y != points[i - 1].Y + 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if points are consecutive along the X-axis for top/bottom side
|
||||
if (points[i].X != points[i - 1].X + 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int GetRoomSizeByType(RoomType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case RoomType.Entrance:
|
||||
return SIDE_LENGTH_OF_ENTRANCE;
|
||||
case RoomType.Monster:
|
||||
return
|
||||
SIDE_LENGTH_OF_MONSTER;
|
||||
case RoomType.Normal:
|
||||
return SIDE_LENGTH_OF_NORMAL;
|
||||
default:
|
||||
return SIDE_LENGTH_OF_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
private Room GenerateOnlyBossRoom(int xLengthOfDungeon, int yLengthOfDungeon, int width, int heigth)
|
||||
{
|
||||
// Place monster room in the middle third
|
||||
int middleAreaX = xLengthOfDungeon / 3;
|
||||
int middleAreaY = yLengthOfDungeon / 3;
|
||||
Random random = new Random();
|
||||
int bossX = middleAreaX + random.Next(0, middleAreaX - width);
|
||||
int bossY = middleAreaY + random.Next(0, middleAreaY - heigth);
|
||||
return new Room(RoomType.Boss, width, heigth, new Point(bossX, bossY));
|
||||
}
|
||||
|
||||
private enum Side
|
||||
{
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
Left
|
||||
}
|
||||
|
||||
private List<Room> GenerateEntranceRooms(int xLengthOfDungeon, int yLengthOfDungeon, int numberOfEntranceLines)
|
||||
{
|
||||
if (numberOfEntranceLines > 4)
|
||||
{
|
||||
throw new Exception("Number of entrance lines cannot be greater than 4");
|
||||
}
|
||||
|
||||
const int numEntranceRooms = 12;
|
||||
List<Room> entranceRooms = new List<Room>();
|
||||
Random random = new Random();
|
||||
Dictionary<Side, int> 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 roomsPerLine = numEntranceRooms / numberOfEntranceLines;
|
||||
int bottom = yLengthOfDungeon - SIDE_LENGTH_OF_NORMAL;
|
||||
int oneRoomFromBottom = bottom - SIDE_LENGTH_OF_NORMAL;
|
||||
int right = xLengthOfDungeon - SIDE_LENGTH_OF_NORMAL;
|
||||
// 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 < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(i,0)));
|
||||
}
|
||||
|
||||
// Add the rest of the points for this entrance line to the top right side.
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right - i,0)));
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
int startOfLine = GetStartOfCenteredLine(xLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL,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 < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right, i + SIDE_LENGTH_OF_NORMAL)));
|
||||
}
|
||||
|
||||
// Add the rest of the points for this entrance line to the bottom right side.
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right, oneRoomFromBottom - i)));
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
int startOfLine = GetStartOfCenteredLine(yLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(right, 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 < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(i, bottom)));
|
||||
}
|
||||
|
||||
// Add the rest of the points for this entrance line to the bottom right side.
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point((xLengthOfDungeon - 1) - i, bottom)));
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
int startOfLine = GetStartOfCenteredLine(xLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(startOfLine + i, bottom)));
|
||||
}
|
||||
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 < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(0, i + SIDE_LENGTH_OF_NORMAL)));
|
||||
}
|
||||
|
||||
// Add the rest of the points for this entrance line to the bottom left side.
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(0, oneRoomFromBottom - i)));
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
int startOfLine = GetStartOfCenteredLine(yLengthOfDungeon, roomsPerLine, SIDE_LENGTH_OF_NORMAL);
|
||||
for (var i = 0; i < roomsPerLine * SIDE_LENGTH_OF_NORMAL; i += SIDE_LENGTH_OF_NORMAL)
|
||||
{
|
||||
entranceRooms.Add(new Room(RoomType.Entrance, SIDE_LENGTH_OF_NORMAL, SIDE_LENGTH_OF_NORMAL, new Point(0, startOfLine + i)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return entranceRooms;
|
||||
}
|
||||
|
||||
private int GetStartOfCenteredLine(int length, int numberOfRooms, int sizeOfRoom)
|
||||
{
|
||||
int midpoint = length / 2;
|
||||
return midpoint - (numberOfRooms * sizeOfRoom / 2);
|
||||
}
|
||||
|
||||
private Room FindClosestSeenConnection(Room nodeRoom, RoomSide attachedSide, List<Room> possibleConnections,
|
||||
List<Room> obstructions)
|
||||
{
|
||||
const float minClosestLength = 0;
|
||||
Vector2 lookDirection = attachedSide switch
|
||||
{
|
||||
RoomSide.Top => new Vector2(0, -1), // Expanding upward
|
||||
RoomSide.Bottom => new Vector2(0, 1), // Expanding downward
|
||||
RoomSide.Left => new Vector2(-1, 0), // Expanding left
|
||||
RoomSide.Right => new Vector2(1, 0), // Expanding right
|
||||
_ => Vector2.Zero // Default (should not happen)
|
||||
};
|
||||
|
||||
Point nodeCenter = nodeRoom.GetCenterOfRoom();
|
||||
float visionConeAngle = MathF.PI / 4; // 45 degree cone in each side
|
||||
|
||||
Room closestSeenRoom = null;
|
||||
float closestDistance = float.MaxValue;
|
||||
|
||||
foreach (Room otherRoom in possibleConnections)
|
||||
{
|
||||
if (otherRoom == nodeRoom) continue;
|
||||
|
||||
Point otherCenter = otherRoom.GetCenterOfRoom();
|
||||
Vector2 toOther = new Vector2(otherCenter.X - nodeCenter.X, otherCenter.Y - nodeCenter.Y);
|
||||
float distance = toOther.Length();
|
||||
|
||||
if (distance <= minClosestLength || distance >= closestDistance) continue;
|
||||
|
||||
// Normalize direction vectors
|
||||
toOther = Vector2.Normalize(toOther);
|
||||
Vector2 lookNormalized = Vector2.Normalize(lookDirection);
|
||||
|
||||
float angle = MathF.Acos(Vector2.Dot(lookNormalized, toOther));
|
||||
|
||||
if (angle <= visionConeAngle)
|
||||
{
|
||||
if (!IsObstructed(nodeCenter, otherCenter, obstructions))
|
||||
{
|
||||
closestSeenRoom = otherRoom;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestSeenRoom; // Could be null if no valid room is found
|
||||
}
|
||||
|
||||
private bool IsObstructed(Point start, Point end, List<Room> obstructions)
|
||||
{
|
||||
List<Point> linePoints = GetPointsOnLine(start, end);
|
||||
foreach (Point point in linePoints)
|
||||
{
|
||||
if (obstructions.Any(room => room.ContainsPoint(point)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bresenham’s Line Algorithm to get points between two points
|
||||
private List<Point> GetPointsOnLine(Point p0, Point p1)
|
||||
{
|
||||
List<Point> points = new List<Point>();
|
||||
int dx = Math.Abs(p1.X - p0.X), sx = p0.X < p1.X ? 1 : -1;
|
||||
int dy = -Math.Abs(p1.Y - p0.Y), sy = p0.Y < p1.Y ? 1 : -1;
|
||||
int err = dx + dy, e2;
|
||||
|
||||
int x = p0.X, y = p0.Y;
|
||||
while (true)
|
||||
{
|
||||
points.Add(new Point(x, y));
|
||||
if (x == p1.X && y == p1.Y) break;
|
||||
e2 = 2 * err;
|
||||
if (e2 >= dy) { err += dy; x += sx; }
|
||||
if (e2 <= dx) { err += dx; y += sy; }
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private List<Room> PlaceRoomsOrganicallyTowardRoom(Room startingRoom, Room targetRoom, RoomType type,
|
||||
HashSet<Point> occupiedPoints, HashSet<Point> unoccupiedPoints)
|
||||
{
|
||||
List<Room> placedRooms = new List<Room>();
|
||||
Room currentRoom = startingRoom;
|
||||
|
||||
// TODO I don't think I actually need this
|
||||
List<Point> path = GetPointsOnLine(startingRoom.GetCenterOfRoom(), targetRoom.GetCenterOfRoom());
|
||||
|
||||
bool roomPlaced = true;
|
||||
while (currentRoom.GetCenterOfRoom().ManhattanDistance(targetRoom.GetCenterOfRoom()) >=
|
||||
GetRoomSizeByType(type) && roomPlaced)
|
||||
{
|
||||
Room newRoom;
|
||||
// Get sides of room to try and place new room
|
||||
(RoomSide primary, RoomSide secondary) =
|
||||
GetSidesTowardsEndPoint(currentRoom.GetCenterOfRoom(), targetRoom.GetCenterOfRoom());
|
||||
|
||||
List<Point> availablePointsOnSide = GetUnoccupiedPointsOnSide(currentRoom, primary, unoccupiedPoints);
|
||||
newRoom = CreateAdjacentRoom(type, availablePointsOnSide, primary, occupiedPoints);
|
||||
// If the primary side doesn't have room for a new room the try secondary
|
||||
if (newRoom == null)
|
||||
{
|
||||
availablePointsOnSide = GetUnoccupiedPointsOnSide(currentRoom, secondary, unoccupiedPoints);
|
||||
newRoom = CreateAdjacentRoom(type, availablePointsOnSide, secondary, occupiedPoints);
|
||||
}
|
||||
|
||||
if (newRoom != null)
|
||||
{
|
||||
placedRooms.Add(newRoom);
|
||||
occupiedPoints.AddRange(newRoom.GetPointsInRoom());
|
||||
currentRoom = newRoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
roomPlaced = false;
|
||||
}
|
||||
}
|
||||
|
||||
return placedRooms;
|
||||
}
|
||||
|
||||
public (RoomSide primarySide, RoomSide secondarySide) GetSidesTowardsEndPoint(Point start, Point end)
|
||||
{
|
||||
// Calculate the differences in X and Y
|
||||
int deltaX = end.X - start.X;
|
||||
int deltaY = end.Y - start.Y;
|
||||
|
||||
// Determine the primary side
|
||||
RoomSide primarySide;
|
||||
RoomSide secondarySide;
|
||||
|
||||
if (Math.Abs(deltaX) > Math.Abs(deltaY))
|
||||
{
|
||||
// If horizontal movement is greater, primary side is horizontal
|
||||
primarySide = deltaX > 0 ? RoomSide.Right : RoomSide.Left;
|
||||
secondarySide = deltaY > 0 ? RoomSide.Bottom : RoomSide.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If vertical movement is greater, primary side is vertical
|
||||
primarySide = deltaY > 0 ? RoomSide.Bottom : RoomSide.Top;
|
||||
secondarySide = deltaX > 0 ? RoomSide.Right : RoomSide.Left;
|
||||
}
|
||||
|
||||
return (primarySide, secondarySide);
|
||||
}
|
||||
|
||||
} // Class
|
||||
} // Namespace
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e9db8a8571ad45d9a8d7c8d22ebe0d05
|
||||
timeCreated: 1738336090
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DungeonGenerator
|
||||
{
|
||||
public class DungeonMap
|
||||
{
|
||||
private int _width;
|
||||
private int _height;
|
||||
private List<Room> _monsterRooms = new List<Room>();
|
||||
private List<Room> _entranceRooms = new List<Room>();
|
||||
private List<Room> _normalRooms = new List<Room>();
|
||||
private Room _bossRoom;
|
||||
private HashSet<Point> _unoccupiedPoints = new HashSet<Point>();
|
||||
private HashSet<Point> _occupiedPoints = new HashSet<Point>();
|
||||
|
||||
public DungeonMap(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
_unoccupiedPoints.AddRange(Enumerable.Range(0, _width)
|
||||
.SelectMany(x => Enumerable.Range(0, _height)
|
||||
.Select(y => new Point(x,y))));
|
||||
}
|
||||
|
||||
public void AddRoom(Room room)
|
||||
{
|
||||
switch (room.TypeOfRoom)
|
||||
{
|
||||
case RoomType.Monster:
|
||||
_monsterRooms.Add(room);
|
||||
break;
|
||||
case RoomType.Entrance:
|
||||
_entranceRooms.Add(room);
|
||||
break;
|
||||
case RoomType.Normal:
|
||||
_normalRooms.Add(room);
|
||||
break;
|
||||
case RoomType.Boss:
|
||||
_bossRoom = room;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
AddPointsToOccupied(room.GetPointsInRoom());
|
||||
}
|
||||
|
||||
public void AddRooms(List<Room> rooms)
|
||||
{
|
||||
if (rooms.Count == 0) return;
|
||||
|
||||
switch (rooms[0].TypeOfRoom)
|
||||
{
|
||||
case RoomType.Monster:
|
||||
_monsterRooms.AddRange(rooms);
|
||||
break;
|
||||
case RoomType.Entrance:
|
||||
_entranceRooms.AddRange(rooms);
|
||||
break;
|
||||
case RoomType.Normal:
|
||||
_normalRooms.AddRange(rooms);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
AddPointsToOccupied(rooms.SelectMany(room => room.GetPointsInRoom()).ToList());
|
||||
}
|
||||
|
||||
public List<Room> GetMonsterRooms()
|
||||
{
|
||||
return _monsterRooms;
|
||||
}
|
||||
|
||||
public Room GetBossRoom()
|
||||
{
|
||||
return _bossRoom;
|
||||
}
|
||||
|
||||
public List<Room> GetEntranceRooms()
|
||||
{
|
||||
return _entranceRooms;
|
||||
}
|
||||
|
||||
public List<Room> GetNormalRooms()
|
||||
{
|
||||
return _normalRooms;
|
||||
}
|
||||
|
||||
public HashSet<Point> GetUnoccupiedPoints()
|
||||
{
|
||||
return new HashSet<Point>(_unoccupiedPoints);
|
||||
}
|
||||
|
||||
public HashSet<Point> GetOccupiedPoints()
|
||||
{
|
||||
return new HashSet<Point>(_occupiedPoints);
|
||||
}
|
||||
|
||||
public void PrintMap()
|
||||
{
|
||||
List<Point> monsterRoomPoints = _monsterRooms.SelectMany(room => room.GetPointsInRoom()).ToList();
|
||||
List<Point> entranceRoomPoints = _entranceRooms.SelectMany(room => room.GetPointsInRoom()).ToList();
|
||||
List<Point> normalRoomPoints = _normalRooms.SelectMany(room => room.GetPointsInRoom()).ToList();
|
||||
char[,] mapMatrix = new Char[_width, _height];
|
||||
for (int x = 0; x < _width; x++)
|
||||
{
|
||||
for (int y = 0; y < _height; y++)
|
||||
{
|
||||
Point point = new Point(x, y);
|
||||
if (entranceRoomPoints.Contains(point))
|
||||
{
|
||||
mapMatrix[x, y] = '*';
|
||||
}
|
||||
else if (monsterRoomPoints.Contains(point))
|
||||
{
|
||||
mapMatrix[x, y] = 'X';
|
||||
}
|
||||
else if (_bossRoom.GetPointsInRoom().Contains(point))
|
||||
{
|
||||
mapMatrix[x, y] = 'B';
|
||||
}
|
||||
else if (normalRoomPoints.Contains(point))
|
||||
{
|
||||
mapMatrix[x, y] = 'N';
|
||||
}
|
||||
else
|
||||
{
|
||||
mapMatrix[x, y] = '-';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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($"{DateTime.Now}{Environment.NewLine}" +
|
||||
$"X Length: {_width}{Environment.NewLine}" +
|
||||
$"Y Length: {_height}{Environment.NewLine}" +
|
||||
$"Monster Rooms: {_monsterRooms.Count}{Environment.NewLine}" +
|
||||
sb.ToString());
|
||||
}
|
||||
|
||||
private void AddPointsToOccupied(List<Point> points)
|
||||
{
|
||||
_occupiedPoints.AddRange(points);
|
||||
_unoccupiedPoints.ExceptWith(points);
|
||||
}
|
||||
|
||||
private void RemovePointsFromOccupied(List<Point> points)
|
||||
{
|
||||
_occupiedPoints.ExceptWith(points);
|
||||
_unoccupiedPoints.AddRange(points);
|
||||
}
|
||||
|
||||
// Node rooms are the rooms placed around each monster room and the boss room.
|
||||
public List<Room> GetNodeRooms()
|
||||
{
|
||||
List<Room> nodeRooms = new List<Room>();
|
||||
nodeRooms.AddRange(_monsterRooms.SelectMany(room => room.GetAdjacentRooms()));
|
||||
nodeRooms.AddRange(_bossRoom.GetAdjacentRooms());
|
||||
return nodeRooms;
|
||||
}
|
||||
|
||||
public Dictionary<RoomSide, List<Room>> GetNodesWithSides()
|
||||
{
|
||||
Dictionary<RoomSide, List<Room>> nodesWithSides = new Dictionary<RoomSide, List<Room>>();
|
||||
foreach (Room room in _monsterRooms)
|
||||
{
|
||||
foreach (var kvp in room.GetAdjacentRoomsDict())
|
||||
{
|
||||
if (!nodesWithSides.ContainsKey(kvp.Key))
|
||||
{
|
||||
nodesWithSides[kvp.Key] = new List<Room>(kvp.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodesWithSides[kvp.Key].AddRange(kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodesWithSides;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b8c880c12ad649f8a395e6367a9c8838
|
||||
timeCreated: 1738774051
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.UIElements;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace DungeonGenerator
|
||||
{
|
||||
public class EvenDisperser
|
||||
{
|
||||
private List<Room> _samples = new List<Room>(){new Room(
|
||||
RoomType.Normal,
|
||||
1,
|
||||
1,
|
||||
new Point(1000, 1000))};
|
||||
private HashSet<Point> _availablePoints;
|
||||
|
||||
public EvenDisperser(int xLength, int yLength, HashSet<Point> availablePoints)
|
||||
{
|
||||
_availablePoints = availablePoints;
|
||||
}
|
||||
|
||||
private HashSet<Point> GenerateAvailablePoints(int xLength, int yLength, List<Point> excludedPoints)
|
||||
{
|
||||
HashSet<Point> availablePoints = new HashSet<Point>();
|
||||
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;
|
||||
}
|
||||
|
||||
public Room GenerateAndPlaceRoom(int xLength, int yLength, RoomType roomType)
|
||||
{
|
||||
int numCandidates = 100; // Increasing improves results but greatly effects performance.
|
||||
Random rnd = new Random();
|
||||
Room bestCandidate = new Room(roomType, xLength, yLength, new Point(Int32.MaxValue, Int32.MaxValue));
|
||||
int bestDistance = 0;
|
||||
for (var i = 0; i < numCandidates; i++)
|
||||
{
|
||||
var candidate = new Room(
|
||||
roomType, xLength, yLength, _availablePoints.ToList()[rnd.Next(0, _availablePoints.Count)]);
|
||||
var distance = candidate.GetDistanceToRoom(FindClosestRoom(_samples, candidate));
|
||||
if (distance > bestDistance
|
||||
&& candidate.GetPointsInRoom().All(room => _availablePoints.Contains(room)))
|
||||
{
|
||||
_availablePoints.ExceptWith(candidate.GetPointsInRoom());
|
||||
bestCandidate = candidate;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
_samples.Add(bestCandidate);
|
||||
return bestCandidate;
|
||||
}
|
||||
|
||||
private Room FindClosestRoom(List<Room> options, Room reference)
|
||||
{
|
||||
Room closest = options[0];
|
||||
foreach (var option in options)
|
||||
{
|
||||
if (reference.GetDistanceToRoom(option) < reference.GetDistanceToRoom(closest))
|
||||
{
|
||||
closest = option;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3ce67840c99f4761af7805355099b495
|
||||
timeCreated: 1738577856
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5c2b1ba54c4043b19dd204ab61f465c3
|
||||
timeCreated: 1738579679
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.VisualScripting;
|
||||
|
||||
namespace DungeonGenerator
|
||||
{
|
||||
public enum RoomSide
|
||||
{
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
public enum RoomType
|
||||
{
|
||||
Normal,
|
||||
Entrance,
|
||||
Monster,
|
||||
Boss
|
||||
}
|
||||
|
||||
public class Room
|
||||
{
|
||||
public Room(RoomType roomType, int width, int height, Point positionOfTopLeft)
|
||||
{
|
||||
TypeOfRoom = roomType;
|
||||
Width = width;
|
||||
Height = height;
|
||||
PositionOfTopLeft = positionOfTopLeft;
|
||||
}
|
||||
|
||||
private readonly Dictionary<RoomSide, List<Room>> _adjacentRooms = new();
|
||||
|
||||
public RoomType TypeOfRoom { get; set; }
|
||||
public int Height { get; set; }
|
||||
public int Width { get; set; }
|
||||
public Point PositionOfTopLeft { get; set; }
|
||||
|
||||
public List<Point> GetPointsInRoom()
|
||||
{
|
||||
List<Point> points = new List<Point>();
|
||||
for (int i = 0; i < Width; i++)
|
||||
{
|
||||
for (int j = 0; j < Height; j++)
|
||||
{
|
||||
points.Add(new Point(PositionOfTopLeft.X + i, PositionOfTopLeft.Y + j));
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
public bool ContainsPoint(Point point)
|
||||
{
|
||||
return GetPointsInRoom().Any(p => p.Equals(point));
|
||||
}
|
||||
|
||||
public int GetDistanceToRoom(Room other)
|
||||
{
|
||||
Point centerOfRoom = GetCenterOfRoom();
|
||||
Point centerOfOther = other.GetCenterOfRoom();
|
||||
return centerOfRoom.ManhattanDistance(centerOfOther);
|
||||
}
|
||||
|
||||
public Point GetCenterOfRoom()
|
||||
{
|
||||
return new Point(PositionOfTopLeft.X + Width / 2, PositionOfTopLeft.Y + Height / 2);
|
||||
}
|
||||
|
||||
public void AddAdjacentRoom(Room room, RoomSide side)
|
||||
{
|
||||
if (!_adjacentRooms.ContainsKey(side))
|
||||
{
|
||||
_adjacentRooms[side] = new List<Room>();
|
||||
}
|
||||
|
||||
// Prevent duplicates
|
||||
if (!_adjacentRooms[side].Contains(room))
|
||||
{
|
||||
_adjacentRooms[side].Add(room);
|
||||
room.AddAdjacentRoom(this, GetOppositeSide(side)); // Ensure bidirectional linkage
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Room> GetAdjacentRooms()
|
||||
{
|
||||
return _adjacentRooms.Values.SelectMany(roomList => roomList);
|
||||
}
|
||||
|
||||
public Dictionary<RoomSide, List<Room>> GetAdjacentRoomsDict()
|
||||
{
|
||||
return new Dictionary<RoomSide, List<Room>>(_adjacentRooms);
|
||||
}
|
||||
|
||||
// Helper method to get the opposite side
|
||||
private RoomSide GetOppositeSide(RoomSide side) => side switch
|
||||
{
|
||||
RoomSide.Top => RoomSide.Bottom,
|
||||
RoomSide.Bottom => RoomSide.Top,
|
||||
RoomSide.Left => RoomSide.Right,
|
||||
RoomSide.Right => RoomSide.Left,
|
||||
_ => throw new ArgumentException("Invalid RoomSide", nameof(side))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2dd79ebcc11d4938a8d116e711842b89
|
||||
timeCreated: 1738337240
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
using DungeonGenerator;
|
||||
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();
|
||||
DungeonMap dungeon = gen.GenerateDungeon(0 , 0.5f);
|
||||
dungeon.PrintMap();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 05a951debbad424aada4449064592d39
|
||||
Loading…
Add table
Add a link
Reference in a new issue