寻找一个好的世界地图生成算法

100

我正在开发一个类似文明游戏的项目,需要一种好的算法来生成类似地球的世界地图。我已经尝试过几种方案,但都没有找到真正成功的。

其中一种选择是使用Perlin噪声生成高度图,并在某个水平面上添加水,以使大约30%的世界为陆地。虽然Perlin噪声(或类似的分形技术)通常用于地形,而且相当逼真,但它不能很好地控制结果大陆的数量、大小和位置,而这些信息对游戏玩法至关重要。

Perlin noise

第二种选择是从随机位置的单个瓷砖开始(我正在使用网格瓦片),确定大陆的期望大小,每次添加一个与现有大陆水平或垂直相邻的瓷砖,直到达到所需大小。然后为其他大陆重复此过程。这种技术是《文明4》中使用的算法的一部分。问题在于,在放置了前几个大陆之后,可能会选择被其他大陆包围的起始位置,从而无法适应新大陆。此外,它倾向于产生距离过近的大陆,看起来更像是河流而不是大陆。

Random expansion

有没有人知道一种好的算法,可以在基于网格的地图上生成逼真的大陆,并控制它们的数量和相对大小?

11个回答

39
你可以从自然中汲取灵感,修改你的第二个想法。一旦生成了大陆(它们的大小都差不多),让它们随机移动、旋转、碰撞、变形和相互漂移。 (注意:这可能不是最容易实现的事情。)

编辑:还有一种方法,完整的实现方式在游戏中的多边形地图生成中。


2
这是一个很棒的想法。我不确定要尝试直接模拟构造板块,但只要每个大陆“拥有”自己的土地瓦片(而不仅仅是作为地图数组的生成器),并且本质上坐在或作为自己的板块,实现起来并不难。我现在要尝试一下 :) - nathanchere
3
一种廉价的技巧是定义一个数学函数来描述“好”地图的特征,然后进行10次随机的微小更改,并选出其中最佳的。不断重复这个过程,地图会逐渐接近你想要的类型。 - dascandy
你可以使用修改后的K-means聚类算法来确定每块土地属于哪个“大陆”。然后使用Voronoi图(一旦定义了聚类,这应该很容易)来确定板块边界...对于Voronoi中的每条线段,您将确定一个随机运动向量,并且应该能够确定地震区与火山区等。然后,每个土地点将根据其相对于板块边界的位置而具有一个单独的向量,并且应该得到一个相当真实的模拟结果。 - Steve

11

我用JavaScript创建了与你第一张图片类似的东西。它不是非常复杂,但它可以工作:

http://jsfiddle.net/AyexeM/zMZ9y/

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<style type="text/css">
    #stage{
        font-family: Courier New, monospace;
    }
    span{
        display: none;
    }
    .tile{
        float:left;
        height:10px;
        width:10px;
    }
    .water{
        background-color: #55F;
    }
    .earth{
        background-color: #273;
    }
</style>
</head>

<body>


<div id="stage">

</div>

<script type="text/javascript">

var tileArray = new Array();
var probabilityModifier = 0;
var mapWidth=135;
var mapheight=65;
var tileSize=10;

var landMassAmount=2; // scale of 1 to 5
var landMassSize=3; // scale of 1 to 5


$('#stage').css('width',(mapWidth*tileSize)+'px');


for (var i = 0; i < mapWidth*mapheight; i++) {

    var probability = 0;
    var probabilityModifier = 0;

    if (i<(mapWidth*2)||i%mapWidth<2||i%mapWidth>(mapWidth-3)||i>(mapWidth*mapheight)-((mapWidth*2)+1)){

        // make the edges of the map water
        probability=0;
    }
    else {

        probability = 15 + landMassAmount;

        if (i>(mapWidth*2)+2){

            // Conform the tile upwards and to the left to its surroundings 
            var conformity =
                (tileArray[i-mapWidth-1]==(tileArray[i-(mapWidth*2)-1]))+
                (tileArray[i-mapWidth-1]==(tileArray[i-mapWidth]))+
                (tileArray[i-mapWidth-1]==(tileArray[i-1]))+
                (tileArray[i-mapWidth-1]==(tileArray[i-mapWidth-2]));

            if (conformity<2)
            {
                tileArray[i-mapWidth-1]=!tileArray[i-mapWidth-1];
            }
        }

        // get the probability of what type of tile this would be based on its surroundings 
        probabilityModifier = (tileArray[i-1]+tileArray[i-mapWidth]+tileArray[i-mapWidth+1])*(19+(landMassSize*1.4));
    }

    rndm=(Math.random()*101);
    tileArray[i]=(rndm<(probability+probabilityModifier));

}

for (var i = 0; i < tileArray.length; i++) {
    if (tileArray[i]){
        $('#stage').append('<div class="tile earth '+i+'"> </div>');
    }
    else{
        $('#stage').append('<div class="tile water '+i+'"> </div>');
    }
}

</script>

</body>
</html>

11

我曾为文明1的自动屏幕保护程序编写过类似的内容。记录一下,我是用VB.net写的,但由于你在问题中没有提到语言或平台,所以我会保持抽象。

"地图"指定了大陆数量、大陆面积变化(例如,1.0将保留所有大陆的近似土地面积相同,而0.1将允许存在质量为最大大陆的1/10的大陆),要生成的最大陆地面积(以百分比表示)和中心地区的偏向性。每个大陆随机分布在地图上的“种子”,根据中央偏向性加权分布(例如,低偏向性产生更接近地球的分散大陆,而高中心偏向性则类似于Pangaea)。然后,在每次增长迭代中,“种子”根据分配算法(稍后详述)分配土地瓦片,直到达到最大陆地面积。

土地分配算法可以非常精确,但我发现应用各种遗传算法和随机因素会得到更有趣的结果。康威的“生命游戏”是一个很容易上手的例子。你需要添加一些全局意识的逻辑来避免大洲之间相互生长,但大部分情况下事情会自行解决。我发现使用更基于分形的方法(这是我的第一倾向)的问题在于,结果要么看起来太有规律,要么会导致太多需要hacky感觉的绕路规则的情况,才能得到仍然不够动态的结果。根据所使用的算法,您可能希望对结果应用“模糊”处理,以消除过多的单方块海洋瓦片和棋盘式海岸线等问题。如果出现像大陆被几个其他大陆包围并且没有任何增长空间的情况,请将种子重新定位到地图上的新点并继续增长。是的,这可能意味着您有时会得到比计划更多的大陆,但如果这真的是您坚决不想要的东西,则另一个帮助避免它的方法是偏向于增长算法,使它们倾向于在与其他种子距离最远的方向上生长。在最坏的情况下(至少在我看来),当一个种子没有增长空间时,您可以标记系列为无效,并生成新的地图。只需要确保设置最大尝试次数,以便如果指定了任何不切实际的东西(比如将50个均匀分布的大陆放在10x10的板上),它不会花费很长时间来寻找有效的解决方案。 我不能担保Civ等游戏如何做到这一点,当然也没有涵盖气候、土地年龄等问题,但通过调整种子增长算法,可以得到类似于大陆、群岛等有趣的结果。您还可以使用相同的方法来产生“有机”的河流、山脉等外观。

11

我建议您回过头来,

  1. 思考哪些因素构成了“好”的大陆。
  2. 编写一个算法,可以从不好的大陆中找出好的大陆。
  3. 优化这个算法,以便能够量化出一个好布局的程度。

一旦有了以上准备,就可以开始实现下面这个形状的算法:

  • 生成糟糕的大陆并改进它们。

对于改进,您可以尝试各种标准的优化技巧,无论是模拟退火、遗传编程,还是一些完全特定领域的方法,例如将随机选择的边缘方块从大陆上的任何位置移动到质心相反的边缘。关键是要能够编写一个程序,可以从不好的大陆和好的大陆中区分出来。从手绘的大陆和测试大陆开始,直到得到自己喜欢的结果为止。


2
在这个背景下,这种说法没有什么意义。这就好像说,对于一个分形生成器来说,你应该先编写一个生成劣质分形的程序,然后再尝试编写一个判断你所拥有的分形是好还是坏的程序。从一开始就把它做好会更容易。否则,你会完全歪曲问题的重点和范围。 - nathanchere
1
强烈不同意。在图形方面,最好先将任何东西显示在屏幕上,这样你的右脑才能开始发挥作用。 - luser droog

5

我随意想了一下:

选择一些起点,为每个起点分配一个随机大小。如果需要的话,您可以为计划大陆和计划岛屿维护单独的大小设定。

循环遍历土地元素,并在它们尚未达到计划大小时添加一个正方形。但有趣的部分是权衡每个相邻元素成为选中元素的几率。以下是一些可能会影响因素:

  1. 最近的“其他”土地距离。距离越远,生成广阔的海洋空间就越好。距离越近,则会形成狭窄的通道。您必须决定是否允许碎片合并。
  2. 距离种子的距离。越近越好表示紧凑的陆地,越远则表示长而分散的陆地。
  3. 相邻现有土地正方形的数量。对许多相邻正方形进行加权使海岸线更平滑,而偏爱少数正方形则会产生许多小港口和半岛。
  4. 附近是否存在“资源”正方形?这取决于游戏规则以及生成资源正方形的时间和难易程度。
  5. 您是否允许碎片接近或连接极点?
  6. ??? 不知道还有什么其他的

继续进行,直到所有陆地达到计划大小或由于某些原因无法再生长为止。

请注意,调整这些加权因素的参数可以调整生成的世界类型,这是我喜欢一些文明游戏的功能之一。

这样,您将需要单独对每个部分进行地形生成。


4
您可以尝试使用钻石方算法或Perlin噪声生成类似于高度图的东西。然后,为出现在地图上的内容分配范围值。如果您的“高度”从0到100,则将0-20设置为水域,20-30设置为海滩,30-80设置为草地,80-100设置为山脉。我认为Notch在Minicraft中做了类似的事情,但我不是专家,只是在终于让它工作后沉迷于钻石方算法。

3

我认为你可以在这里使用“动态规划”风格的方法。

首先解决小问题,然后巧妙地组合解决方案以解决更大的问题。

A1= [elliptical rectangular random ... ]// list of continents with area A1 approx. 
A2= [elliptical rectangular random ... ]// list of continents with area A2 approx.
A3= [elliptical rectangular random ... ]// list of continents with area A3 approx.
...
An= [elliptical rectangular random ... ]// list of continents with area An approx.

// note that elliptical is approximately elliptical in shape and same for the other shapes.

Choose one/more randomly from each of the lists (An).

Now you have control over number and area of continents.

You can use genetic algorithm for positioning them 
as you see "fit" ;)

查看一些“图形布局算法”将非常有益。

您可以修改这些以适应您的目的。


3

我有一个制作地图的想法,类似于构造板块的答案。 大致如下:

  1. 遍历网格方格,如果rnd <= 0.292(地球上实际干陆的百分比),则给每个方格一个“陆地”方格。
  2. 将每个陆地块向其最近的较大邻居移动一个方格。 如果邻居等距离,则向较大的块移动。 如果块大小相等,则随机选择一个。
  3. 如果两个陆地方格相接触,则将它们分组成一个块,从现在开始将所有方格一起移动。
  4. 从步骤2重复。当所有块都连接时停止。

这类似于重力在三维空间中的工作方式。 这很复杂。 对于您的需求,更简单的算法应如下:

  1. 在随机的x,y位置和彼此可接受的距离处放置n个起始陆地方块。 这些是大陆的种子。(使用勾股定理确保种子之间及与其他方块之间有最小距离。)
  2. 如果该方向为海洋方格,则从现有陆地方格中向随机方向生成一个陆地方格。
  3. 重复步骤2。当陆地方格填满总地图大小的30%时停止。
  4. 如果大陆彼此足够接近,请根据需要放置陆地桥梁以模拟巴拿马效应。
  5. 按需要放置较小的随机岛屿,以获得更自然的外观。
  6. 对于您添加的每个额外的“岛屿”方格,使用相同的算法反向削减内陆海和湖泊方格。 这将保持所需数量的陆地百分比。

让我知道这个想法的效果如何。 我自己从未尝试过。

附:我看到这与您尝试的类似。 除了在开始之前一次设置所有种子,因此大陆之间将足够远,并且在地图填充足够时停止。


2
我实际上还没有尝试过这个方法,但是它受到了David Johnstone关于构造板块的回答的启发。我在我的旧文明项目中尝试了自己实现它,当涉及到处理碰撞时,我有另一个想法。而不是直接生成图块,每个大陆都由节点组成。将质量分配给每个节点,然后使用2D metaball方法生成一系列“blob”大陆。通过移动节点,构造板块和大陆漂移将非常容易地“伪造”。根据您想要的复杂程度,甚至可以应用像流之类的东西来处理节点运动,并生成与板块边界重叠的山脉。这可能对游戏玩法方面并没有太大的贡献,但从纯学术角度来看,它可能会产生有趣的地图生成结果:)
如果您以前没有使用过metaballs,以下是一个很好的解释:

http://www.gamedev.net/page/resources/_//feature/fprogramming/exploring-metaballs-and-isosurfaces-in-2d-r2556


2
这是我的想法,因为我即将实现一个类似于正在开发中的游戏的东西:
世界分为不同的区域。根据世界的大小,将决定有多少个区域。例如,我们假设一个中等大小的世界,有6个区域。每个网格区域又分为9个网格区域。这些网格区域又分为9个网格。 (这不是用于角色移动,而仅用于地图创建) 网格用于生物群系,网格区域用于总体地形特征(大陆与海洋),而区域则用于整体气候。网格分解为瓷砖。
随机生成后,逻辑气候集将被分配给区域。例如,网格区域会随机分配到海洋或陆地。网格将根据其网格区域和气候随机分配生物群系,并具有修饰符,包括森林、沙漠、平原、冰川、沼泽或火山。一旦分配完所有基本信息,就可以使用基于随机百分比的函数将它们混合在一起,以填充瓷砖集。例如,如果您有一个森林生物群系,紧挨着一个沙漠生物群系,您将拥有一个算法,它会减少瓷砖“森林化”的可能性,并增加其“沙漠化”的可能性。因此,在它们之间的中间位置,您将看到一种混合效果,将两个生物群系结合在一起,以实现它们之间的平滑过渡。从一个网格区域到另一个网格区域的过渡可能需要更多的工作,以确保逻辑地形形成。例如,一个网格区域的生物群系接触另一个网格区域的生物群系,而不是根据接近程度简单地切换百分比。例如,从生物群系中心到边缘有50个瓷砖,这意味着从接触的边缘到下一个生物群系的中心有50个瓷砖。这在逻辑上将完全改变一个生物群系到另一个生物群系的概率。因此,当瓷砖越来越靠近两个生物群系的边界时,百分比会缩小到约60%左右。我认为,在远离边界的交叉生物群系的情况下,给予太高的概率是不明智的,但您会希望边界有一定的混合。对于网格区域,百分比变化将更加明显。百分比不会降至约60%,而只会降至约80%。然后必须进行第二次检查,以确保在陆地生物群系旁边的海洋中没有随机水瓷砖而没有逻辑。因此,要么将该水瓷砖连接到海洋质量中以形成通道以解释该水瓷砖,要么完全删除它。在水基生物群系中的陆地更容易解释,例如使用岩石露头等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接