程序化随机多边形地图生成

前言

最近跟团队想要开发一个开放世界的游戏,这是很有趣的游戏概念,然而参考了《塞尔达传说 荒野之息》的设定后发现,这个游戏的成功很大程度是美工和设计大量的工作,才形成了这个很有趣的大陆,然而我们的团队没有办法手工搭建出这么大的场景,于是我萌生了一个很自然的想法:写一个自动生成还算合理的地形的脚本。

起初我尝试了Perlin噪声直接生成高度图的做法,然而产生的地形太过极端,脚本的开发也进入了僵局,直到我看到了一篇2010年的文章:
原文地址:
http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation

这篇文章是在flash上实现了一个自动随机多边形地图的demo,我跟着这篇文章的思路,基本上复制了一个unity3D的版本,以后可能会再写出一个unreal的版本。

源码github地址

效果图

demo

基础多边形细胞图

既然要产生一个多边形为基础的地图,那么第一步当然是产生一个由多边形填满的二维图,术语上这种图形叫做维诺图(Voronoi Diagram),我更喜欢叫他细胞图。

Voronoi

这种图形的生成原理网上有很多,我参考了这篇文章:

http://blog.csdn.net/k346k346/article/details/52244123

需要注意的是,采用这种方法生成出的图形太过随机,我们需要让他看起来齐整一些,这采用了叫做Lloyd的放松算法,这是一种聚类算法,简单来讲就是经过数次迭代,每次迭代将维诺图的中心点移动到所在多边形的中心,重新计算维诺图。实际实验后发现2次迭代已经完全能达到效果。

数据结构

源代码中的VoronoiElement文件里保存了数据结构,大部分都具有注释,需要解释的Center类代表了维诺图的中心点,也就是Delaunay三角网的顶点,而Corner类代表三角形的外心,也就是图中多边形的交点。这样做的目的事实上产生了两张图的信息,三角图 和多边形图,后期开发时三角图可以用作寻路而多边形图主要用于渲染。

区分陆地和水

第二步我们要为我们的地图添加大陆和水的区别,自然界中的地表水的形成是有很多因素的,然而我们模拟时只能采用随机模拟。

原文中给出了很多模拟的思路,我才用了柏林噪声的方式进行模拟,首先为地图上每一个Corner点计算一个Perlin噪声值,设定一定的参数决定这个噪声值是否能使这个Corner具有水的属性。我才用的方式是比较(PerlinNoise)与(waterScale + waterScale * Distance(地图中心,该点))大小,这样既可以修改waterScale的参数值来修改生成地图的水量多少,同时也确保在地图边缘更容易出现水,让我们的地图看起来像是一个海上的大陆(我默认地图边缘都是水)。

在决定好Corner点的属性后,我们把与水Corner相接的Center(也就是多边形)记为水属性,可以通过Corner的touches链表获取到与其相接的Center。

区分水和大陆

区分海洋、内陆湖、海岸线

首先我们规定与地图边界接壤的多边形为海洋,所有与海洋接触的水均为海洋,与海洋接触的陆地是海岸线,其余的水为湖泊。

这里采用了一种种子填充的算法来计算。实际上这是一种广度优先搜素的策略,我们首先将地图边缘的Center添加到一个Queue队列中,接下来执行一个循环,直到队列中没有剩余元素。循环时每次取出队列的尾部Center,检查与该Center直接接触的临近多边形(这可以在neighbors链表中找到),若临近Center为水且不为海洋,代表这是一个还未处理过的Center,将他置为海洋并加入队列,若临近Center不为水,则置为Coast并加入队列。

在处理完全部的Center后,我们只需把海洋多边形的Corner置为海洋,海岸线多边形的Corner置为海岸线即可。

海岸线、海洋和湖泊

赋予高度

接下来我们为我们的地图添加高度的元素,让我们的地形变得崎岖。

为了地形随机但是合理,我们采用了这样一种策略:

靠近海岸线的地方更容易地势低,岛屿中心的地方更容易地势高,这样是很合理的。

实现方案同样是广度优先搜索,我们为每一个Corener定义了一个elevation的属性,代表距离海岸线的最短距离,每经过一个Corner,这个距离就加上一定的随机值(这个随机值范围越大地形会变得越崎岖)。

在搜索结束后,我们把每个Corner的elevation统一到0~1,作为这个点的高度,而每个Center的高度为多边形所有corner高度的平均值。

需要注意的是,我们在统一Corner高度后需要进行一个额外的操作,我们要计算每个Corner下降梯度最大的方向,这会为以后的河流生成做准备。

高度

河流

在上一步时我们已经计算好每个Corner的下降梯度最大的方向,这样我们的河流生成就简单了,我们只需要给出几个参数:河流的最小生成高度,河流的数量范围。然后我们随机给出河流的起点(确保大于最小高度,以及在陆地上),然后让河流按梯度向下流淌,流过的每个Corener置为河流,直到流入湖泊或海洋,或者经过一定路程

河流
  • 到这里我们的地图已经很好了,但是为了游戏性我们仍然为多边形添加两个特征

湿度和生物群落

我们仍然采用了和自然界相反的过程,我们通过多边形到水源的距离来决定多边形的湿度,计算的方案和高度计算基本一致。

湿度图

有了湿度和高度后我们就可以模拟地形上的植物类型了,这里采用了这样一张图表来进行模拟:

生物群落带
群落分布

读者可以自行修改数据让地形变得更干燥或者湿润

渲染

到目前为止我们都是用图形化调试的方式展示,我们还需要渲染出3D网格。

我采用了Unity自带的Mesh渲染的方式,为一个GameObject添加一个MeshRenderer和MeshFilter组件,修改mesh的顶点以及三角形属性,把所有的Center和Corner都作为顶点,对于每个多边形,让Center顶点和每一条边组成一个三角形网格(这里注意三角形要按顺时针排序顶点,确保法线方向正确),同时为mesh分13个submesh(代表13种群落),根据多边形的生物群落决定将三角形网格分配给哪个submesh(mesh.SetTriangles),为MeshRenderer分配13个材质,完成渲染。

demo

尾声

目前不同群落的材质我只用纯色替代,以后会精致制作每一个材质,实现类似低多边形渲染的效果。

且有很多噪声、融合的计算还没有做,如混合边界等。

文中步骤的图片都是一共500个多边形,而最后的效果图共有2000个多边形,从编译、启动、计算到渲染完成一共耗时20s左右,实际游戏运行中计算耗时会更低,且完全没有用到unity引擎的库所以可以放在多线程中完成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 前言 多边形偏移 (polygon offset) 算法可能我们印象不深,不过用过 autoCAD 的同学应该有印...
    zyl06阅读 10,453评论 17 14
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 等将来…… 等不忙…… 等下次…… 等有时间…… 等有条件…… 等有钱了…… 等来等去 …… 等没了缘分…… 等没...
    心羽暖姐姐阅读 114评论 3 0