还在用随机游走?带你用 WFC 算法“手搓”一个无限肉鸽地
前言:告别“全随机”的垃圾关卡
做 Roguelike 游戏的兄弟们都知道,地图生成是核心体验的基石。最早我们用 随机游走(Random Walk),生成的地图像蚯蚓,太线性,没探索欲。后来我们用 柏林噪声(Perlin Noise),生成的地图太自然,缺乏人工设计的建筑结构感。直到 WFC(波函数坍缩) 横空出世。它就像一个“高智商的数独玩家”,能在保证局部规则(比如:墙壁必须连接墙壁,水流不能直接接岩浆)的前提下,生成全局合理且千变万化的地图。这也是《Townscaper》、《Bad North》等神作背后的核心技术。但 WFC 难就难在代码实现:熵值计算、约束传播、回溯机制,任何一个环节写错,地图就会“坍缩失败”,留给你一堆报错。今天,我们不谈枯燥的量子力学理论,直接打开 Unity 和 通义灵码,手把手写一套工业级的 2D WFC 地牢生成器。
模块一:核心逻辑拆解 —— WFC 到底在算什么?
在使用 AI 辅助写代码之前,我们必须先理解算法的物理意义。你可以把 WFC 想象成在填一个超级复杂的数独。
WFC 的三个核心概念:
- 叠加态:
- 地图上每一个格子(Slot),在没生成之前,它既是墙,又是路,又是门。
- 初始状态下,每个格子的可能性(熵)*是最大的。
- 观察:
- 找到当前地图上熵值最低(可能性最少,比如只剩“墙”和“路”两种可能)的那个格子。
- 强行让它坍缩成某一个确定的 Tile(比如,我拍板决定这个格子就是“墙”)。
- 传播:
- 这是最难的一步。一旦某个格子变成了“墙”,它周围的邻居就不能是“岩浆”了(假设规则如此)。
- 这种“约束”会像水波纹一样向四周扩散,削减周围格子的可能性,直到整个地图稳定。

- 图注:左图为初始叠加态(所有格子都是混沌的);中图为一次观察后,选定中心为草地;右图为传播后,周围格子自动排除了“深海”的可能性。
- 目的:用可视化图表建立算法直觉。
模块二:设计“乐高积木”的接口
WFC 的强大取决于“邻接规则” (Socket System)。我们需要告诉程序,每块瓦片(Tile)的上下左右能连接什么。这就像乐高积木的凸起和凹槽。
1. 瓦片数据结构
我们不需要自己手写繁琐的 ScriptableObject,让通义灵码帮我们生成这个模板。
Prompt 指令(发送给通义灵码):
"在 Unity 中,我需要一个
WFCTile的 ScriptableObject 类。 属性包含:
GameObject prefab: 对应的预制体。float weight: 出现的权重(概率)。string[] sockets: 定义 上、下、左、右 四个方向的接口类型(例如 'Road', 'Wall', 'Grass')。Rotation rotation: 是否允许旋转(0, 90, 180, 270)。 请生成 C# 代码,并添加详细注释。"
2. 规则配置
在 Unity 编辑器中,我们创建几种 Tile:
- 地面 (Floor): Sockets = [A, A, A, A] (全向连接 A)
- 直墙 (Wall_Straight): Sockets = [B, B, A, A] (上下接墙B,左右接地面A)
- 转角 (Wall_Corner): Sockets = [B, A, A, B] (上接墙B,右接墙B)
重点:接口标签(A、B)必须严格匹配。A 只能连 A,B 只能连 B。

- 图注:Unity Inspector 面板。展示了
WFCTile的配置细节,Sockets 数组清晰地定义了四个方向的连接规则。- 目的:展示数据驱动的设计思路。
模块三:熵值最小堆与传播逻辑
这是整个系统的引擎。我们将利用通义灵码来编写最容易出错的 传播 (Propagation) 逻辑。
1. 单元格类 (Cell Class)
每个格子需要记录当前还剩下哪些可能的 Tile。
public class Cell {
public int x, y;
public bool isCollapsed = false;
public List<WFCTile> possibleOptions; // 当前还能放哪些 Tile
// 熵值:可能性越多,熵越大
public int Entropy => possibleOptions.Count;
}
2. 编写传播函数 (AI 辅助)
这是一个递归或堆栈处理过程,手写极易出现死循环或索引越界。
Prompt 指令(发送给通义灵码):
"编写 WFC 算法中的
Propagate函数。 输入参数:Vector2Int startPos(刚坍缩的格子坐标)。 逻辑:
- 使用
Stack<Vector2Int>存储待检查的格子。- 遍历当前格子的四个邻居。
- 检查邻居的
possibleOptions是否与当前格子的 Socket 兼容。- 如果邻居的可能性被削减了(Constrain),将邻居加入 Stack 继续传播。
- 如果邻居的可能性降为 0,抛出异常(生成失败)。"
AI 生成代码片段(经过人工 Code Review 修正):
void Propagate(Vector2Int startPos) {
Stack<Vector2Int> stack = new Stack<Vector2Int>();
stack.Push(startPos);
while (stack.Count > 0) {
Vector2Int current = stack.Pop();
Cell currentCell = grid[current.x, current.y];
// 遍历四个方向:上、下、左、右
foreach (var dir in directions) {
Vector2Int neighborPos = current + dir;
if (!IsValid(neighborPos)) continue;
Cell neighborCell = grid[neighborPos.x, neighborPos.y];
// 核心逻辑:检查兼容性,移除不可能的选项
bool changed = Constrain(currentCell, neighborCell, dir);
if (changed) {
stack.Push(neighborPos); // 产生涟漪效应,继续传播
if (neighborCell.Entropy == 0) {
throw new ContradictionException("生成死锁!");
}
}
}
}
}
- 图注:展示了当中心格子确定为“墙壁”后,红色的“不可用标记”迅速向四周扩散,周围的格子自动剔除了“水流”的可能性。
- 目的:直观展示算法的运行过程。
模块四: 遇到“死胡同”怎么回溯?
WFC 最让人头疼的问题是 矛盾。有时候生成到一半,发现某个格子无论放什么 Tile 都对不上邻居的接口(比如被四面墙围住,但中间必须是路)。如果不处理,程序就会卡死或报错。传统的做法是:全部重来。这在 100x100 的大地图上是不可接受的,效率太低高级的做法是:回溯 (Backtracking)。
1. 建立快照机制
我们需要在每次“观察(坍缩)”之前,保存当前地图的状态。
Prompt 指令:
"优化上面的 WFC 代码,加入回溯机制。
- 定义一个
HistoryState类,深拷贝保存 grid 中每个 Cell 的possibleOptions。- 使用
Stack<HistoryState>保存历史。- 当
Propagate抛出ContradictionException时,执行Backtrack():从 Stack 中弹出一个状态,恢复 Grid,并把导致死锁的那个选项从可能性中移除,重试。"
2. 优化后的实操逻辑
通义灵码会帮你生成深拷贝(Deep Copy)的代码,这在 C# 中写起来很繁琐。
void RunWFC() {
try {
// ... 选择最小熵格子 ...
SaveState(); // 存档
CollapseCell(target); // 坍缩
Propagate(target); // 传播
}
catch (ContradictionException) {
Debug.LogWarning("死锁检测!开始回溯...");
RestoreState(); // 读档
// 关键:把刚才导致死锁的那个随机选项移除,换一个选
RemoveFailedOption();
RunWFC(); // 递归重试
}
}
通过这种“时光倒流”的机制,只要规则本身没有逻辑硬伤,WFC 总能找到一个解,或者至少不会让游戏崩溃。

- 图注:Unity Console 截图。黄色警告显示“检测到死锁,回退至 Step 45”,随后绿色日志显示“地图生成成功”。
- 目的:证明回溯机制的有效性。
模块五:性能与视觉优化
代码跑通了,但要变成商业级产品,还得做两件事。
1. 分块生成 (Chunking)
不要一次性生成 1000x1000 的地图。 将地图分为 16x16 的 Chunk。当玩家接近边缘时,触发新 Chunk 的 WFC 生成。
- 技巧:新 Chunk 的边缘必须继承旧 Chunk 边缘的 Socket 约束,这样地图才是无缝连接的。
2. 协程可视化 (Coroutine)
为了不卡死主线程(避免掉帧),将 while 循环放入 IEnumerator 中。 yield return new WaitForSeconds(0.01f); 这不仅能平滑帧率,还能让玩家看到地牢“逐格生长”的酷炫动画,甚至可以作为 Loading 界面的视觉效果。
结语
WFC 算法是程序化生成领域的一座高山,但有了 AI 辅助编程工具,爬山的难度降低了 80%。 通义灵码 帮我们搞定了繁琐的 Stack 操作、深拷贝和邻居遍历。
参考资料与开源库:
- mxgmn/WaveFunctionCollapse (GitHub) - WFC 算法的鼻祖仓库,包含 C++ 和 C# 参考。
- BorisTheBrave's WFC Guide - 全网讲得最透彻的算法图解博客。
- Self-Similar: Infinite WFC - 针对无限地图生成的变体研究。
Tags: #游戏开发 #WFC #Roguelike #地图生成 #Unity #通义灵码 #算法实战#



