14 KiB
程序化生成模块
脚本入口: eve.Procgen()
按算法名和 Params 生成网格、地图层、图像、法线图或 GPU 纹理。
基本用法
local gen = eve.Procgen();
local p = gen.newParams();
p.setSeed(42); p.setSize(64, 40);
local grid = gen.generate("dungeon.bsp", p);
对象关系与调用时机
Params 描述 seed、尺寸和算法参数;Grid2D 是结果;OutputSpec 决定写入 TileLayer、Image 或 Texture;Procgen 按注册算法名执行。
纯脚本 PointSet 管线
程序化世界编排使用普通 Squirrel 函数,不要求节点图。PointSet 是带位置、法线、
旋转、缩放、密度、独立 seed 和自定义属性的 3D 采样集合。sampleGrid、
filterHeight、filterDensity、excludeRadius、jitterPoints 和 selfPrune
均返回新的集合,不修改输入,因此中间结果可以命名、检查、复用或分支:
local rootSeed = 42;
local candidates = procgen.sampleGrid(32, 20, 2.0,
procgen.deriveSeed(rootSeed, "trees"), 0.75);
local outsideRoad = procgen.excludeRadius(candidates, 20.0, 12.0, 4.0);
local trees = procgen.selfPrune(outsideRoad, 1.8);
不要让不同内容共享一个可变随机流。用 deriveSeed(root, "trees")、
deriveSeed(root, "rocks") 为分支派生稳定 seed;修改岩石管线不会扰动树木结果。
事务式 hot reload
命名 ProcgenContext 是一次完整重建的 staging 区。publish 会复制输出,
commitSystem 成功后才原子替换该系统的上次快照;fail、abortSystem、脚本异常或
未提交的 context 都不会破坏旧结果:
function rebuildForest(seed) {
local ctx = procgen.beginSystem("forest", seed);
try {
local points = procgen.sampleGrid(32, 20, 2.0, ctx.seedFor("trees"), 0.8);
local trees = procgen.selfPrune(points, 1.8);
ctx.trace("self prune", points.getCount(), trees.getCount(), 0.0);
if (!ctx.publish("trees", trees)) throw ctx.getError();
if (!procgen.commitSystem(ctx)) throw procgen.lastError();
} catch (error) {
ctx.fail(error.tostring());
procgen.commitSystem(ctx); // 失败并关闭 staging;旧快照仍然有效
}
}
eve_reload <- function() { rebuildForest(42); };
getSystemOutput 返回已提交输出的副本;getSystemRevision 可判断是否成功换代;
getSystemDebugReport 输出 seed、revision、命名阶段点数/耗时和最终输出点数。
可运行示例见 examples/procgen-script-pipeline。
目标导向指南
生成可玩的地牢层
创建 Params,设置 seed 和尺寸,按需添加算法参数;用 generate("dungeon.bsp", p) 先检查 Grid,也可配置 Output 将结果直接写入 TileLayer。保存 seed 可复现关卡。也可用 generate("wfc.simple", p)(preset=dungeon|cave|terrain)做约束驱动铺贴。
生成等值面网格(Marching Cubes)
local p = gen.newParams();
p.setSeed(1);
p.setInt("resolution", 32);
p.setString("field", "sphere"); // sphere | torus | noise | terrain
local cpu = gen.buildMesh("mesh.marchingcubes", p);
local gpu = gen.generateMesh("mesh.marchingcubes", p, gfx);
生成六边形网格星球
mesh.hexplanet 生成细分二十面体的对偶网格。星球表面以六边形单元为主,
并包含球面拓扑必需的 12 个五边形单元。所有顶点都位于指定半径的球面上,
可直接交给 3D 渲染系统使用。
local gen = eve.Procgen();
local p = gen.newParams();
p.setFloat("radius", 1.0);
p.setInt("subdivisions", 3);
p.setFloat("tileInset", 0.12);
local cpu = gen.buildMesh("mesh.hexplanet", p);
local gpu = gen.generateMesh("mesh.hexplanet", p, gfx);
参数:
radius:星球半径,必须大于0,默认1.0。subdivisions:二十面体细分次数,范围[0, 7],默认2。单元总数为10 * 4^subdivisions + 2;每增加一级,网格规模约增至四倍。tileInset:每个单元向自身中心收缩的比例,范围[0, 0.5),默认0.06。设为0时单元无缝相接;增大该值可形成清晰的网格间隙。
生成结果的 metadata 包含 algorithm、cells、pentagons、hexagons
和 subdivisions。网格始终含 12 个五边形,其余单元均为六边形;这些五边形
是用多边形铺满球面时无法消除的拓扑要求。
建议在创建关卡或切换星球时生成一次并缓存 GPU Mesh,不要逐帧重新生成。
生成可玩地牢(level.roguelike,Roguelike 风格)
level.roguelike 是种子驱动的房间-走廊地牢生成器,在墙/地板网格之上
再叠一层细节(Grid2D.detail)与对象,适合快速搭建 2D / 2.5D 关卡:
- 墙格
getDetail= 8 位邻接掩码(哪些方向是可行走地板),即瓦片方向, 可驱动方向感知的自动拼墙。 - 地板格
getDetail= 地板图案变体(1..N);>= 100表示随机散落的装饰瓦片。 getObjectType提供spawn/stairs以及pillar/chest道具。getMeta记录seed/rooms/floorPattern/decorTiles/corridorStyle, 便于复现或存档关卡。
local p = procgen.newParams();
p.setSeed(42);
p.setSize(48, 32);
p.setInt("roomCount", 12);
p.setString("corridorStyle", "l"); // l | straight | diagonal
p.setString("floorPattern", "brick");// brick | checker | plank | cobble | plain
p.setFloat("decorDensity", 0.06);
p.setString("decorSet", "mixed"); // none | pillars | treasure | nature | mixed
local grid = procgen.generate("level.roguelike", p);
常用规则:roomCount / roomMin / roomMax(房间预算与尺寸)、
corridorWidth(走廊宽)、padding(外框墙厚)、spacing(房间间距)、
floorVariants(地板变体数)、autotile(是否写入墙方向掩码)。
配套工具:
procgen.autotileGrid(grid):对任意已生成网格的墙格补写 8 位方向掩码, 为其它算法生成的关卡也加上“瓦片方向”细节。procgen.randomSeed():产生一个非 0 的随机种子,用于再掷一局。
可运行脚本与快捷键见 examples/roguelike-generator。
生成城区布局(urban.parcels / mesh.urban)
urban.parcels 与 mesh.urban 是基于 Eurographics 2024 论文
Hierarchical Co-generation of Parcels and Streets in Urban Modeling
(Chen/Song/Ortner,CGF 43(2))的引擎移植:从输入地块多边形层级化二分生成
地块(parcels)与街道(streets) 协同的城区布局。核心流程:
- 每层对每个可分割地块计算 ~20 条流线候选(交叉场 + 超流线追踪,必要时回退
直线弦),用论文式 2 质量分
Q = λ1·Qsize + λ2·Qregu + λ3·Qacce选最优分割线; - 消除地块网格中的短边;
- 对不可达地块分组,生成 I 形/L 形街道入口,并用转角感知 Dijkstra 接入既有 街道网络,保证每个地块可达、网络连通;
- 全局几何优化(规则角、边/街平滑、交叉口直角、贴近初始),带“变差回滚”保护。
local p = gen.newParams();
p.setSeed(20260823);
p.setString("land", "rect"); // rect | triangle | ellipse | l | hexagon
p.setFloat("landWidth", 100);
p.setFloat("landHeight", 60);
p.setFloat("minParcelArea", 4.0);
p.setInt("targetParcels", 120);
p.setString("streetPattern", "default"); // default | loop | culdesac | tree
p.setInt("optimize", 1);
// 1) 语义地图:路 = Semantic::Road(11),地块 = Floor(2),detail = 地块 id(1..N)
local grid = gen.generate("urban.parcels", p);
// 2) 城区网格:地块块 + 街道带(extrude>0 时挤压成体块)
p.setFloat("extrude", 6.0);
local mesh = gen.generateMesh("mesh.urban", p, gfx);
常用参数:land 也支持显式多边形("0,0;100,0;100,60;0,60");
lambdaSize/lambdaRegu/lambdaAcce 控制地块形状偏好(论文式 2 权重);
orientation 设为 east-west / north-south 可控制地块长边朝向;
boundaryStreet 设为 none / random 可关闭或随机化地块边界街道;
cellSize(栅格分辨率)、extrude(网格块高)。网格与地图的 metadata 记录
parcels / streets / junctions / streetLength / avgIrregularity。
交互示例与完整参数见 examples/urban-generator。
生成随机树木网格
mesh.tree 生成可复现的树干、分枝和叶片网格。branchAlgorithm 的两个值互斥:
weberPenn(默认):按层级、枝序和分枝角快速生成稳定骨架。spaceColonization:让枝梢向树冠吸引点迭代生长,适合更不规则的冠形。
local p = gen.newParams();
p.setSeed(31415);
p.setString("style", "lowpoly"); // lowpoly | realistic
p.setString("branchAlgorithm", "weberPenn");
p.setString("leafMode", "canopy"); // cards | canopy | none
p.setFloat("leafDensity", 0.75);
p.setFloat("height", 6.0);
p.setFloat("crownRadius", 2.0);
local tree = gen.generateMesh("mesh.tree", p, gfx);
两种算法共享主干曲率、向性、下垂、随高度变化的枝长/枝径,以及上下层叶片覆盖参数。常用调整:
- 外形与精度:
height、trunkRadius、crownRadius、radialSegments、curveSegments。 - 通用枝形:
trunkCurve、branchCurve、curveBack、tropism、droop。 - 层级生长:
branchLengthFalloff、branchRadiusFalloff;默认表现为下层枝更粗更长。 - 叶片:
leafSize、leafDensity、foliageStart、lowerLeafCoverage、upperLeafCoverage。 - Weber–Penn:
branchLevels、branchCount、branchAngle、branchAngleVariation、phyllotaxis、apicalDominance。 - 空间殖民:
attractorCount、colonizationIterations、influenceRadius、killRadius、growthStep、branchInertia、maxTurnAngle、maxCumulativeAngle、maxChildren。
完整参数范围、交互快捷键和可运行脚本见 examples/tree-generator。生成网格有一定成本,应在加载、换 seed 或修改参数时重建,不要每帧调用。
生成线性可拼接结构(栅栏 / 石墙 / 桥 / 长城 / 树篱 / 拒马)
将单个可拼接单元段沿 X 轴重复 N 次生成连续结构,接缝处纹理连续平铺。
local p = gen.newParams();
p.setInt("segments", 8); // 重复单元数
p.setFloat("segLength", 2.0); // 单元段长度
p.setFloat("height", 1.5); // 高度覆盖
p.setFloat("uvRepeat", 2.0); // 每世界单位的纹理重复次数
local mesh = gen.generateMesh("mesh.stonewall", p, gfx); // 或 mesh.fence / mesh.bridge
// mesh.greatwall / mesh.hedge / mesh.chevaldefrise
共享参数:segments、segLength、height、depth、thickness、scale、uvRepeat。
生成无缝材质
设置 texture recipe、尺寸、octaves、pixelSize 和 seamless,调用纹理或法线图生成接口;开发期切换 seed 预览,发布时缓存 Texture,不能每帧重新生成。
常见问题
- 未保存 seed,无法复现玩家问题。
- Output palette 缺少算法输出的 tile key。
- 在每帧 update 生成大地图或纹理。
- 在每帧重新生成树木网格;应缓存
Mesh,仅在 seed 或参数变化时重建。
API 快查
下列方法名来自当前 Squirrel 绑定;同一模块创建的辅助对象(例如 World、Body、Source)的方法也列在这里。
abort()、abortSystem()、add()、addObject()、addObjectAt()、applyToLayer()、autotileGrid()、beginSystem()、buildMesh()、clear()、clearObjects()、commitSystem()、deriveSeed()、empty()、excludeRadius()、fail()、fill()、filterDensity()、filterHeight()、generate()、generateImage()、generateMesh()、generateNormalImage()generateTexture()、generateTo()、getAlgorithmCount()、getAlgorithmId()、getCell()、getDetail()、getFloat()、getHeight()、getInt()getLayer()、getMeshRecipeCount()、getMeshRecipeId()、getMeta()、getName()、getObjectCount()、getObjectGid()、getObjectHeight()、getObjectName()、getObjectType()getObjectWidth()、getObjectX()、getObjectY()、getPalette()、getPaletteGid()、getPath()、getSeed()、getString()getTarget()、getTextureRecipeCount()、getTextureRecipeId()、getWidth()、gridToJson()、has()、hasAlgorithm()、hasMeshRecipe()、hasTextureRecipe()getDensity()、getError()、getFloatAttribute()、getNormalX()、getNormalY()、getNormalZ()、getOutput()、getOutputCount()、getOutputName()、getPointSeed()、getScaleX()、getScaleY()、getScaleZ()、getStringAttribute()、getSystemDebugReport()、getSystemOutput()、getSystemOutputCount()、getSystemOutputName()、getSystemRevision()、getSystemSeed()、getTraceCount()、getTraceInputCount()、getTraceMilliseconds()、getTraceName()、getTraceOutputCount()、getX()、getY()、getYaw()、getZ()、hasFailed()、hasFloatAttribute()、hasOutput()、hasStringAttribute()、hasSystem()、isActive()、jitterPoints()、lastError()、newGrid()、newOutput()、newParams()、newPointSet()、publish()、randomSeed()、removeSystem()、resize()、sampleGrid()、seedFor()、selfPrune()、setCell()、setDensity()、setDetail()、setFloat()、setFloatAttribute()、setInt()setLayer()、setMeta()、setNormal()、setPalette()、setPaletteGid()、setPath()、setPointSeed()、setPosition()、setScale()、setSeed()、setSize()、setString()、setStringAttribute()、setYaw()、trace()setTarget()
使用要点
- 模块对象和它创建的资源对象应保存在全局或实体状态中,不要在每帧重复创建。
- 带
update(dt)的系统应在eve_update调用;绘制方法应在eve_render调用。 - 参数约束、默认值和返回类型以对应模块头文件及
addFunc绑定为准;本文 API 快查与当前源码同步生成。
源码: src/modules/procgen/
相关测试: 在 test/ 中搜索 procgen;跨模块 hex 关卡见 test/hex_level_simulation.cpp、test/hex_level_data.cpp、examples/hex-levels/。