一直很想做一个模拟现实世界的游戏,最近开始动手了!既然是一个世界那么地图自然是第一个要解决的问题,网上有很多生成地图的教程,但找不到一个完整的文章。经过几天的研究总结了一些问题。
第一步:散列函数
想在计算机中生成随机数并不容易,而且完全随机的数字对生成地图并没有什么帮助,最开始我想过使用无理数的小数部分来充当随机数列,但计算无理数是一个非常耗时的过程。拜读多位博主的博文后我找到一个不错的散列函数,其核心思想是使用移位、精度溢出来产生散列值。如下(java/c++通用)
int hash(int key) {
key = ~key + (key << 15);
key = key^(key >>> 12);
key = key + (key << 2);
key = key^(key >>> 4);
key = key * 2057;
key = key^(key >>> 16);
return key;
}
然而生成高程时有x,y两个参数,当需要更多数据时需要更多参数,这时可以使用hash(x+hash(y))、hash(seed+hash(x+hash(y))这样的嵌套方式来获得散列值。
第二步:生成基础地形
网上对于地形生成搜索出来的全是关于value噪声、柏林噪声来获得高程图,效果如下
(下面的图绿色为高度0.5以上的地块高度越高颜色越浅,蓝色为高度0.5以下的地块越低颜色越深)
虽然有了高低错落、零零散散的自然美感,但这显然无法称之为地图。
我需要使用一些其他的方法来使它在更大尺度上具有一定的结构,比如在图上选一个点,以此点为中心,在一定范围内距离越远高度越低,一言以蔽之就是画一个圆。如下(黄色点为控制点的中心位置)我们使用第二张图片的高程为基础高程(要求:最大值为1权重1/2)第一张图片为辅助高程(要求:最大值为1权重1/3),剩下1/6的权重使用连续度较差的柏林噪声,重新生成图片:
控制点权重1/2的原因是该处必须保证生成陆地,由于控制点中心的高程为1,权重是1/2,得到的高度至少是海面高度0.5,即使其他高程全部为0也可以保证陆地在控制点处生成。当然也可以增加噪声层数,只要保证权重合理,高程映射到[0,1]之间即可。
现在我们添加多个控制点
这些圆的中心坐标是随机生成的,但位置被限制在x∈[width/4,3*width/4],y∈[height/4,3*height/4],防止控制点靠近边缘,生成后使用lloyd算法松弛一次,避免控制点拥挤在一起。
注意这些圆有大有小,这表示他们的控制范围,也是通过种子随机生成的。
以上所有的图像均使用相同的种子生成
再看看其他种子的图像
地图有了下一步需要分析地图中的图块,比如随机找一个陆地位置,寻找一个岛屿由于地图中的每个像素都是一个方块,我们可以从左到右从下到上逐行扫描地图,将同一类型的格子合并到一个多边形中,如图
每个格子初始有5个点,从左下角开始逆时针一圈回到左下角得到一个格子的轮廓,这个轮廓使用双向链表保存,便于后面搜索和修改。
扫描时使用一个数组保存扫描线上每个格子右上角顶点的引用(称a数组),使用另一个数组保存每个顶点所属的区域(称b数组),只要当前格子扫描完成便将对应的信息填入对应数组
当扫描到B时从a数组中取得A右上角的顶点,向前回溯一个找到A的右下角,新建两个顶点并连接到此处,完成操作
当扫描到C时通过a数组获取A右上角顶点的引用,新建两个顶点并连接到这个节点上,完成操作
扫描到D时,通过b数组发现左侧下方和左下均是本区域的节点,直接修改B左上角节点的坐标到D右上角坐标,完成操作
扫描到E时发现左和下不是相同区域所以它当前是独立的区域,但到达F时发现左与下是相同类型区域,需要合并。1、从a数组中拿到E的右上角顶点,向前回溯一个找到E的右下角,再通过a数组找到H右上角,向后移动一个顶点得到H的左上角。2、E从右下角拆开,H从左上角拆开,重新连接(不太容易描述,H与E均是逆时针连接,将边界顶点正确连接即可)。3、将E所属区域的多边形终点删除,之后将其尾节点连接到首节点之后,由于已经从E右下角拆开并连接到H的左上角,现在E原先所属区域的边界已经与H所属区域合并合。4、删除E所属的区域信息,保留H所属区域。5、替换B数组中所有E数组所属区域为本区域。6、由于左和下均是本区域,所以按照D的添加方式添加H点,完成操作
扫描到K时发现左和下均是本区域,但左下角的L(需要单独一个变量保存左下角所属区域)不属于本区域,此时判断本区域内部出现孔洞,1、使用类似E的方式连接J和M。2、新建一个链表将M左上角作为起点J右下角作为终点保存在本区域的内部孔洞列表中。3、新建一个顶点坐标为K的右上角。4、将此顶点连接到M的右上角之后,完成操作。
这样造成内部孔洞的顺序是顺时针存储的。如果需要保持与外轮廓相同的旋转方向需要额外操作。
通过上面的算法我们得到各个区域的外轮廓和内部孔洞。以便于更多操作。