莉娜是《无尽之城》的首席规划师。过去,每做一个新街区,她都要手工画好每一栋楼、每一条路;几百张图纸堆成山,玩家却几天就玩腻了。后来,她得到了一本《市政魔法书》:只要写下一串像咒语一样的种子、几页市政规范、一组参数,就能自动变出一张又一张全新的街区图纸。更神奇的是,这些图纸还能通过质检——起点能走到终点,台阶跳得过去,敌人藏得合理。这就是程序化关卡生成(PCG)的日常:用算法代替手绘,用规则和约束守住质量,用同一套逻辑在有限存储下创造无限可玩性。
掌握 PCG 意味着你拿到了“无限关卡工厂”的钥匙:既能稳定生成多样内容,又能像质检员一样保证可玩性,这是游戏开发与 AI 内容生产领域极具变现价值的硬技能。
目录
可视化演示
展示基于Perlin噪声的地形生成、元胞自动机规则约束与连通性验证的完整关卡生成流程。切换模式查看不同阶段,拖动滑块调节噪声频率。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Perlin噪声地形生成 梯度噪声:插值随机梯度向量生成平滑连续噪声场 多倍频:叠加不同频率倍频增加地形细节层次 n(x) = Σ aᵢ · noise(2ⁱ · x) Perlin噪声生成自然过渡的地形高度图,决定关卡基本结构
基础概念:从魔法定律到无限城市
莉娜上了三天规划课才想通:真正的“城市规划魔法”,不是把每一栋楼都亲手画出来,而是写下一整套“生成定律”。只要把这套定律交给计算机,它就能批量生产街区。这个思想放到游戏里,就是程序化内容生成(Procedural Content Generation,简称 PCG):让算法代替人工,自动创建关卡、地形、房间、敌人分布等内容。莉娜不再是一个画图工,而是规则的立法者。
PCG 的核心不是“随机乱涂”,而是“有约束的自动创作”。程序接收一个种子(seed)作为随机起点,接收一组规则(rules)定义边界,再接收参数(params)控制规模和难度,最终输出一个完整关卡。这个关卡可能是 200 个房间的地下城,也可能是 10 万平方米的森林。玩家看到的是一张新地图,程序里走的其实是一行行生成逻辑。
莉娜把这条公式贴在工作室墙上。她意识到,自己只需要维护规则和参数,就能在有限存储空间里变出永远玩不完的城市——这就是“有限存储,无限体验”的起点。
- 关卡变量
\text{关卡}是最终交付的内容,包括房间、走廊、敌人、宝箱等元素。 - 生成函数
G是生成器,就像莉娜的“魔法规划器”,输入材料,输出图纸。 - 三个输入
seed是随机起点,rules是市政手册,params是地块大小、怪物密度等可调旋钮。
规则驱动:市政手册里的房间摆放
莉娜的市政手册里写着:房间之间至少留 10 米防火通道,不能把房子建在湖中央,门必须能通向走廊。这套“红头文件”就是规则驱动生成法的雏形:先定义合法条件,再让程序不断尝试摆放,直到满足条件为止。
在游戏 PCG 中,规则驱动是最直观的一类方法。程序每放一个房间,就检查它是否越界、是否和已有房间重叠、是否留出了足够的通道空间。如果违反规则,这个候选房间就被拒绝,程序重新随机生成一个;如果通过,就正式写入关卡。这个过程叫“拒绝采样”:不合格的样本会被扔掉,直到采样到合格品。
莉娜把手册里的每一条规范都转成代码检查项。规则越多,生成出的城市越规矩;规则太少,房间就会像地震后一样叠在一起。随机种子则在幕后决定每张图纸的独特细节,让规则相同、结果不同。
- 候选房间
r是程序刚刚随机生成的新房间。 - 关卡集合
L是所有已经放入关卡中的房间集合。 - 全体约束
\bigwedge_{r' \in L}表示“对 L 里的每个房间都要检查一遍”。 - 不重叠判定
\neg \text{overlap}(r, r')表示新房间与旧房间不能重叠,这是最基础的市政规范。
语法生成:用建筑文法长出一片城堡
有些建筑不是把房间并排摆就行的。莉娜想建一座带塔楼的城堡,塔楼里要套小房间,小房间之间还要有回廊——这种一层套一层的关系,像极了句子的嵌套结构。于是她改用另一套工具:形状语法(Shape Grammar)。
形状语法的核心是“重写规则”:从一个起始符号 S 开始,每一条规则都把一个符号替换成一个结构片段加上一个新的符号。S 可以生成一个房间再接一个 S,也可以生成一条走廊再接一个 S,直到使用空符号 ε 停下来。整个过程像滚雪球一样递归展开,最后形成一棵“结构树”,这棵树就是关卡的骨架。
莉娜看到,城堡的塔楼、地牢的三层回廊、村庄的放射状街道,都能用同一套语法“长”出来。规则驱动擅长摆家具,语法生成则擅长画出有机生长的结构。
- 起始符号
S表示“还没生成完的结构”,是递归的起点。 - 递归生成
S \rightarrow \text{room}\ S表示先放一个房间,然后继续生成剩余部分。 - 分支选择
\mid是“或者”,程序按随机权重选择不同展开方向,产生多样化布局。 - 终止条件
\epsilon表示空串,递归在这里结束,关卡结构最终闭合。
噪声地形:用自然画笔画出山峦起伏
莉娜要建设城市外围的荒野区域,总不能一格一格给每座山画等高线。她拿起一支“噪声画笔”:笔尖带随机起伏,画出的线条既不稳定也不重复,却天然有自然的连绵感。这个工具在算法里叫 Perlin 噪声。
Perlin 噪声能生成平滑的伪随机数,相邻位置的值不会突变,因此适合模拟地形高度。但一层噪声通常不够:频率太低,地形太平;频率太高,地形又抖得像碎玻璃。莉娜的做法是叠加多层噪声——低频大振幅负责大山脉,高频小振幅负责岩石纹理,最终合成一张高度图。
莉娜拿着合成好的高度图,再用阈值划分出平原、丘陵和雪山,自然地形就这样“画”出来了。噪声本身不是地形,但它是把自然感带给关卡的魔法颜料。
- 高度函数
h(x,y)表示坐标(x,y)处的地表高度值。 - 多层叠加
\sum_{i=1}^{n}表示由 n 层噪声相加,每一层贡献一部分细节。 - 振幅控制
A_i是第 i 层噪声的振幅,振幅越大,这一层造成的落差越大。 - 频率控制
f_i是第 i 层噪声的频率,频率越高,噪声值在空间上变化越快,纹理越细密。
约束验证:质检员确保城市能住人
地图画得再漂亮,也得让玩家真能玩下去。莉娜的团队里有一群最严格的质检员,他们不关心房间漂不漂亮,只关心三件事:起点能不能走到终点?台阶是不是高到跳不过去?敌人会不会刷在墙里?这些检查就是约束验证。
PCG 的生成阶段经常产出“好看但玩不了”的关卡。比如两个房间之间没有通道,比如走廊中间横着一堵墙,比如跳跃平台间距超过角色能力极限。约束验证要做的是把这些问题扼杀在上线之前。最经典的连通性检查是广度优先搜索(BFS):从起点格子出发,像水波一样向四周扩散,如果水波能漫到终点,说明道路连通。
如果关卡没通过验证,莉娜会让程序换一个种子重新生成,或者对局部区域做自动修复:缺通道就补一条走廊,跳不过去就降低平台高度。质检员不是事后找茬,而是生成流水线里不可绕过的关卡。
- 可玩性判定
\mathrm{playable}(L)表示对关卡 L 做整体质检。 - 连通条件
\mathrm{reachable}(\mathrm{start},\mathrm{end})表示从起点能沿可行走格到达终点。 - 动作可行
\mathrm{feasible}(\mathrm{jump}, L)表示关卡中的每个跳跃段都小于角色的最大跳跃能力。 - 同时成立
\wedge表示两个条件必须都为真,缺一不可。
工程实践:把魔法装进游戏引擎
莉娜的魔法蓝图要从纸上落到手机游戏里,必须考虑现实问题:内存够不够?加载要多久?玩家存档怎么还原地图?这一章讲的是 PCG 的工程化,让它能稳定跑在游戏引擎里。
第一个问题是种子复用。玩家玩到第 100 关时,游戏不需要保存整个关卡地图,只需要保存一个整数种子。下次进入时,PCG 用相同种子重新生成一遍,地图就和上次一模一样。第二个问题是分块生成:开放世界地图巨大,不能一次全生成完。莉娜把世界切成区块,玩家走到哪,程序就生成哪一区块,用完的部分还能释放内存。
为了不让生成卡住游戏主线程,莉娜还会限制每帧生成预算:这帧只生成 30 个房间,剩下的放到下一帧。再配合缓存复用,同一块区域的生成结果可以直接复用,减少重复计算。工程实践的本质,是给魔法装上节流阀。
- 区块编号
t是玩家当前所处区块的索引,类似城市地图上的网格编号。 - 子种子
\mathrm{hash}(\mathrm{baseSeed},t)把全局种子和区块编号混合,生成该区块独有的稳定种子。 - 区块生成
\mathrm{gen}(\cdot)用子种子独立生成该区块的关卡L_t,这样不同区块可以按需加载。
AI融合:让城市学会读心术
莉娜的新课题是“会呼吸的城市”:关卡生成器能观察玩家的行为,自动变难或变容易。以前,规则和噪音是固定的;现在,AI 成了这支生成管线的军师。
AI 与 PCG 的融合有三种典型姿势。第一,强化学习训练生成器:程序不断生成关卡,让 AI 角色试玩,根据“可玩性、多样性、通关时长”计算奖励,再调整生成参数,让奖励越来越高。第二,生成式模型做风格迁移:输入一张玩家喜爱的地图风格图,让模型生成同样风格的新关卡。第三,玩家行为适配:玩家连续失败三次,系统自动缩短跳跃间距;连续通关十次,系统增加敌人密度,这就是动态难度。
莉娜看着城市在 AI 调配下时而温和、时而刺激,玩家始终停留在“有点难但能过”的心流通道里。规则给了城市骨架,噪声给了自然皮肤,AI 则让整个城市真正活了过来。
- 策略参数
\theta是生成器的可调参数,可以是神经网络权重,也可以是规则概率。 - 采样关卡
\mathbb{E}_{L \sim p_\theta}表示从当前生成器分布中采样大量关卡并计算平均收益。 - 玩家反馈
R(L,\mathrm{player})是玩家在关卡 L 上的综合回报,包含可玩性、趣味性和难度合理度。 - 最优参数
\arg\max_{\theta}表示寻找让期望回报最大的那一组参数。
AI 应用场景
场景一:手游《无尽秘境》每日地牢刷新
阶段:内容生成阶段
解决的问题:传统团队靠策划手绘副本,一周只能做一张图,玩家两天就刷完,消耗速度远超生产速度。PCG 让每张地图都由算法生成,规则保证房间不重叠,约束验证保证可达。
具体案例:运营团队把当天日期映射为一个整数种子,每天零点调用 LevelGenerator(seed=当日种子) 生成 50 层地牢。生成完跑一遍 BFS 连通性检查,不合格就换种子重生成,最终生成的种子写进活动配置表。
场景二:开放世界地形自动生成
阶段:生产管线阶段
解决的问题:手摆山体、河流和森林在 10 平方公里级开放世界中不现实。利用多层 Perlin 噪声生成高度图,再用规则映射草地、岩石、雪线,最后按生物群系规则生成植被。
具体案例:技术美术写了一份“地形调料单”:低频大振幅噪声生成大山脉,中频噪声叠出丘陵起伏,高频小振幅噪声补充岩石纹理。然后用海拔阈值决定地面材质,最后让规则系统在平地上散布树木。
场景三:基于玩家行为的动态难度调节
阶段:运行期动态调优
解决的问题:固定难度会导致新手流失或老手无聊。AI 观察玩家操作数据,动态修改 PCG 参数,让关卡难度始终匹配玩家能力。
具体案例:平台跳跃游戏中,系统统计玩家最近 20 次跳跃的成功率。成功率低于 40% 时,自动减小平台间距;高于 80% 时,增加平台间距并加入更多移动敌人,实现动态难度。
Python 演示
import randomfrom collections import deque class LevelGenerator: def __init__(self, seed=42, width=50, height=30): self.seed = seed self.rng = random.Random(seed) self.width = width self.height = height self.rooms = [] self.corridors = [] self.grid = [['#' for _ in range(width)] for _ in range(height)] def _make_room(self): w = self.rng.randint(3, 7) h = self.rng.randint(3, 6) x = self.rng.randint(1, self.width - w - 1) y = self.rng.randint(1, self.height - h - 1) return (x, y, w, h) def _overlaps(self, room, margin=1): for (x2, y2, w2, h2) in self.rooms: if (room[0] < x2 + w2 + margin and room[0] + room[2] + margin > x2 and room[1] < y2 + h2 + margin and room[1] + room[3] + margin > y2): return True return False def _carve(self, room): x, y, w, h = room for i in range(x, x + w): for j in range(y, y + h): self.grid[j][i] = '.' def generate_rooms(self): while len(self.rooms) < 6: room = self._make_room() if not self._overlaps(room): self.rooms.append(room) self._carve(room) if len(self.rooms) >= 8: break def generate_corridors(self): for i in range(1, len(self.rooms)): x1, y1, _, _ = self.rooms[i - 1] x2, y2, _, _ = self.rooms[i] x1 += 1; y1 += 1 x2 += 1; y2 += 1 mx = (x1 + x2) // 2 self._h_line(x1, mx, y1) self._v_line(y1, y2, mx) self._h_line(mx, x2, y2) self.corridors.append((x1, y1, x2, y2)) def _h_line(self, x1, x2, y): for x in range(min(x1, x2), max(x1, x2) + 1): self.grid[y][x] = '.' def _v_line(self, y1, y2, x): for y in range(min(y1, y2), max(y1, y2) + 1): self.grid[y][x] = '.' def check_connectivity(self, start=0, end=-1): if not self.rooms: return False start_room = self.rooms[start] end_room = self.rooms[end] start = (start_room[0] + 1, start_room[1] + 1) end = (end_room[0] + 1, end_room[1] + 1) q = deque([start]) seen = {start} while q: x, y = q.popleft() if (x, y) == end: return True for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): nx, ny = x + dx, y + dy if 0 <= nx < self.width and 0 <= ny < self.height and self.grid[ny][nx] == '.' and (nx, ny) not in seen: seen.add((nx, ny)) q.append((nx, ny)) return False def generate(self): self.generate_rooms() self.generate_corridors() return { 'rooms': self.rooms, 'corridors': self.corridors, 'playable': self.check_connectivity(), 'grid': self.grid, } if __name__ == '__main__': level = LevelGenerator(seed=2024) data = level.generate() print('rooms:', len(data['rooms'])) print('playable:', data['playable']) for row in data['grid']: print(''.join(row))
self.rng = random.Random(seed)用种子构造独立随机源,保证相同种子生成相同关卡。if not self._overlaps(room):规则驱动的拒绝采样:只有不重叠的房子才能被接受。self._carve(room)把房间写入网格,'#' 是墙,'.' 是地板。mx = (x1 + x2) // 2选择 L 型走廊的转折点。self.grid[y][x] = '.'走廊占用的格子也标记为可行走地板。q = deque([start])BFS 从起点房间开始扩散。for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):四个方向搜索邻居格。return True一旦到达终点房间,说明关卡连通,约束验证通过。'playable': self.check_connectivity()最终把可玩性作为关卡元数据输出。
常见误区
误区PCG 不就是 random 一下吗?我给随机数设个种子,就能得到无限关卡。
正解随机只是抽样工具,不是生成策略。PCG 要有规则(如何放房间)、语法(结构如何展开)、噪声(地形如何起伏)和约束验证(是否可玩)。没有这些,随机数只会吐出一堆垃圾布局。
为何常见:因为看到生成结果每次不同,而且随机种子能复现,所以误以为随机就是全部。
误区既然算法自动生成,肯定每张图都符合我的要求,不需要人工验收。
正解生成器本身也可能违反约束:房间重叠、两个区域不连通、跳不过去。PCG 的“自动”是指生成过程自动,不是质量保证自动。必须跑约束验证,甚至加自动修复或换种子重试。
为何常见:手绘地图时,策划本身就是质检员;程序生成后少了这道人工眼,大家容易想当然。
误区只要把种子数量从 1 亿提到 100 亿,玩家就不会腻。
正解种子只决定随机序列的起点,决定体验的是规则表达能力和约束质量。如果规则单一,100 亿个种子也只是 100 亿个长得差不多的地牢。多样性来自设计空间,不是种子数量。
为何常见:宣传语爱说“海量关卡”,让大家把数据量当成内容量。
误区跑一个 Perlin 噪声,得到灰度图,黑色是深渊、白色是雪顶,这不就是地形吗?
正解噪声只是数值场,需要映射成地形语义:阈值切分决定平原/丘陵/山脉,再配合规则生成植被和河流。直接硬套会出现悬崖穿模、河流悬空等离谱地形。
为何常见:噪声可视化后画面很像地形,新手容易把中间产物当成最终产物。
误区未来 AI 生成器自己会学,传统规则和约束验证都会过时。
正解当前 AI 生成依然需要规则编码可玩性约束(如碰撞、可达性),或在生成后跑验证阶段。AI 擅长风格化与多样性,规则擅长硬约束;PCG 的最佳实践是二者协作。
为何常见:看到 AI 生成的图片或关卡很惊艳,误以为硬约束也能凭空学会。
面试题
Q1. PCG 的核心目标是?
- A.减少游戏所有代码量
- B.用算法自动创建内容← 正确答案
- C.取代全部人工策划
- D.提高画面渲染帧率
答案:B。PCG 是“程序化内容生成”,核心是用算法自动生成关卡等内容。它可能减少美术和策划工作量,但不会取代全部人工;帧率属于渲染优化问题。就像莉娜用魔法代替手绘街景,但不是让魔法师去做游戏引擎的活。
Q2. 相同随机种子通常可以?
- A.让每次生成完全不同
- B.复现相同关卡结果← 正确答案
- C.消除所有重叠房间
- D.提升关卡运行速度
答案:B。随机种子是伪随机序列的起点,相同种子会得到相同随机数,因此能复现同一个关卡。重叠消除靠规则,速度优化靠工程手段,都和种子无关。
Q3. 规则驱动生成中“拒绝采样”是指?
- A.拒绝玩家进入房间
- B.生成后移除所有走廊
- C.若违反规则则重新尝试← 正确答案
- D.采样器强制选择固定点
答案:C。规则驱动会不断生成候选房间,如果它重叠或越界,就拒绝这个候选并重新采样。就像市政手册说不能建在湖心,施工队就换个地基继续试。
Q4. 语法生成里的“重写规则”最像哪种过程?
- A.将句子主语递归替换← 正确答案
- B.对地图执行模糊滤波
- C.对噪声做傅里叶变换
- D.将存档压缩成字符串
答案:A。语法生成中 S → 房间 S | 走廊 S | ε 就像自然语言文法把非终结符不断替换成具体结构。模糊滤波、傅里叶变换和压缩都不是递归结构展开。
Q5. Perlin 噪声的“频率”主要控制什么?
- A.山峰的颜色饱和度
- B.地形起伏的空间密度← 正确答案
- C.玩家的移动速度大小
- D.房间数量的随机分布
答案:B。频率越高,噪声值在空间上变化越快,地形细节越密。颜色饱和度、移动速度和房间分布与噪声频率没有直接关系。
Q6. 关卡约束验证通常包括哪些?
- A.起点终点可达性← 正确答案
- B.跳跃距离可行性← 正确答案
- C.贴图颜色是否美观
- D.敌人刷新是否合理← 正确答案
答案:ABD。可玩性约束关心能否走通、动作是否可行、敌人是否可到达。贴图颜色属于美术评审,不属于可玩性约束验证。
Q7. 检查关卡连通性最常用的算法是?
- A.深度优先遍历房间列表
- B.广度优先搜索可行格← 正确答案
- C.最小生成树连接房间
- D.快速排序查找出口
答案:B。BFS 从起点格子向四邻域扩散,直到终点,能判断道路是否连通;最小生成树是生成算法,快速排序是排序算法,都不负责连通性检测。
Q8. 在工程中控制生成性能的常用手段有哪些?
- A.分块生成延迟加载← 正确答案
- B.限制每帧生成预算← 正确答案
- C.缓存复用生成结果← 正确答案
- D.关闭所有校验步骤
答案:ABC。分块、帧预算、缓存都是高性能 PCG 的常见做法。关闭校验能提速但会牺牲可玩性,属于饮鸩止渴。
Q9. 玩家行为适配生成关卡的核心思想是?
- A.让玩家手动编辑种子
- B.根据表现调整难度参数← 正确答案
- C.定期随机重排关卡
- D.固定生成同一地图
答案:B。根据玩家成功率、死亡位置等表现动态调整生成参数,比如跳跃距离或敌人密度,这是动态难度的核心。随机重排不基于行为,固定地图更不是适配。
Q10. 关于 PCG 与 AI 生成的说法正确的有?
- A.可结合强化学习优化关卡← 正确答案
- B.生成式模型可做风格迁移← 正确答案
- C.AI 生成无需约束验证
- D.玩家数据可驱动自适应← 正确答案
答案:ABD。强化学习可以按奖励生成并优化关卡;生成式模型可以迁移地图风格;玩家数据能驱动难度适配。但 AI 输出仍可能违反硬约束,必须保留验证与修复环节。