我的网站上最受欢迎的文章之一是
关于多边形地图的
生成 (Habré中的
翻译 )。 创建这样的卡需要大量的努力。 但是我不是从这个开始的,而是从一个简单得多的任务开始的,我将在这里进行描述。 这种简单的技术使您可以用少于50行的代码创建此类卡:
我不会解释如何
绘制此类卡:它取决于语言,图形库,平台等。 我将仅说明如何使用地图数据
填充数组 。
噪音
生成2D贴图的标准方法是使用有限频段的噪声作为构建块,例如Perlin噪声或单工噪声。 噪声函数如下所示:
我们为地图上的每个点分配一个介于0.0到1.0之间的数字。 在此图像中,0.0是黑色,而1.0是白色。
以下是使用类C语言的语法设置每个网格点颜色的方法:
for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { double nx = x/width - 0.5, ny = y/height - 0.5; value[y][x] = noise(nx, ny); } }
该循环在Javascript,Python,Haxe,C ++,C#,Java和大多数其他流行语言中都将起作用,因此我将以类似C的语法显示它,以便将其转换为所需的语言。 在本教程的其余部分中,我将展示添加新函数时循环主体的变化(线
value[y][x]=…
)。 该演示将显示一个完整的示例。
在某些库中,有必要对结果值进行移位或相乘,以便将它们返回的范围从0.0到1.0。
身高
噪声本身只是数字的集合。 我们需要赋予它
意义 。 您可以想到的第一件事是将噪声值绑定到高度(这称为“高度图”)。 让我们以上面显示的噪声并将其绘制为高度:
除了内部循环外,代码几乎相同。 现在看起来像这样:
elevation[y][x] = noise(nx, ny);
是的,就是这样。 地图数据保持不变,但现在我将其称为
elevation
(高度),而不是
value
。
我们有很多山丘,但仅此而已。 怎么了
频次
噪声可以在任何
频率下产生。 到目前为止,我只选择了一种频率。 让我们看看它是如何影响的。
尝试使用滑块 (在原始文章中)
更改值,并查看在不同频率下会发生什么:
它只是改变比例。 起初,这似乎不是很有用,但事实并非如此。 我还有另外一本
教程 (用Habré
翻译 )解释了该
理论 :诸如频率,幅度,八度,粉红色和蓝色噪声等概念。
elevation[y][x] = noise(freq * nx, freq * ny);
有时召回与频率成反比
的波长有时也很有用。 当频率增加一倍时,大小只会减半。 波长加倍,所有光子都加倍。 波长是以像素/图块/米或您为地图选择的任何其他单位测量的距离。 它与频率有关:
wavelength = map_size / frequency
。
八度
为了使高度图更有趣,我们将
添加不同频率的噪声 :
elevation[y][x] = 1 * noise(1 * nx, 1 * ny); + 0.5 * noise(2 * nx, 2 * ny); + 0.25 * noise(4 * nx, 2 * ny);
让我们在一张地图中混合大型低频山丘和小型高频山丘。
移动滑块 (在原始文章中),以向混合添加小丘陵:
现在,它更像我们需要的分形浮雕! 我们可以获得丘陵和崎rough的山脉,但仍然没有平坦的平原。 为此,您还需要其他一些东西。
重新分配
噪声函数为我们提供0到1之间的值(或从-1到+1,取决于库)。 要创建平坦的平原,我们可以
将高度提高到幂 。
移动滑块 (在原始文章中)获得不同的角度。
e = 1 * noise(1 * nx, 1 * ny); + 0.5 * noise(2 * nx, 2 * ny); + 0.25 * noise(4 * nx, 4 * ny); elevation[y][x] = Math.pow(e, exponent);
较高的值会
降低到达平原的平均高度 ,而较低的值会提高到达山峰的平均高度。 我们需要省略它们。 我使用幂函数是因为它们更简单,但是您可以使用任何曲线。 我有一个更复杂的
演示 。
现在我们有了一个逼真的海拔图,让我们添加生物群落!
生物群落
噪声给出数字,但是我们需要一张包含森林,沙漠和海洋的地图。 您可以做的第一件事是将小高地变成水:
function biome(e) { if (e < waterlevel) return WATER; else return LAND; }
哇,这已经变得像程序产生的世界了! 我们有水,草和雪。 但是,如果我们需要更多呢? 让我们来序列水,沙,草,森林,大草原,沙漠和雪:
根据身高的救济 function biome(e) { if (e < 0.1) return WATER; else if (e < 0.2) return BEACH; else if (e < 0.3) return FOREST; else if (e < 0.5) return JUNGLE; else if (e < 0.7) return SAVANNAH; else if (e < 0.9) return DESERT; else return SNOW; }
哇,看起来很棒! 对于您的游戏,您可以更改值和生物群系。 孤岛危机将会有更多的丛林。 天际有更多的冰雪。 但是,无论您如何更改数字,此方法都非常有限。 凸版类型对应于高度,因此形成条状。 为了使它们更有趣,我们需要基于其他内容来选择生物群落。 让我们为湿度创建
第二个噪声图。
上面是高处的噪音; 底部-湿度噪声现在,让我们
一起使用高度和湿度。 在下面显示的第一个图像中,y轴是高度(从上图获取),x轴是湿度(第二个图像更高)。 这给了我们一个引人注目的地图:
基于两个噪声值的缓解小高度是海洋和海岸。 大高度是岩石和下雪。 在这之间,我们得到了各种各样的生物群落。 代码如下:
function biome(e, m) { if (e < 0.1) return OCEAN; if (e < 0.12) return BEACH; if (e > 0.8) { if (m < 0.1) return SCORCHED; if (m < 0.2) return BARE; if (m < 0.5) return TUNDRA; return SNOW; } if (e > 0.6) { if (m < 0.33) return TEMPERATE_DESERT; if (m < 0.66) return SHRUBLAND; return TAIGA; } if (e > 0.3) { if (m < 0.16) return TEMPERATE_DESERT; if (m < 0.50) return GRASSLAND; if (m < 0.83) return TEMPERATE_DECIDUOUS_FOREST; return TEMPERATE_RAIN_FOREST; } if (m < 0.16) return SUBTROPICAL_DESERT; if (m < 0.33) return GRASSLAND; if (m < 0.66) return TROPICAL_SEASONAL_FOREST; return TROPICAL_RAIN_FOREST; }
如有必要,您可以根据游戏要求更改所有这些值。
如果我们不需要生物群落,那么平滑的渐变(请参阅
本文 )可以创建颜色:
对于生物群系和梯度,单个噪声值不能提供足够的可变性,但是两个就足够了。
气候状况
在上一节中,我用
海拔代替了
温度 。 高度越高,温度越低。 但是,地理纬度也会影响温度。 让我们同时使用高度和纬度来控制温度:
在两极(大纬度)附近,气候较冷,而在山顶(大高度)上,气候也较冷。 到目前为止,我的工作还不是很辛苦:要正确使用这些参数,您需要进行许多细微的设置。
还有
季节性的气候变化。 在夏季和冬季,北半球和南半球变暖和变冷,但在赤道,情况变化不大。 在这里也可以做很多事情,例如,可以模拟盛行的风和洋流,生物群落对气候的影响以及海洋对温度的平均影响。
岛屿
在某些项目中,我需要地图的边框为水。 这将世界变成一个或多个岛屿。 有很多方法可以做到这一点,但是我在多边形地图生成器中使用了一个非常简单的解决方案:我将高度更改为
e = e + a - b*d^c
,其中
d
是距中心的距离(比例为0-1)。 另一种选择是更改
e = (e + a) * (1 - b*d^c)
。 常数
a
使所有事物升高,
b
使边缘降低,而
c
控制下降率。
我对此并不完全满意,还有很多事情要做。 应该是曼哈顿距离还是欧几里得距离? 它应该取决于到中心的距离还是到边缘的距离? 距离应该是平方的,还是线性的,或具有其他程度? 应该是加/减,乘/除,还是其他? 在原始文章中,
尝试加,a = 0.1,b = 0.3,c = 2.0,或
尝试乘以a = 0.05,b = 1.00,c = 1.5。 适合您的选项取决于您的项目。
为什么要坚持使用标准数学函数? 正如我在
有关RPG中的损坏 (在Habré上
翻译 )中所讲的那样,每个人(包括我在内)都使用数学函数,例如多项式,指数分布等,但是在计算机上,我们不能局限于此。 我们可以使用查找表
e = e + height_adjust[d]
来获取
任何形成函数并在此处使用。 到目前为止,我还没有研究过这个问题。
尖刺的噪音
除了将高度提高到幂之外,我们可以使用绝对值创建尖峰:
function ridgenoise(nx, ny) { return 2 * (0.5 - abs(0.5 - noise(nx, ny))); }
要增加八度,我们可以改变高频的幅度,以便只有山峰才能接收到增加的噪声:
e0 = 1 * ridgenoise(1 * nx, 1 * ny); e1 = 0.5 * ridgenoise(2 * nx, 2 * ny) * e0; e2 = 0.25 * ridgenoise(4 * nx, 4 * ny) * (e0+e1); e = e0 + e1 + e2; elevation[y][x] = Math.pow(e, exponent);
我对这项技术没有太多的经验,所以我需要进行实验以学习如何很好地使用它。 将尖峰的低频噪声与非尖峰的高频噪声混合可能也很有趣。
梯田
如果将高度四舍五入到下n个水平,则会得到梯田:
这是应用
e = f(e)
形式的高度重新分配函数的结果。 上面,我们使用
e = Math.pow(e, exponent)
来锐化山峰; 在这里,我们使用
e = Math.round(e * n) / n
创建平台。 如果使用非步进功能,则梯形可以倒圆或仅出现在特定高度。
树的位置
通常我们使用分形噪声来表示高度和湿度,但也可以用来放置空间不均匀的物体,例如树木和石头。 对于高度,我们使用低频的高振幅(“红色噪声”)。 要放置物体,您需要使用高频高振幅(“蓝噪声”)。 左边是蓝色的噪音图案。 右边是噪声大于相邻值的地方:
for (int yc = 0; yc < height; yc++) { for (int xc = 0; xc < width; xc++) { double max = 0;
为每个生物群系选择不同的R,我们可以获得可变的树木密度:
可以使用这种噪声来放置树是很棒的,但是其他算法通常更有效并且可以创建更均匀的分布:泊松点,范图块或图形抖动。
超越无限
在位置(x,y)的生物群系的计算独立于所有其他位置的计算。 这种
局部计算具有两个方便的属性:可以并行计算,并且可以用于无尽的地形。
将鼠标光标放在左侧
的小地图上 (在原始文章中),以在右侧生成地图。 您可以生成卡的任何部分而无需生成(甚至不存储)整个卡。


实作
使用噪声生成地形是一种流行的解决方案,在Internet上,您可以找到针对许多不同语言和平台的教程。 用于生成不同语言的卡的代码大致相同。 这是三种不同语言的最简单循环:
- Javascript:
let gen = new SimplexNoise(); function noise(nx, ny) {
- C ++:
module::Perlin gen; double noise(double nx, double ny) {
- Python:
from opensimplex import OpenSimplex gen = OpenSimplex() def noise(nx, ny):
所有的噪声库都差不多。
对于Python ,请尝试
opensimplex; 对于C ++ ,请尝试
libnoise;对于Javascript,请尝试
simplex-noise 。 对于大多数流行的语言,有许多噪声库。 或者,您可以了解Perlin噪音的工作原理或亲自了解噪音。
我没做在针对您的语言的不同噪声库中,应用程序的详细信息可能会略有不同(一些返回值的范围是0.0到1.0,其他返回值的范围是-1.0到+1.0),但是基本思想是相同的。 对于真实的项目,可能需要将
noise
函数和
gen
对象包装在一个类中,但是这些细节无关紧要,因此我将它们设置为全局。
对于这样一个简单的项目,使用什么噪声都没有关系:Perlin噪声,单纯形噪声,OpenSimplex噪声,值噪声,中点偏移,菱形算法或傅立叶逆变换。 它们每个都有其优缺点,但是对于类似的卡生成器,它们都创建或多或少相同的输出值。
地图
的渲染取决于平台和游戏,因此我没有实现它。 该代码仅用于生成高度和生物群落,其渲染取决于游戏中使用的样式。 您可以在项目中复制,移植和使用它。
实验
我研究了混合八度音阶,将度数提升至幂,以及将高度与湿度结合起来以创建生物群系。
在这里,您可以研究一个交互式图形,该图形可以让您试验所有这些参数,从而显示代码组成:
这是一个示例代码:
var rng1 = PM_PRNG.create(seed1); var rng2 = PM_PRNG.create(seed2); var gen1 = new SimplexNoise(rng1.nextDouble.bind(rng1)); var gen2 = new SimplexNoise(rng2.nextDouble.bind(rng2)); function noise1(nx, ny) { return gen1.noise2D(nx, ny)/2 + 0.5; } function noise2(nx, ny) { return gen2.noise2D(nx, ny)/2 + 0.5; } for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var nx = x/width - 0.5, ny = y/height - 0.5; var e = (1.00 * noise1( 1 * nx, 1 * ny) + 0.50 * noise1( 2 * nx, 2 * ny) + 0.25 * noise1( 4 * nx, 4 * ny) + 0.13 * noise1( 8 * nx, 8 * ny) + 0.06 * noise1(16 * nx, 16 * ny) + 0.03 * noise1(32 * nx, 32 * ny)); e /= (1.00+0.50+0.25+0.13+0.06+0.03); e = Math.pow(e, 5.00); var m = (1.00 * noise2( 1 * nx, 1 * ny) + 0.75 * noise2( 2 * nx, 2 * ny) + 0.33 * noise2( 4 * nx, 4 * ny) + 0.33 * noise2( 8 * nx, 8 * ny) + 0.33 * noise2(16 * nx, 16 * ny) + 0.50 * noise2(32 * nx, 32 * ny)); m /= (1.00+0.75+0.33+0.33+0.33+0.50); } }
有一个困难:对于高湿噪声,必须使用不同的种子,否则它们将变成相同的种子,并且卡片看起来不会那么有趣。 在Javascript中,我使用
prng-parkmiller库 ; 在C ++中,可以使用两个单独的
linear_congruential_engine对象 ; 在Python中,您可以创建
random.Random类的两个单独的实例。
思想
我喜欢这种方法来
简化地图生成。 它速度快,只需要很少的代码即可产生不错的结果。
我不喜欢他在这种方法上的局限性。 本地计算意味着每个点都独立于所有其他点。 地图的不同区域
没有相互连接 。 地图上的每个地方“似乎”都一样。 没有全球性的限制,例如“在地图上应该有3至5个湖泊”或全球性的特征,例如从最高峰的顶部流入海洋的河流。 另外,我不喜欢这样的事实,为了获得良好的画面,您需要长时间配置参数。
我为什么推荐它? 我认为这是一个很好的起点,特别是对于独立游戏和游戏果酱。 我的两个朋友
在短短30天的
游戏大赛中写下了
“狂神王国”的最初版本。 他们要求我帮助创建地图。 我使用了这种技术(加上一些其他功能,这些功能原来并不是很有用),并为它们绘制了地图。 几个月后,在收到玩家的反馈并仔细研究了游戏的设计之后,我们基于Voronoi多边形创建了一个更高级的地图生成器,
在此进行了介绍(
译自 Habré)。 此卡生成器不使用本文所述的技术。 它使用噪音以完全不同的方式创建地图。
附加信息
噪声功能可以完成
很多很酷的事情。 如果您在Internet上搜索,则可以找到各种选项,例如湍流,波涛,脊形多重分形,振幅阻尼,阶梯形,voronoi噪声,分析导数,域扭曲等。 您可以使用
此页面作为灵感来源。 我在这里不考虑它们;我的文章集中在简单性上。
该项目受到我以前的地图生成项目的影响:
- 我将Perlin的整体噪音用于“狂神”卡生成器的第一个领域 。 我们在前六个月的alpha测试中使用了它,然后将其替换为Voronoi多边形上的地图生成器 , 该生成器是专门为在alpha测试期间确定的游戏要求而创建的。 生物群落及其文章的颜色均来自这些项目。
- 在研究音频信号的处理时,我写了一个噪声教程 ,解释了诸如频率,幅度,八度和噪声的“颜色”等概念。 适用于声音的相同概念也适用于基于噪声的卡生成。 当时,我创建了一些原始的演示浮雕生成器 ,但是我没有完成它们。
- 有时我会尝试寻找界限。 我想知道创建引人注目的地图最少需要多少代码。 在这个小型项目中,我达到了零行代码-一切都由图像滤镜完成(湍流,阈值,颜色渐变)。 这让我感到高兴和悲伤。 图像过滤器可以在多大程度上执行地图生成? 在足够大。 以上关于“平滑颜色渐变方案”的所有描述均来自此实验。 噪声层是湍流图像滤波器。 八度音阶是彼此叠加的图像; 学位工具在Photoshop中称为“曲线校正”。
让我感到困扰的是,游戏开发人员为基于噪声的地形生成(包括中点位移)编写的大多数代码都与声音和图像过滤器中的相同。 另一方面,它仅用几行代码即可产生相当不错的结果,这就是我撰写本文的原因。 这是一个
快速简便的参考点 。 通常,我不会长时间使用此类卡,但是一旦发现哪种类型的卡更适合游戏设计,就立即用更复杂的地图生成器替换它们。 这是我的标准模式:从非常简单的内容开始,然后在我更好地了解要使用的系统之后才进行替换。
在噪声中,还有
很多事情可以做,在本文中我只提到了一些。 尝试
Noise Studio交互式测试各种功能。