room positioning and door generator

This commit is contained in:
Marco 2025-04-11 18:39:39 +02:00
commit 5bfffc22ad
19 changed files with 571 additions and 17 deletions

View file

@ -0,0 +1,38 @@
using Godot;
namespace Cirno.Scripts.Controllers;
// [Tool]
public partial class DoorMarker : Marker2D
{
[Export] public RoomDirection Direction { get; set; } = RoomDirection.North;
[Export] public int WallIndex { get; set; } = 0; // Useful if there are multiple along a wall
public Vector2I GetWorldDirection()
{
return Direction switch
{
RoomDirection.North => new Vector2I(0, -1),
RoomDirection.South => new Vector2I(0, 1),
RoomDirection.East => new Vector2I(1, 0),
RoomDirection.West => new Vector2I(-1, 0),
_ => Vector2I.Zero
};
}
// public override void _Draw()
// {
// if (!Engine.IsEditorHint()) return;
//
// DrawLine(Vector2.Zero, Vector2.Up * 16, Colors.Orange, 2);
// DrawString(GetFont("font", "Label"), new Vector2(10, -10), Direction.ToString(), HAlign.Left);
// }
}
public enum RoomDirection
{
North,
South,
East,
West
}

View file

@ -0,0 +1 @@
uid://ddry5kjj3fr6c

View file

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Resources.Roguelite;
using Godot;
@ -9,11 +10,74 @@ namespace Cirno.Scripts.Controllers;
public partial class RogueliteRoom : Node2D
{
[Export] public RogueliteRoomResource RoomResource { get; set; }
public Vector2I GridPosition { get; set; } // Set by dungeon manager
[Export] public PackedScene DoorPrefab { get; private set; }
private static readonly Dictionary<string, Vector2I> DirectionMap = new()
{
{ "North", new Vector2I(0, -1) },
{ "South", new Vector2I(0, 1) },
{ "East", new Vector2I(1, 0) },
{ "West", new Vector2I(-1, 0) },
};
private Array<EnemyResource> SpawnableEnemies => RoomResource.SpawnableEnemies;
public void Spawn()
public RogueliteRoom Spawn(Func<Vector2I, bool> connectionChecker)
{
SpawnEnemies();
HandleDoors(connectionChecker);
return this;
}
private void HandleDoors(Func<Vector2I, bool> connectionChecker)
{
if (!HasNode("Doors")) return;
var doorsNode = GetNode("Doors");
foreach (Node child in doorsNode.GetChildren())
{
if (child is not DoorMarker marker) continue;
var baseDir = marker.GetWorldDirection();
// WallIndex determines the offset *along* the edge of the room
Vector2I offset = marker.Direction switch
{
RoomDirection.North or RoomDirection.South => new Vector2I(marker.WallIndex, 0),
RoomDirection.East or RoomDirection.West => new Vector2I(0, marker.WallIndex),
_ => Vector2I.Zero
};
// Combine GridPosition + offset to locate where this door aligns
Vector2I doorEdge = GridPosition + offset;
Vector2I neighborPos = doorEdge + baseDir;
bool connected = connectionChecker.Invoke(neighborPos);
if (!connected) continue;
var door = this.CreateChild<Door>(DoorPrefab, marker.GlobalPosition);
door.State = DoorState.Open;
// PackedScene prefab = hasConnection
// ? GD.Load<PackedScene>("res://Prefabs/Door.tscn")
// : GD.Load<PackedScene>("res://Prefabs/Wall.tscn");
//
// var instance = prefab.Instantiate<Node2D>();
// AddChild(instance);
// instance.GlobalPosition = ((Node2D)child).GlobalPosition;
// instance.Rotation = ((Node2D)child).GlobalRotation;
}
}
private void SpawnEnemies()
{
if (SpawnableEnemies is null || !SpawnableEnemies.Any()) return;
var enemySpawners = this.GetNode("EnemySpawners").GetChildren().Cast<Marker2D>();
foreach (var spawner in enemySpawners)
@ -26,6 +90,5 @@ public partial class RogueliteRoom : Node2D
var enemyScene = GD.Load<PackedScene>(e.PrefabPath);
var spawnedEnemy = spawner.CreateChild<Node2D>(enemyScene);
}
}
}

View file

@ -1,4 +1,6 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Resources.Roguelite;
using Godot;
using Godot.Collections;
@ -10,13 +12,27 @@ public partial class RogueliteRoomManager : Node2D
[Export] public Array<RogueliteRoomResource> Rooms { get; set; }
[Export] public Node2D SceneContainer { get; set; }
private Godot.Collections.Dictionary<Vector2I, RogueliteRoomResource> _grid = new();
[Export] public int DungeonWidth = 10;
[Export] public int DungeonHeight = 10;
[Export] public int MaxRooms = 12;
//[Export] public int Seed = -1;
[Export] public Vector2I TileSize { get; set; } = new Vector2I(16, 16);
[Export] public Vector2I RoomSizeInTiles { get; set; } = new Vector2I(20, 10);
public override void _Ready()
{
// Spawn first room
}
public void InitSpawning()
{
SpawnRoomsBinaryTree();
}
private void SpawnRoomsGrid()
{
//var firstRoom = Rooms.FirstOrDefault();
@ -30,12 +46,70 @@ public partial class RogueliteRoomManager : Node2D
{
var roomIndex = GD.RandRange(0, Rooms.Count - 1);
var room = Rooms[roomIndex];
SpawnRoom(room, origin + (room.Size * new Vector2(i, j) * tileSize));
//SpawnRoom(room, origin + (room.Size * new Vector2(i, j) * tileSize));
}
}
//CallDeferred(MethodName.RebakeNavigationDeferred);
}
private void SpawnRoomsBinaryTree()
{
var directions = new List<Vector2I>
{
new Vector2I(0, -1), // North
new Vector2I(0, 1), // South
new Vector2I(1, 0), // East
new Vector2I(-1, 0), // West
};
var origin = Vector2I.Zero;
var tileSize = new Vector2(16, 16);
var gridRoomSizeInTiles = new Vector2(20, 10);
var starterRooms = Rooms.Where(x => x.Type is RoomType.Starter).ToList();
var regularRooms = Rooms.Where(x => x.Type == RoomType.Regular).ToList();
var starterRoom = starterRooms[GD.RandRange(0, starterRooms.Count - 1)];
var spawnedStartRoom = SpawnRoom(starterRoom, origin);
MarkRoom(origin, starterRoom.Size, starterRoom);
Queue<(Vector2I Position, RogueliteRoomResource Room)> queue = new();
queue.Enqueue((origin, starterRoom));
int spawnedRoomCount = 1;
while (spawnedRoomCount < MaxRooms && queue.Count > 0)
{
var (currentPos, currentRoom) = queue.Dequeue();
foreach (var dir in directions)
{
var offsetPos = currentPos + dir;
// Avoid placing rooms in occupied space
if (_grid.ContainsKey(offsetPos)) continue;
var nextRoom = regularRooms[GD.RandRange(0, regularRooms.Count - 1)];
var roomSize = nextRoom.Size;
// Multi-tile room placement validation
if (!CanPlaceRoom(offsetPos, roomSize)) continue;
SpawnRoom(nextRoom, offsetPos);
MarkRoom(offsetPos, roomSize, nextRoom);
queue.Enqueue((offsetPos, nextRoom));
spawnedRoomCount++;
if (spawnedRoomCount >= MaxRooms)
break;
}
}
//SpawnSpecialRooms(rng);
}
private void RebakeNavigationDeferred()
@ -43,12 +117,46 @@ public partial class RogueliteRoomManager : Node2D
GameManager.Instance.RebakeNavigation();
}
private void SpawnRoom(RogueliteRoomResource room, Vector2 position)
private RogueliteRoom SpawnRoom(RogueliteRoomResource room, Vector2I gridPos)
{
var roomScene = GD.Load<PackedScene>(room.ScenePath);
var position = gridPos * TileSize * RoomSizeInTiles;
var roomScene = GD.Load<PackedScene>(room.ScenePath);
var spawnedScene = this.CreateChild<RogueliteRoom>(roomScene, position);
spawnedScene.Spawn();
// for reference
//SpawnRoom(room, origin + (room.Size * new Vector2(i, j) * tileSize));
return spawnedScene.Spawn(CheckConnection);
}
private bool CheckConnection(Vector2I pos)
{
return _grid.ContainsKey(pos); // <- this is your dungeon grid dictionary
}
private bool CanPlaceRoom(Vector2I origin, Vector2I size)
{
for (int x = 0; x < size.X; x++)
{
for (int y = 0; y < size.Y; y++)
{
var pos = origin + new Vector2I(x, y);
if (_grid.ContainsKey(pos)) return false;
}
}
return true;
}
private void MarkRoom(Vector2I origin, Vector2I size, RogueliteRoomResource room)
{
for (int x = 0; x < size.X; x++)
{
for (int y = 0; y < size.Y; y++)
{
var pos = origin + new Vector2I(x, y);
_grid[pos] = room;
}
}
}
}