UNTESTED Implemented connection rooms.

This commit is contained in:
Max 2025-02-07 15:01:41 +01:00
parent 2baa78bd90
commit 1c447ce510
3 changed files with 261 additions and 46 deletions

View file

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using Unity.VisualScripting;
using UnityEngine;
using Random = System.Random;
namespace DungeonGenerator
@ -43,10 +43,39 @@ namespace DungeonGenerator
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, 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)
{
AddNormalRoomsAroundRoom(dungeon.GetBossRoom(), dungeon);
@ -64,46 +93,10 @@ namespace DungeonGenerator
{
Random random = new Random();
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);
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);
}
}
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;
@ -119,16 +112,15 @@ namespace DungeonGenerator
// Randomly shuffle and take the required number of sides
foreach (RoomSide side in availableSides.OrderBy(_ => random.Next()).Take(numRoomsToAdd))
{
// Select appropriate unoccupied points based on the side
List<Point> unoccupiedPointsOnSide = side switch
{
RoomSide.Top => topUnoccupiedPoints,
RoomSide.Bottom => bottomUnoccupiedPoints,
RoomSide.Left => leftUnoccupiedPoints,
RoomSide.Right => rightUnoccupiedPoints,
_ => new List<Point>()
_ => 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)
@ -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)
{
int sizeOfNewRoom = GetRoomSizeByType(type);
@ -394,5 +445,127 @@ namespace DungeonGenerator
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;
}
// Bresenhams 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

View file

@ -165,5 +165,31 @@ namespace DungeonGenerator
_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, 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;
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.VisualScripting;
@ -51,6 +52,11 @@ namespace DungeonGenerator
return points;
}
public bool ContainsPoint(Point point)
{
return GetPointsInRoom().Any(p => p.Equals(point));
}
public int GetDistanceToRoom(Room other)
{
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
private RoomSide GetOppositeSide(RoomSide side) => side switch
{