2 个不稳定版本
0.2.0 | 2024年5月17日 |
---|---|
0.1.0 | 2024年1月19日 |
#159 in 游戏开发
在 bevy_ghx_proc_gen 中使用
145KB
2K SLoC
一个用于2D & 3D过程生成(带有模型合成/Wave function Collapse),也适用于Bevy
引擎的Rust库。
使用模型合成/Wave function Collapse,算法输入提供了邻接约束,并且内部,求解器(本例中为AC-4)将尝试生成满足这些约束的解决方案。
尽管它可以应用于纹理合成(主要与位图相关),但ghx_proc_gen
更侧重于基于网格的使用场景,如地形或结构生成,目标如下
- 快速。尽可能将算法的核心实现为快速;如果API操作可能损害性能,这将清楚地可见。
- 输出格式无关。在核心算法中,模型只是数字,你在生成器结果中如何解释它们取决于你(3d对象、2d精灵、文本、颜色等)。
- 人体工程学设计
- 良好的文档(
#![warn(missing_docs)]
)
快速入门
cargo add ghx_proc_gen
生成的基本构建块称为Models
,邻接约束用Socket
定义。每个模型在其每一侧都有一个或多个插口。
定义插口之间的连接,并允许在相对两侧有连接插口的模型成为邻居。
让我们构建一个棋盘图案
- 首先为算法创建
Rules
// A SocketCollection is what we use to create sockets and define their connections
let mut sockets = SocketCollection::new();
// For this example, we will only need two sockets
let (white, black) = (sockets.create(), sockets.create());
// With the following, a `white` socket can connect to a `black` socket and vice-versa
sockets.add_connection(white, vec![black]);
let mut models = ModelCollection::<Cartesian2D>::new();
// We define 2 very simple models: a white tile model with the `white` socket on each side
// and a black tile model with the `black` socket on each side
models.create(SocketsCartesian2D::Mono(white));
// We keep the black model for later
let black_model = models.create(SocketsCartesian2D::Mono(black)).clone();
// We give the models and socket collection to a RulesBuilder and get our Rules
let rules = RulesBuilder::new_cartesian_2d(models, sockets).build().unwrap();
- 创建一个
GridDefinition
// Like a chessboard, let's do an 8x8 2d grid
let grid = GridDefinition::new_cartesian_2d(8, 8, false, false);
- 创建一个
Generator
// There many more parameters you can tweak on a Generator before building it, explore the API.
let mut generator = GeneratorBuilder::new()
.with_rules(rules)
.with_grid(grid)
// Let's ensure that we make a chessboard, with a black square bottom-left
.with_initial_nodes(vec![(GridPosition::new_xy(0, 0), black_model)]).unwrap()
.build()
.unwrap();
- 获取结果
// Here we directly generate the whole grid, and ask for the result to be returned.
// The generation could also be done iteratively via `generator.select_and_propagate()`, or the results could be obtained through an `Observer`
let chess_pattern = generator.generate_collected().unwrap();
通过在终端中简单地打印结果,我们获得
let icons = vec!["◻️ ", "⬛"];
for y in 0..chess_pattern.grid().size_y() {
for x in 0..chess_pattern.grid().size_x() {
print!("{}", icons[chess_pattern.get_2d(x, y).model_index]);
}
println!();
}
API的更多详细信息
模型变化
为了简化规则定义步骤,可以自动为您创建一些模型变体。这将负责适当地旋转所有模型的 sockets
。
让我们以这个绳桥模型为例
let bridge_model = SocketsCartesian3D::Simple {
x_pos: bridge_side,
x_neg: bridge_side,
z_pos: bridge,
z_neg: bridge,
y_pos: bridge_top,
y_neg: bridge_bottom,
}
.to_template()
.with_additional_rotation(ModelRotation::Rot90)
使用上述声明,我们声明了我们的基础模型(默认允许 Rot0
),并允许额外的 Rot90
度旋转。内部,当构建 Rules
时,将创建两个模型变体。
当检索生成的结果时,您将获得 ModelInstances
,它引用原始模型的 index
以及对其应用的 ModelRotation
。
您还可以手动创建模型的旋转变体:bridge_model.rotated(ModelRotation::Rot180)
,并为它使用不同的资源,更改其重量等。[文档]
坐标系 & 轴
ghx_proc_gen
使用一个 右手 坐标系。但用于创建模型变体的旋转轴可以不同
- 当使用
Cartesian3D
时,它默认为Y+
,并且可以在RulesBuilder
上自定义。 - 当使用
Cartesian2D
时,旋转轴固定为Z+
[文档]
有关 Bevy 的信息,请参阅非官方 Bevy 速查表。
套接字连接
如快速入门所示,套接字连接使用 SocketCollection
声明 [文档]
请注意,如果它们用于具有生成旋转变体的模型,则位于您的旋转轴上的套接字连接应不同处理。
在上述图中旋转模型 2 将导致其顶部的套接字(此处为 B
)不同。对于此示例,我们可以使用
// a socket `B` can only be connected to another `B` if their **relative** rotation is 0°
sockets.add_constrained_rotated_connection(B, vec![ModelRotation::Rot0], vec![B]);
让我们假设模型 1 和 2 在其顶部和底部分别有不同的套接字声明,并且这些套接字仅在相对旋转为 0° 或 180° 时兼容
// a socket `model_2_top` can only be connected to another `model_1_bottom`
// if their **relative** rotation is 0° or 180°
sockets.add_constrained_rotated_connection(
model_2_top,
vec![ModelRotation::Rot0, ModelRotation::Rot180],
vec![model_1_bottom]
);
例如,请参阅峡谷示例中的 bridge_start_bottom
套接字,它只能从岩石面向外部。
生成器与人类交互
可以通过用户设置特定的初始值来定制生成:通过调用 with_initial_nodes
/with_initial_grid
,或者通过调用 set_and_propagate
直接与正在进行中的生成交互。[Bevy 插件视频示例]
这被 ProcGenDebugPlugin
使用。
观察者
您可以直接通过连接到生成器的 Observer
来检索生成器的结果,而不是直接收集生成器的结果 [文档]
这被 ProcGenDebugPlugin
使用。
网格循环
可以配置网格在任意轴上循环,这在其 GridDefinition
上设置 [文档]
Cargo 功能
在 ghx_proc_gen/cargo.toml 中找到列表和描述
-
models-names
[默认]:在创建模型时,可以使用with_name
函数为它们注册名称。禁用此功能时,该函数不执行任何操作。但启用后,您的模型名称将在运行时(如果启用,则可在调试跟踪中可见)可访问。 -
debug-traces
:默认禁用,此功能将使用tracing
包添加跟踪到包的核心算法。由于其中一些日志位于热点路径上,因此仅在调试时启用此功能。日志级别可以通过用户包(
tracing::level
、Bevy 的LogPlugin
等)进行配置。 -
bevy
:默认禁用,启用它仅使包的常见结构体自动派生Component
。 -
reflect
:默认禁用,启用它仅使包的常见结构体自动派生Reflect
。
针对 Bevy 用户
请参阅使用并公开 ghx_proc_gen
以及针对 Bevy 定制的附加插件和工具的 bevy_ghx_proc_gen
包。
cargo add bevy_ghx_proc_gen
示例
网格坐标系 | 资产 | 引擎 | 插件 | 相机 | |
---|---|---|---|---|---|
棋盘 |
笛卡尔2D | Unicode | 无 | N/A | N/A |
Unicode 地形 |
笛卡尔2D | Unicode | 无 | N/A | N/A |
Bevy-棋盘 |
笛卡尔2D | 过程网格 | Bevy | ProcGenSimplePlugin | 3D |
柱子 |
笛卡尔3D | .glb | Bevy | ProcGenDebugPlugin | 3D |
瓦片-层 |
笛卡尔3D | .png | Bevy | ProcGenDebugPlugin | 2D |
峡谷 |
笛卡尔3D | .glb | Bevy | ProcGenDebugPlugin | 3D |
unicode-terrain
、pillars
、tile-layers
和 canyon
的示例视频已被减速,以便查看生成进度。
[命令行] 棋盘示例
cargo run --example chessboard
简单的独立示例,与快速入门部分中的示例相同。
[命令行] Unicode 地形示例
cargo run --example unicode-terrain
简单的独立示例,生成一个俯视的 2D 地形,并在终端中用 Unicode 字符显示。
https://github.com/Henauxg/ghx_proc_gen/assets/19689618/6a1108af-e078-4b27-bae1-65c793ef99c1
[Bevy] Bevy 棋盘示例
cargo run --example bevy-chessboard
最简单的 Bevy 示例,与快速入门部分中的示例相同。
[Bevy] 柱子示例
cargo run --example pillars
此示例在空房间中生成多个不同大小的柱子。它的 rules
非常简单,只有 4 个模型:一个空块、一个柱子底座、一个柱子核心和一个柱子顶部。
https://github.com/Henauxg/ghx_proc_gen/assets/19689618/7beaa23c-df88-47ca-b1e6-8dcfc579ede2
[Bevy] 瓦片层示例
cargo run --example tile-layers
此示例使用 Bevy 和 2D 相机,但通过组合多个 z 层生成俯视瓦片图,因此使用的网格和规则仍然是 3D 的。
https://github.com/Henauxg/ghx_proc_gen/assets/19689618/3efe7b78-3c13-4100-999d-af07c94f5a4d
[Bevy] 峡谷示例
cargo run --example canyon
此示例生成一个类似峡谷的地形,并有一些动画风力涡轮。
https://github.com/Henauxg/ghx_proc_gen/assets/19689618/25cbc758-3f1f-4e61-b6ed-bcf571e229af
其他
编写规则的建议
- 从简单开始,然后逐步添加复杂性(新模型、插座和连接)。添加一个模型可能会对生成结果产生巨大影响,并可能需要调整权重。
- 不要犹豫,根据需要定义尽可能多的插座。插座仅存在于
Rules
完全创建之前,并在之后被优化掉。 - 更改节点选择启发式算法可能会极大地改变生成的结果。
- 在矩形网格上,对角线约束更难以处理,需要中间模型。
- 通常有多种实现特定结果的方法,而WFC/模型综合在与其他工具和效果结合使用时表现出色。特别是,您可能会发现对生成结果进行一些后处理很有用(添加支撑、合并模型等)。
局限性
-
生成大小可能很快就会成为问题。目前,当生成器遇到错误(规则与节点状态之间的矛盾)时,生成将从头开始。
有一些方法可以减轻这个问题,例如在生成过程中回溯和/或部分修改。请参阅BorisTheBrave的《模型综合和块修改》Model Synthesis and Modifying in Blocks 或P.Merell的《北卡罗来纳大学教堂山分校2009年博士论文》Ph.D. Dissertation, University of North Carolina at Chapel Hill, 2009。
为什么叫“ghx”?
- 它作为一个命名空间,避免选择诸如
proc_gen
或bevy_proc_gen
的负载名称。
致谢
感谢
- Paul Merrel 为其 模型综合 算法和实现
- Maxim Gumin 为其 Wave Function Collapse 算法和实现
- BorisTheBrave 为其C#库 DeBroglie 以及其网站 上的文章系列
许可证
代码
ghx-proc-gen是免费和开源的。此存储库中的所有代码都根据您的选择双许可以下任一许可证
- MIT许可证(LICENSE-MIT 或 http://opensource.org/licenses/MIT)
- Apache许可证,版本2.0(LICENSE-APACHE 或 http://www.apache.org/licenses/LICENSE-2.0)
任选。
除非您明确表示,否则任何根据Apache-2.0许可证定义有意提交以包含在您的工作中的贡献,均将根据上述许可证双许可,而无需任何其他条款或条件。
资产
- 示例资产
pillars
和canyon
是由Gilles Henaux为这些示例制作的,并可在 CC-BY-SA 4.0 许可下使用 - 在
tile-layers
示例中的资产是George Bailey的“16x16 Game Assets”,可在 OpenGameArt 上使用,根据 CC-BY 4.0 许可
依赖关系
~3–32MB
~483K SLoC