feat(procgen): connect dungeon growth branches directly
This commit is contained in:
1 parent
b6ecb576a0
commit
1aa670ea25
6 files changed
+35
-16
No files matched your search
@@ -5,8 +5,9 @@ It builds floors and boundary walls from semantic cells, then instantiates the
|
||||
generator's automatically placed themed props with their emitted asset id, yaw,
|
||||
footprint and placement flags.
|
||||
|
||||
The showcase uses the generator's `clustered` room growth and nearest-parent
|
||||
connections. `clusterGapMin` and `clusterGapMax` configure the structural gap
|
||||
The showcase uses the generator's `clustered` room growth and `growth`
|
||||
connections, which preserve the generated parent-child tree and make short,
|
||||
direct door links. `clusterGapMin` and `clusterGapMax` configure the structural gap
|
||||
between clustered rooms; setting both to `1` produces the compact, short-link
|
||||
composition used here. It also uses an orthographic camera framed from the occupied bounds, merged
|
||||
cut-corner stone floor meshes, modular wall/corner/doorway pieces, perimeter
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 419 KiB |
@@ -235,7 +235,7 @@ function rebuildDungeon() {
|
||||
p.setInt("corridorWidth", 1);
|
||||
p.setString("layoutStyle", "clustered");
|
||||
p.setString("corridorStyle", "l");
|
||||
p.setString("connectionStyle", "nearest");
|
||||
p.setString("connectionStyle", "growth");
|
||||
p.setString("floorPattern", "cobble");
|
||||
p.setString("decorSet", "mixed");
|
||||
p.setFloat("decorDensity", 0.055);
|
||||
@@ -407,12 +407,13 @@ function rebuildDungeon() {
|
||||
// authored at floor origin, so overlays sharing a table cell are raised.
|
||||
if (role == "light" && (flags & 2) != 0) py = 2.15;
|
||||
else if (role == "weapon") py = 2.0;
|
||||
else if (role == "banner") py = 1.35;
|
||||
else if (role == "food") py = 1.88;
|
||||
|
||||
local propScale = 1.0;
|
||||
if (role == "table" || role == "tavern") propScale = 1.65;
|
||||
else if (role == "bed") propScale = 1.45;
|
||||
else if (role == "banner") propScale = 1.18;
|
||||
else if (role == "banner") propScale = 1.42;
|
||||
else if (role == "seating" || role == "container") propScale = 1.55;
|
||||
else if (role == "treasure") propScale = 1.40;
|
||||
addAsset(id, px, py, pz, yaw, propScale, propScale, propScale);
|
||||
@@ -424,8 +425,8 @@ function rebuildDungeon() {
|
||||
local light = eve.Light3D();
|
||||
light.setType("point");
|
||||
light.setPosition(candidate[0], candidate[1], candidate[2]);
|
||||
light.setColor(1.0, 0.55, 0.25, 3.2);
|
||||
light.setRadius(DUNGEON_CELL * 3.0);
|
||||
light.setColor(1.0, 0.62, 0.34, 2.45);
|
||||
light.setRadius(DUNGEON_CELL * 3.6);
|
||||
dungeonLights.append(light);
|
||||
}
|
||||
}
|
||||
@@ -438,12 +439,12 @@ if (dungeonCamera == null) {
|
||||
dungeonCamera.setFov(58.0);
|
||||
dungeonCamera.setOrthographic(190.0);
|
||||
dungeonCamera.setClipPlanes(0.1, 400.0);
|
||||
dungeonCamera.setAmbient(0.62, 0.64, 0.70);
|
||||
dungeonCamera.setAmbient(0.76, 0.78, 0.84);
|
||||
dungeonCamera.setActive(true);
|
||||
gfx.setDirectionalLight(-0.45, -1.0, -0.25, 1.75, 1.62, 1.45);
|
||||
gfx.setDirectionalLight(-0.45, -1.0, -0.25, 2.05, 1.92, 1.76);
|
||||
}
|
||||
if (dungeonGrid == null) rebuildDungeon();
|
||||
gfx.setBackgroundColor(0.025, 0.015, 0.055, 1.0);
|
||||
gfx.setBackgroundColor(0.045, 0.018, 0.085, 1.0);
|
||||
|
||||
function eve_update(dt) {
|
||||
if (keyboard.isDown("r") || keyboard.isDown("R")) {
|
||||
|
||||
@@ -38,7 +38,10 @@ function configureDungeonAssetPack(p) {
|
||||
p.setString("assets.light", "candle,candle_lit,candle_melted,candle_thin,candle_thin_lit,candle_triple,torch,torch_lit,torch_mounted");
|
||||
p.setString("assets.wallShelf", "shelf_small,shelf_small_candles,wall_shelves");
|
||||
p.setString("assets.wallLight", "torch_mounted");
|
||||
p.setString("assets.banner", "banner_blue,banner_brown,banner_green,banner_red,banner_white,banner_yellow,banner_patternA_blue,banner_patternA_brown,banner_patternA_green,banner_patternA_red,banner_patternA_white,banner_patternA_yellow,banner_patternB_blue,banner_patternB_brown,banner_patternB_green,banner_patternB_red,banner_patternB_white,banner_patternB_yellow,banner_patternC_blue,banner_patternC_brown,banner_patternC_green,banner_patternC_red,banner_patternC_white,banner_patternC_yellow,banner_shield_blue,banner_shield_brown,banner_shield_green,banner_shield_red,banner_shield_white,banner_shield_yellow,banner_thin_blue,banner_thin_brown,banner_thin_green,banner_thin_red,banner_thin_white,banner_thin_yellow,banner_triple_blue,banner_triple_brown,banner_triple_green,banner_triple_red,banner_triple_white,banner_triple_yellow");
|
||||
// Repeated ids are weights, not hard-coded renderer behavior. The showcase
|
||||
// favours blue cloth like the reference while retaining every colour and
|
||||
// silhouette in the configurable pool.
|
||||
p.setString("assets.banner", "banner_blue,banner_blue,banner_blue,banner_patternA_blue,banner_patternA_blue,banner_patternB_blue,banner_patternC_blue,banner_shield_blue,banner_thin_blue,banner_triple_blue,banner_triple_blue,banner_brown,banner_green,banner_red,banner_white,banner_yellow,banner_patternA_brown,banner_patternA_green,banner_patternA_red,banner_patternA_white,banner_patternA_yellow,banner_patternB_brown,banner_patternB_green,banner_patternB_red,banner_patternB_white,banner_patternB_yellow,banner_patternC_brown,banner_patternC_green,banner_patternC_red,banner_patternC_white,banner_patternC_yellow,banner_shield_brown,banner_shield_green,banner_shield_red,banner_shield_white,banner_shield_yellow,banner_thin_brown,banner_thin_green,banner_thin_red,banner_thin_white,banner_thin_yellow,banner_triple_brown,banner_triple_green,banner_triple_red,banner_triple_white,banner_triple_yellow");
|
||||
p.setString("assets.weapon", "sword_shield,sword_shield_broken,sword_shield_gold");
|
||||
p.setString("assets.trap", "spikes,floor_tile_big_spikes");
|
||||
p.setString("assets.food", "plate,plate_small,plate_stack,plate_food_A,plate_food_B,bottle_A_brown,bottle_A_green,bottle_A_labeled_brown,bottle_A_labeled_green,bottle_B_brown,bottle_B_green,bottle_C_brown,bottle_C_green");
|
||||
|
||||
@@ -214,6 +214,7 @@ bool genRoguelike(const Params ¶ms, Grid2D &out, std::string &error) {
|
||||
const int cols = std::max(1, int(std::sqrt(double(roomCount))));
|
||||
const int rows = std::max(1, (roomCount + cols - 1) / cols);
|
||||
std::vector<Rect> rooms;
|
||||
std::vector<size_t> roomParents;
|
||||
std::uniform_int_distribution<int> dim(roomMin, roomMax);
|
||||
std::uniform_int_distribution<int> jx(0, 0), jy(0, 0);
|
||||
|
||||
@@ -221,11 +222,13 @@ bool genRoguelike(const Params ¶ms, Grid2D &out, std::string &error) {
|
||||
const int firstW = std::min(dim(rng), w - padding * 2);
|
||||
const int firstH = std::min(dim(rng), h - padding * 2);
|
||||
rooms.push_back({(w - firstW) / 2, (h - firstH) / 2, firstW, firstH});
|
||||
roomParents.push_back(0);
|
||||
std::uniform_int_distribution<int> jitter(-1, 1);
|
||||
std::uniform_int_distribution<int> clusterGap(clusterGapMin, clusterGapMax);
|
||||
const int maxAttempts = roomCount * 160;
|
||||
for (int attempt = 0; attempt < maxAttempts && int(rooms.size()) < roomCount; ++attempt) {
|
||||
const Rect &parent = rooms[size_t(rng()) % rooms.size()];
|
||||
const size_t parentIndex = size_t(rng()) % rooms.size();
|
||||
const Rect &parent = rooms[parentIndex];
|
||||
const int rw = std::min(dim(rng), w - padding * 2);
|
||||
const int rh = std::min(dim(rng), h - padding * 2);
|
||||
const int gap = clusterGap(rng);
|
||||
@@ -254,7 +257,10 @@ bool genRoguelike(const Params ¶ms, Grid2D &out, std::string &error) {
|
||||
for (const Rect &other : rooms) {
|
||||
if (rectsOverlap(cand, other, spacing)) { ok = false; break; }
|
||||
}
|
||||
if (ok) rooms.push_back(cand);
|
||||
if (ok) {
|
||||
rooms.push_back(cand);
|
||||
roomParents.push_back(parentIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int r = 0; r < rows; ++r) {
|
||||
@@ -277,7 +283,11 @@ bool genRoguelike(const Params ¶ms, Grid2D &out, std::string &error) {
|
||||
for (const Rect &other : rooms) {
|
||||
if (rectsOverlap(cand, other, spacing)) { ok = false; break; }
|
||||
}
|
||||
if (ok) { rooms.push_back(cand); break; }
|
||||
if (ok) {
|
||||
roomParents.push_back(rooms.empty() ? 0 : rooms.size() - 1);
|
||||
rooms.push_back(cand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,6 +297,7 @@ bool genRoguelike(const Params ¶ms, Grid2D &out, std::string &error) {
|
||||
const int rw = std::max(roomMin, w / 3);
|
||||
const int rh = std::max(roomMin, h / 3);
|
||||
rooms.push_back({(w - rw) / 2, (h - rh) / 2, rw, rh});
|
||||
roomParents.push_back(0);
|
||||
}
|
||||
|
||||
// 2) Carve room floors.
|
||||
@@ -296,7 +307,9 @@ bool genRoguelike(const Params ¶ms, Grid2D &out, std::string &error) {
|
||||
// instead of long row-wrap corridors while preserving deterministic output.
|
||||
for (size_t i = 1; i < rooms.size(); ++i) {
|
||||
size_t parent = i - 1;
|
||||
if (connections == "nearest") {
|
||||
if (connections == "growth" && i < roomParents.size()) {
|
||||
parent = roomParents[i];
|
||||
} else if (connections == "nearest") {
|
||||
int bestDistance = w + h + 1;
|
||||
const int bx = rooms[i].x + rooms[i].w / 2;
|
||||
const int by = rooms[i].y + rooms[i].h / 2;
|
||||
@@ -652,7 +665,8 @@ void registerRoguelikeGenerator(GeneratorRegistry ®istry) {
|
||||
descriptor.params.push_back(ParamDescriptor::choice("layoutStyle", "Room Layout", "grid",
|
||||
{"grid", "clustered"}));
|
||||
descriptor.params.push_back(ParamDescriptor::choice("connectionStyle", "Room Connections",
|
||||
"sequential", {"sequential", "nearest"}));
|
||||
"sequential",
|
||||
{"sequential", "nearest", "growth"}));
|
||||
descriptor.params.push_back(ParamDescriptor::choice("corridorStyle", "Corridor Style", "l",
|
||||
{"l", "straight", "diagonal"}));
|
||||
descriptor.params.push_back(ParamDescriptor::choice("floorPattern", "Floor Pattern", "brick",
|
||||
|
||||
@@ -287,7 +287,7 @@ TEST_CASE("procgen.roguelike.rulesChangeLayout") {
|
||||
pView->setString("floorPattern", pattern);
|
||||
pView->setString("corridorStyle", style);
|
||||
pView->setString("layoutStyle", layout);
|
||||
pView->setString("connectionStyle", layout == "clustered" ? "nearest" : "sequential");
|
||||
pView->setString("connectionStyle", layout == "clustered" ? "growth" : "sequential");
|
||||
auto gLease = requireGrid(*mod, "level.roguelike", p.handle);
|
||||
auto g = gLease.view();
|
||||
REQUIRE(g.isBound());
|
||||
|
||||
Reference in new issue
Block a user