程序化生成(调色板 / 子结构变体)
上一页:自然生成与预加载(生成总控 / 密度 / 再生) · 下一页:秘境与钥石 相关:遗迹模板 · 结构文件 · 蓝图与目标
前面讲的是「整块结构整体落地」。本页讲两套让遗迹「拼装出来、每次不一样」的程序化能力:
- 子结构变体(variants) —— 在主蓝图的槽位上贴一个加权随机的小模板,做「同主体、不同房 / 不同塔尖」。
- 调色板拼接(Tile Palette) —— 把多个小结构(瓦片)按连接规则自动拼成一座大遗迹,整体由程序生成。
两者都是「同一座遗迹每次长得不太一样」的来源,但定位不同:变体是主结构 + 局部随机点缀,调色板是从零按规则拼整体。
🖼️ [图片占位] 左:主塔 + 随机塔尖(变体);右:由若干房间瓦片自动拼成的地牢(调色板) · 建议
assets/procedural-overview.png
一、子结构变体(variants)
1.1 是什么
主结构生成后,QR 会在蓝图声明的每个变体槽位上,从一组候选模板里加权随机抽一个贴上去(NONE 朝向)。用来做:
- 同一座塔,塔尖随机是 A / B / C 三种之一;
- 同一座地宫,某个房间随机是宝库 / 陷阱 / 空房;
- 「塔基随地形起伏」「子结构高低错落」等地形自适应点缀。
变体写在该遗迹的 blueprint.yml 里(坐标都是相对结构原点,结构最小角 = 0,0,0),蓝图全貌见 蓝图与目标。
1.2 字段
# blueprint.yml 片段
variants:
- id: top # 槽位标识(仅作辨识)
x: 8
y: 20
z: 8 # 相对结构原点的槽位坐标
surface: false # true=贴该 x/z 的真实地表高度(忽略 y),做"塔基随地形起伏"
y-min: 0 # 垂直随机重定位:最终 Y 上再随机加 [y-min, y-max] 格
y-max: 0 # 都为 0=不抖动;如 y-min:-2 y-max:3 做"高低错落"
options: # 按 weight 加权随机抽一个贴上去(NONE 朝向)
- { template: spire_a, weight: 30 }
- { template: spire_b, weight: 20 }| 字段 | 默认 | 说明 |
|---|---|---|
id | — | 槽位标识,仅作辨识。 |
x / y / z | — | 槽位坐标(相对结构原点)。 |
surface | false | true = 忽略 y,把变体贴到该 x/z 的真实地表高度(做「塔基 / 立柱随地形起伏」)。地表太低(接近世界底)则跳过该槽。 |
y-min / y-max | 0 / 0 | 垂直随机重定位:在最终 Y 上再随机加 [y-min, y-max] 格。都为 0 = 不抖动;如 y-min:-2 y-max:3 做高低错落(对标其它结构插件的 VerticalRepositioning)。 |
options | — | 候选模板列表,{ template, weight }。按 weight 加权轮盘抽一个贴上。 |
⚙ 抽取与放置:每个槽位按
options的weight之和做轮盘抽一个候选;surface:true时落点取该 x/z 真实地表 Y、否则取原点 + (x,y,z);再叠加[y-min,y-max]随机偏移;最后以NONE朝向粘贴。weight之和 ≤0 的槽位跳过。
1.3 候选模板的要求
- 候选
template必须存在(如templates/spire_a/structure.nbt)。 - 建议候选模板自身
generation.enabled: false,只当变体用,别让它自己也参与自然生成。 - 变体应落在主结构范围内——超出主结构包围盒的部分,
/qr remove/ 消退快照还原还原不到(快照只覆盖主结构体积)。
1.4 递归嵌套(主堡 → 角楼 → 旗)
变体子模板自己的 blueprint.variants 会被递归处理:主堡贴角楼、角楼再贴旗帜……
ancient_keep (主堡)
└─ variants 槽 → corner_tower (角楼) ← 第 1 层
└─ corner_tower 的 variants 槽 → banner ← 第 2 层防爆栈 / 防卡服双保险:
| 限制 | 值 | 行为 |
|---|---|---|
| 最大嵌套深度 | 3 层 | 超过 3 层的更深变体不再展开。 |
| 单次生成变体总数 | 256 个 | 一座遗迹一次生成最多贴 256 个变体,超出截断 + 控制台告警(互相引用导致膨胀时保命)。 |
注意
两个模板互相在变体里引用对方,会被深度 3 / 总数 256 的上限截断并告警——不会无限膨胀冻服,但这是配置错误,应避免环引用。
🖼️ [图片占位] 一座主堡 + 四角随机角楼 + 角楼顶随机旗帜的递归变体效果图 · 建议
assets/variants-nested.png
二、调色板拼接(Tile Palette)
2.1 是什么
调色板是把若干小结构(瓦片 Tile)按连接器规则自动拼成一座大遗迹的程序化生成器。每块瓦片是一个普通模板(有 .nbt),瓦片之间通过**连接器(connector)**对接——从起始瓦片开始,沿开放的连接器不断挂上能对上口的瓦片,直到房间数达上限,最后用「封口瓦片」堵住剩下的开口。
适合做地牢 / 矿洞 / 走廊网络这类「房间 + 走廊拼出来、每次布局不同」的遗迹。
说明
调色板放在 plugins/QinhRuins/palettes/ 下,一个 .yml = 一份调色板,文件名(去扩展名)= 调色板 ID。瓦片引用的模板须先存在于 templates/。
2.2 调色板字段
# palettes/dungeon.yml —— 文件名 dungeon 即调色板 ID
max-rooms: 12 # 最多拼几块瓦片(≥1)
start: hub_room # 起始瓦片的模板 ID(留空=从 tiles 里按 weight 随机起手)
tiles:
- template: hub_room # 瓦片对应的模板 ID(templates/hub_room/)
weight: 10 # 加权随机权重(默认 1)
max-count: 1 # 这种瓦片最多用几次(默认 99)
role: room # 角色:room(默认) / cap(封口瓦片)
connectors: # 连接器:朝向 + 相对该瓦片原点的对接口坐标
- { side: NORTH, x: 3, y: 0, z: 0 }
- { side: SOUTH, x: 3, y: 0, z: 6 }
- { side: EAST, x: 6, y: 0, z: 3 }
- { side: WEST, x: 0, y: 0, z: 3 }
- template: corridor
weight: 20
connectors:
- { side: WEST, x: 0, y: 0, z: 1 }
- { side: EAST, x: 3, y: 0, z: 1 }
- template: dead_end # 封口瓦片:单连接器,role=cap
role: cap
connectors:
- { side: WEST, x: 0, y: 0, z: 1 }| 字段 | 默认 | 说明 |
|---|---|---|
max-rooms | 12 | 整座遗迹最多拼几块瓦片(≥1)。 |
start | 无 | 起始瓦片的模板 ID;留空 = 从 tiles 里按 weight 随机起手。 |
tiles[].template | — | 瓦片对应的模板 ID(须存在,否则该瓦片跳过 + 告警)。 |
tiles[].weight | 1 | 加权随机权重。 |
tiles[].max-count | 99 | 这种瓦片最多用几次。 |
tiles[].role | room | room = 普通房间;cap = 封口瓦片(单连接器,用来堵住拼接结束后剩下的开口)。 |
tiles[].connectors | 空 | 连接器列表:{ side, x, y, z }。side ∈ NORTH/SOUTH/EAST/WEST,x/y/z 是对接口相对该瓦片原点的坐标。 |
tiles[].repetition-penalty | 0 | 邻接重复惩罚:当该瓦片旁边已有同类瓦片时,按相邻数量降低它被抽中的权重(越大越不爱扎堆)。避免「一长串相同房间」。 |
tiles[].no-repeat | false | 设 true 则该瓦片禁止与同类相邻(连接 / 封口都遵守),比惩罚更硬。 |
⚙ 瓦片尺寸自动读取:每块瓦片的占地尺寸由 QR 直接读对应模板的
.nbt结构尺寸算出,无需在调色板里手填——所以瓦片模板的.nbt必须能正常加载。
2.3 拼接算法(怎么长出来)
1. 起手 用 start 指定的瓦片,没指定就按 weight 随机抽一块,放在原点(0,0,0)
2. 开口入队 把起始瓦片所有连接器登记为「开放开口」
3. 反复对接 从开放开口里随机取一个 → 找一块「有反向连接器、未达 max-count、不与已放瓦片重叠」的瓦片对上去
→ 新瓦片其余连接器继续入队,直到瓦片数达 max-rooms 或没有可对接的开口
4. 封口 剩下的开放开口,用 role=cap 的单连接器瓦片逐个堵上(对得上口且不重叠才堵)- 不重叠:拼接时做 外接包围盒检测,对接会造成与已放瓦片重叠的候选直接跳过。
- 自动回溯:走入死路(某开口找不到能对上的瓦片)时,规划器会退一步换方案再继续,连通性更好、断头路更少。回溯有步数预算(随
max-rooms自动伸缩),不会无限兜圈。无需配置。 - 可复现:同一个调色板 + 同一个种子(seed)= 同一份布局(用同一随机种子驱动)。
🔑 调色板拼出的是「整座结构本体」。它和子结构变体不同:变体是「主结构 + 局部点缀」,调色板是「从零按连接规则拼出整体」。
2.4 命令:预览与生成
| 命令 | 用途 |
|---|---|
/qr genstruct <调色板> | 在你脚下用随机种子拼接并实际放置,回报放了几块 / 计划几块 / 用的种子。 |
/qr genstruct <调色板> dry | 只算计划不放置:列出每块瓦片的偏移坐标与尺寸(看布局是否合理,不动世界)。 |
/qr genstruct <调色板> <种子> | 用指定种子拼接放置(同种子复现同布局,便于调试 / 复刻)。 |
⚙ mode 参数三选一:
dry= 干跑预览;纯数字 = 当作种子用于复现;其余 / 留空 = 用系统随机种子。dry模式用临时种子只算计划、不写方块。
🖼️ [图片占位]
/qr genstruct dungeon dry输出的瓦片计划清单 + 实际拼出的地牢俯视图 · 建议assets/genstruct-dry.png
三、变体 vs 调色板:怎么选
| 子结构变体(variants) | 调色板拼接(Tile Palette) | |
|---|---|---|
| 配在哪 | 遗迹的 blueprint.yml 的 variants 段 | palettes/<id>.yml 独立文件 |
| 定位 | 主结构 + 局部随机点缀 | 从零按连接规则拼整体 |
| 随机性 | 每个槽位加权抽 1 个候选 + 垂直抖动 | 整体布局随机(房间数 / 连接路径) |
| 触发 | 主结构自然生成时自动贴上 | 目前由 /qr genstruct 手动生成 |
| 适合 | 同主体不同房 / 塔尖 / 角楼、地形自适应点缀 | 地牢 / 矿洞 / 走廊网络等拼装式大遗迹 |
| 上限保护 | 嵌套深度 3 / 总数 256 | max-rooms / 各瓦片 max-count |
提示
两者可叠用:调色板拼出的瓦片模板自己也能带 blueprint.variants,于是「拼出的每个房间内部再随机点缀」。
四、改完之后
- 改
blueprint.yml的variants/palettes/*.yml→/qr reload。 - 变体不生效:确认候选
template存在、options的weight之和 > 0、槽位坐标在主结构范围内。 - 调色板拼不起来:先
/qr genstruct <id> dry看计划——瓦片数为 0 多半是连接器对不上口或瓦片模板.nbt读不出尺寸;引用了不存在的模板会在/qr reload时告警并跳过该瓦片。