事情是这样的,有个建模师朋友跟我聊天:
朋友:雕地形把颈椎雕坏了,能不能帮我写一个代码自动生成地形?
我:可以…
于是有了这篇文章。
今天,就带大家揭秘 “无限地形生成” 的底层技术。
这项技术不仅是《Minecraft》、《Terraria》、《No Man’s Sky》这些大作背后的核心技术之一!
更能让你用几行代码,在 Cocos 中亲手开垦出属于自己的“无限大陆”!
关键技术 —— 噪声
这里说的“噪声”,不是电流里的杂音,而是指一种连续的伪随机函数。
以下是常见的噪声生成函数:
-
Perlin Noise(柏林噪声):经典,很多老游戏用。 -
Simplex Noise:改进版,性能更好。 -
OpenSimplex Noise:社区维护的版本,专门解决专利和维度问题。
它们的特点是:
-
看起来像随机,但其实是连续的。 -
相邻的输入点,输出不会差太多,像自然界里的山脉、河流一样。 -
输入坐标可以无限扩展,所以理论上能生成无限地图。
以下是使用柏林噪声和白噪声(纯随机)的对比图,可以看到柏林噪声生成的数值是连续的。

由于本文更关注于噪声的应用,因此关于噪声的原理就不再赘述,大家可以自行了解。
游戏里怎么用?
很多你耳熟能详的游戏,都在背后默默用着这项技术:
|
|
|
|
|
|---|---|---|---|
| Minecraft |
|
|
|
| Terraria |
|
|
|
| No Man’s Sky |
|
|
|

在 Cocos Creator 里实践:用噪声生成地形
光说原理可能有点抽象,咱们直接来点实战。
在 Cocos Creator 里,配合OpenSimplexNoise和Terrain组件,我们可以很快就搞出一个“无限地图的雏形”。
我们这里将会使用开源的open-simplex-noise库,可以直接使用npm安装,并在 Cocos 工程中使用。
1. 最简单的噪声生成
我们先写一个最简单的函数:直接用噪声生成高度,不加任何花里胡哨的参数。
import { _decorator, Component, Terrain, CCInteger, HeightField } from'cc';
import OpenSimplexNoise from"open-simplex-noise";
import { type Noise2D } from"open-simplex-noise/lib/2d";
const { makeNoise2D } = OpenSimplexNoise;
const { ccclass, property, executeInEditMode } = _decorator;
@ccclass('TerrainDemo')
@executeInEditMode(true) // 关键!让脚本在编辑器模式下运行
exportclass TerrainDemo extends Component {
@property(Terrain)
public terrain: Terrain = null!;
@property({ type: CCInteger, range: [0, 999999, 1] })
public seed: number = 12345;
// 编辑器按钮:生成地形
@property({
displayName: "🎲 生成地形",
tooltip: "点击生成基础噪声地形"
})
get generateButton() { returnfalse; }
set generateButton(value: boolean) {
if (value) this.generateTerrain();
}
private noise2D: Noise2D;
onLoad() {
this.noise2D = makeNoise2D(this.seed);
}
// 最简单的噪声高度生成
private generateHeight(x: number, z: number): number {
// 直接返回噪声值,范围 [-1, 1]
returnthis.noise2D(x, z);
}
// 生成地形的核心方法
public generateTerrain() {
console.log('开始生成基础噪声地形...');
const info = this.terrain.info;
const tileCount = info.tileCount;
const tileSize = info.tileSize;
// 创建高度场
const heightField = new HeightField(tileCount[0], tileCount[1]);
// 计算地形的世界尺寸
const terrainWorldWidth = tileCount[0] * tileSize;
const terrainWorldHeight = tileCount[1] * tileSize;
const startWorldX = -terrainWorldWidth / 2;
const startWorldZ = -terrainWorldHeight / 2;
// 为每个瓦片生成高度
for (let z = 0; z 1]; z++) {
for (let x = 0; x 0]; x++) {
const worldX = startWorldX + x * tileSize;
const worldZ = startWorldZ + z * tileSize;
// 使用最简单的噪声生成
const noiseValue = this.generateHeight(worldX, worldZ);
// 转换为 Cocos Creator 地形高度格式
const finalHeight = 32768 + noiseValue * 1000; // 简单缩放
heightField.set(x, z, finalHeight);
}
}
// 应用到地形
this.terrain.importHeightField(heightField, 1);
this.terrain.rebuild(this.terrain.info);
console.log('基础地形生成完成!');
}
}
在编辑器中新建地形节点并绑定好脚本。
然后点击编辑器中的生成地形后,就可以直接在编辑器中看到效果了。
这样得到的地形是连续的,而非跳变的,尽管已经可以直接使用,但我们还需要进一步操作修改。

2. 添加高度缩放控制
原始噪声值的范围通常在 [-1, 1] 之间,这个范围对于地形高度来说太小了。
我们需要一个参数heightScale,来控制地形的整体高度范围。
在组件中添加属性:改进 generateHeight 方法
@ccclass('TerrainDemo')
@executeInEditMode(true)
exportclass TerrainDemo extends Component {
/** ... */
@property
public heightScale: number = 20; // 高度缩放
private generateHeight(x: number, z: number): number {
const n = this.noise2D(x, z); // 原始噪声值 [-1,1]
return n * this.heightScale; // 缩放到给定的高度范围
}
/** ... */
}

heightScale的作用很直观,就是单纯的放大原始的噪声值:
-
heightScale = 10→ 地形高度范围 [-10, 10],比较平缓 -
heightScale = 50→ 地形高度范围 [-50, 50],起伏较大 -
heightScale = 100→ 地形高度范围 [-100, 100],高山深谷
实际应用中的经验值:
-
平原地形: heightScale = 5-15 -
丘陵地带: heightScale = 20-40 -
山地地形: heightScale = 50-100 -
极地地形: heightScale = 100+
这个参数是最容易理解和调节的,也是在调试地形时首先要确定的。
3. 加入 noiseScale —— 控制采样粒度
有时候噪声“起伏得太快”,导致地图像一堆抖动的沙子。
这时候就需要设置参数noiseScale,它能拉伸噪声的坐标空间,在更小尺度的空间里采样,让地形更平滑或更紧凑。
@ccclass('TerrainDemo')
@executeInEditMode(true)
exportclass TerrainDemo extends Component {
/** ... */
@property
public noiseScale: number = 0.05; // 噪声缩放
private generateHeight(x: number, z: number): number {
const n = this.noise2D(x * this.noiseScale, z * this.noiseScale);
return n * this.heightScale;
}
/** ... */
}
-
noiseScale 小 → 波动慢,适合大片平原/缓坡。 -
noiseScale 大 → 波动快,适合碎石滩/陡峭小山。
下面两张图中,第二张是0.1倍的缩放,可以看到绿色的框就是第一张图0.05倍的结构。
因为噪声的尺度被放大了两倍,所以这1/4个区域采样的是之前的那部分噪声。


4. 加入 octaves、persistence 和 lacunarity 增加细节层次
自然界的地形往往不是单一频率的,而是包含:
-
大山的起伏 -
小丘陵的细节 -
石块的凹凸
因此,我们常用分形噪声(Fractal Noise)的方法来叠加多层噪声。
@ccclass('TerrainDemo')
@executeInEditMode(true)
exportclass TerrainDemo extends Component {
/** ... */
@property
public octaves: number = 4; // 噪声层数
@property
public persistence: number = 0.5; // 持续性
@property
public lacunarity: number = 2.0; // 间隙度
// 使用多层噪声生成高度
private generateHeight(x: number, z: number): number {
let height = 0;
let amplitude = this.heightScale; // 初始强度
let frequency = this.noiseScale; // 初始频率
for (let i = 0; i this.octaves; i++) {
height += this.noise2D(x * frequency, z * frequency) * amplitude;
amplitude *= this.persistence; // 每层衰减
frequency *= this.lacunarity; // 每层频率增加
}
return height;
}
/** ... */
}
这几个参数的含义如下:
-
heightScale:地形起伏的整体高度。 -
noiseScale:噪声的采样间距,越小越平滑,越大越碎裂。 -
octaves:叠加噪声层数,越多越细节丰富。 -
persistence:每层噪声的强度衰减,越小,细节越柔和。 -
lacunarity:每层噪声的频率增加倍数,越大,细节越密集。


可以这么理解:
-
想要大平原→ noiseScale小一点,octaves 少一点。 -
想要高山密布→ heightScale拉大,octaves 增加。 -
想要碎石滩→ noiseScale大一点,地形就会更破碎。
这就是噪声的魅力,一切尽在参数掌控之中。
5. 利用曲线调控地形
光靠噪声函数算出来的高度,虽然已经比纯随机好看很多,但有时候地形还是“太数学”,不够自然。
比如可能会出现高原过于稀少、山峰太尖锐、或者平原面积不够大的情况。
这时候就可以用曲线来调节。
利用曲线,可以把“原始噪声值”映射成“更符合需求的高度值”。
为了方便调试,我们这里将随机种子也放到参数中:
import { RealCurve } from'cc';
@ccclass('TerrainDemo')
@executeInEditMode(true)
exportclass TerrainDemo extends Component {
/** ... */
@property(RealCurve)
curve: RealCurve = new RealCurve();
private _seed: number = 0;
@property({ type: CCInteger, range: [-999999, 999999, 1] })
get seed() {
returnthis._seed;
};
set seed(seed: number) {
this._seed = seed;
// 修改种子后重新创建噪声生成器
this.noise2D = makeNoise2D(this.seed);
}
// 在地形生成中使用曲线调整
private applyHeightCurve(noiseHeight: number): number {
// 将噪声值标准化到 [0, 1] 范围
const normalizedNoise = (noiseHeight + this.heightScale) / (2 * this.heightScale);
// 应用曲线来调整高度分布
const curveValue = this.curve.evaluate(normalizedNoise);
// 转换为最终高度
return curveValue * this.heightScale;
}
/** ... */
}
这样我们就能通过拖动曲线点,直观地控制地形比例:
-
平原想大一点?就在曲线前段拉平。
-
山脉想更陡峭?就在后段加陡。
-
想要大面积海洋?把 0 附近压低。
由于小尺度的地形很难看出效果,所以这里我将地形瓦片调整到了5*5。
然后我调了一个曲线,可以生成很好看的山地和平原地形。



无限地图是怎么做到的?
到目前为止,原理其实非常简单:
噪声函数的输入是坐标 (x, z),输出是一个确定的值。
所以当玩家走到地图边界时,只要继续往外算新的坐标点,地形就会自然延展,理论上是无限的。
配合分块加载技术,玩家周围可以生成若干块地形。
玩家移动时,卸载远处的块,加载新的块,这样就能模拟出无限世界,是不是很酷!
总结与进阶
噪声函数是一个看似简单却威力巨大的工具,它能生成自然、连续、可控的数据。
配合参数和曲线,就能灵活定制出符合游戏设计需求的地形。
再加上分块加载,就能实现无限可玩的地图。

所以,下次你在 Minecraft 里挖矿时,别忘了背后支撑这一切的,可能就是几行噪声函数的代码。
从地形到生态
到这里我们只是生成了高度地图,但游戏世界远不止“凹凸的地面”。
噪声的妙用在于,它可以被重复利用,生成更多“有机”的内容:
-
环境划分
用一组噪声来决定气候区,比如高纬度是雪原,低纬度是沙漠,中间是森林。 -
生物群落
可以用噪声来分布怪物或动物,例如:某些区域狼群密集,某些地方只有羊驼。 -
植被分布
再加一层噪声,就能控制森林的浓密度:某些块长满树,某些块只有零星几棵。 -
矿物/资源
类似的思路还能用来生成地下矿脉分布,像《Minecraft》就是这么干的。
换句话说:
地形噪声只是“基底”,你可以继续在它上面叠加生态噪声、资源噪声、气候噪声……
这样一步步,就能拼出一个真正无限而且生机勃勃的世界。

作者介绍:

感谢还有醋v的投稿,更多干货内容,可以关注他的公众号。
有需要 demo 的朋友可以在以下链接获取:
“
demo 地址:https://store.cocos.com/app/detail/8191
感谢阅读,希望这篇文章对你有所帮助!
欢迎在评论区交流心得,一起进步!

关注 Cocos 官方公众号
获取更多干货内容