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.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,14 +112,13 @@ 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
_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.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
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue