UNTESTED Implemented connection rooms.
This commit is contained in:
parent
2baa78bd90
commit
1c447ce510
3 changed files with 261 additions and 46 deletions
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Unity.VisualScripting;
|
using Unity.VisualScripting;
|
||||||
using UnityEngine;
|
|
||||||
using Random = System.Random;
|
using Random = System.Random;
|
||||||
|
|
||||||
namespace DungeonGenerator
|
namespace DungeonGenerator
|
||||||
|
|
@ -43,10 +43,39 @@ namespace DungeonGenerator
|
||||||
|
|
||||||
AddNormalRoomsAroundMonsterRooms(dungeonMap);
|
AddNormalRoomsAroundMonsterRooms(dungeonMap);
|
||||||
AddNormalRoomsAroundBossRoom(dungeonMap);
|
AddNormalRoomsAroundBossRoom(dungeonMap);
|
||||||
|
AddConnectionRooms(dungeonMap);
|
||||||
|
|
||||||
return 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, Room> nodeRoomAndSideItIsOn = new Dictionary<RoomSide, Room>();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Room closestSeenRoom = FindClosestSeenConnection(roomSideWithRoom.Value,
|
||||||
|
roomSideWithRoom.Key,
|
||||||
|
nodeRoomsWithEntrances,
|
||||||
|
rootRooms);
|
||||||
|
//Place rooms along the line until they are connected
|
||||||
|
dungeon.AddRooms(PlaceRoomsOrganicallyTowardRoom(roomSideWithRoom.Value, closestSeenRoom, RoomType.Normal,
|
||||||
|
dungeon.GetOccupiedPoints()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AddNormalRoomsAroundBossRoom(DungeonMap dungeon)
|
private void AddNormalRoomsAroundBossRoom(DungeonMap dungeon)
|
||||||
{
|
{
|
||||||
AddNormalRoomsAroundRoom(dungeon.GetBossRoom(), dungeon);
|
AddNormalRoomsAroundRoom(dungeon.GetBossRoom(), dungeon);
|
||||||
|
|
@ -64,46 +93,10 @@ namespace DungeonGenerator
|
||||||
{
|
{
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
|
||||||
var roomPoints = room.GetPointsInRoom();
|
List<Point> topUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Top, dungeon.GetUnoccupiedPoints()));
|
||||||
int minX = roomPoints.Min(p => p.X);
|
List<Point> bottomUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Bottom, dungeon.GetUnoccupiedPoints()));
|
||||||
int maxX = roomPoints.Max(p => p.X);
|
List<Point> leftUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Left, dungeon.GetUnoccupiedPoints()));
|
||||||
int minY = roomPoints.Min(p => p.Y);
|
List<Point> rightUnoccupiedPoints = new List<Point>(GetUnoccupiedPointsOnSide(room, RoomSide.Right, dungeon.GetUnoccupiedPoints()));
|
||||||
int maxY = roomPoints.Max(p => p.Y);
|
|
||||||
|
|
||||||
List<Point> topUnoccupiedPoints = new List<Point>();
|
|
||||||
List<Point> bottomUnoccupiedPoints = new List<Point>();
|
|
||||||
List<Point> leftUnoccupiedPoints = new List<Point>();
|
|
||||||
List<Point> rightUnoccupiedPoints = new List<Point>();
|
|
||||||
|
|
||||||
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 (dungeon.GetUnoccupiedPoints().Contains(point))
|
|
||||||
{
|
|
||||||
topUnoccupiedPoints.Add(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
point = new Point(x, maxY + SIDE_LENGTH_OF_NORMAL);
|
|
||||||
if (dungeon.GetUnoccupiedPoints().Contains(point))
|
|
||||||
{
|
|
||||||
bottomUnoccupiedPoints.Add(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (dungeon.GetUnoccupiedPoints().Contains(point))
|
|
||||||
{
|
|
||||||
leftUnoccupiedPoints.Add(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
point = new Point(maxX + SIDE_LENGTH_OF_NORMAL, y);
|
|
||||||
if (dungeon.GetUnoccupiedPoints().Contains(point))
|
|
||||||
{
|
|
||||||
rightUnoccupiedPoints.Add(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int minNecessaryAvailablePoints = (SIDE_LENGTH_OF_MONSTER + SIDE_LENGTH_OF_NORMAL) / 2;
|
int minNecessaryAvailablePoints = (SIDE_LENGTH_OF_MONSTER + SIDE_LENGTH_OF_NORMAL) / 2;
|
||||||
|
|
||||||
|
|
@ -119,14 +112,13 @@ namespace DungeonGenerator
|
||||||
// Randomly shuffle and take the required number of sides
|
// Randomly shuffle and take the required number of sides
|
||||||
foreach (RoomSide side in availableSides.OrderBy(_ => random.Next()).Take(numRoomsToAdd))
|
foreach (RoomSide side in availableSides.OrderBy(_ => random.Next()).Take(numRoomsToAdd))
|
||||||
{
|
{
|
||||||
// Select appropriate unoccupied points based on the side
|
|
||||||
List<Point> unoccupiedPointsOnSide = side switch
|
List<Point> unoccupiedPointsOnSide = side switch
|
||||||
{
|
{
|
||||||
RoomSide.Top => topUnoccupiedPoints,
|
RoomSide.Top => topUnoccupiedPoints,
|
||||||
RoomSide.Bottom => bottomUnoccupiedPoints,
|
RoomSide.Bottom => bottomUnoccupiedPoints,
|
||||||
RoomSide.Left => leftUnoccupiedPoints,
|
RoomSide.Left => leftUnoccupiedPoints,
|
||||||
RoomSide.Right => rightUnoccupiedPoints,
|
RoomSide.Right => rightUnoccupiedPoints,
|
||||||
_ => new List<Point>()
|
_ => throw new ArgumentOutOfRangeException(nameof(side), $"Unexpected RoomSide value: {side}")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create room and add it if valid
|
// Create room and add it if valid
|
||||||
|
|
@ -139,6 +131,65 @@ namespace DungeonGenerator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
private Room CreateAdjacentRoom(RoomType type, List<Point> unoccupiedPointsOnSide, RoomSide side, HashSet<Point> occupiedPoints)
|
||||||
{
|
{
|
||||||
int sizeOfNewRoom = GetRoomSizeByType(type);
|
int sizeOfNewRoom = GetRoomSizeByType(type);
|
||||||
|
|
@ -394,5 +445,127 @@ namespace DungeonGenerator
|
||||||
int midpoint = length / 2;
|
int midpoint = length / 2;
|
||||||
return midpoint - (numberOfRooms * sizeOfRoom / 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)
|
||||||
|
{
|
||||||
|
List<Room> placedRooms = new List<Room>();
|
||||||
|
Room currentRoom = startingRoom;
|
||||||
|
bool sideToggle = true; // Start with left placement first
|
||||||
|
|
||||||
|
List<Point> path = GetPointsOnLine(startingRoom.GetCenterOfRoom(), targetRoom.GetCenterOfRoom());
|
||||||
|
|
||||||
|
foreach (Point p in path)
|
||||||
|
{
|
||||||
|
List<RoomSide> availableSides = sideToggle
|
||||||
|
? new List<RoomSide> { RoomSide.Left, RoomSide.Top }
|
||||||
|
: new List<RoomSide> { RoomSide.Right, RoomSide.Bottom };
|
||||||
|
|
||||||
|
sideToggle = !sideToggle; // Alternate side for next placement.
|
||||||
|
|
||||||
|
foreach (RoomSide side in availableSides)
|
||||||
|
{
|
||||||
|
List<Point> unoccupiedPointsOnSide = GetUnoccupiedPointsOnSide(currentRoom, side, occupiedPoints);
|
||||||
|
Room newRoom = CreateAdjacentRoom(RoomType.Normal, unoccupiedPointsOnSide, side, occupiedPoints);
|
||||||
|
|
||||||
|
if (newRoom != null)
|
||||||
|
{
|
||||||
|
placedRooms.Add(newRoom);
|
||||||
|
occupiedPoints.AddRange(newRoom.GetPointsInRoom());
|
||||||
|
currentRoom = newRoom;
|
||||||
|
break; // Move to the next room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentRoom.GetCenterOfRoom().ManhattanDistance(targetRoom.GetCenterOfRoom()) <=
|
||||||
|
GetRoomSizeByType(type))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return placedRooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Class
|
||||||
|
} // Namespace
|
||||||
|
|
@ -165,5 +165,31 @@ namespace DungeonGenerator
|
||||||
_occupiedPoints.ExceptWith(points);
|
_occupiedPoints.ExceptWith(points);
|
||||||
_unoccupiedPoints.AddRange(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, Room> GetNodesWithSides()
|
||||||
|
{
|
||||||
|
Dictionary<RoomSide, Room> nodesWithSides = new Dictionary<RoomSide, Room>();
|
||||||
|
foreach (Room room in _monsterRooms)
|
||||||
|
{
|
||||||
|
foreach (var kvp in room.GetAdjacentRoomsDict())
|
||||||
|
{
|
||||||
|
foreach (Room adjacentRoom in kvp.Value)
|
||||||
|
{
|
||||||
|
nodesWithSides[kvp.Key] = room;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodesWithSides;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.VisualScripting;
|
using Unity.VisualScripting;
|
||||||
|
|
||||||
|
|
@ -51,6 +52,11 @@ namespace DungeonGenerator
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ContainsPoint(Point point)
|
||||||
|
{
|
||||||
|
return GetPointsInRoom().Any(p => p.Equals(point));
|
||||||
|
}
|
||||||
|
|
||||||
public int GetDistanceToRoom(Room other)
|
public int GetDistanceToRoom(Room other)
|
||||||
{
|
{
|
||||||
Point centerOfRoom = GetCenterOfRoom();
|
Point centerOfRoom = GetCenterOfRoom();
|
||||||
|
|
@ -78,6 +84,16 @@ namespace DungeonGenerator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Helper method to get the opposite side
|
||||||
private RoomSide GetOppositeSide(RoomSide side) => side switch
|
private RoomSide GetOppositeSide(RoomSide side) => side switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue