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

时间:2010-03-25 23:18:38

标签: algorithm map terrain

我正在开发类似文明的游戏,我正在寻找一种用于生成类似地球的世界地图的好算法。我已经尝试了一些替代方案,但尚未找到真正的赢家。

一种选择是使用Perlin noise生成高度图,并在一个级别添加水,以便世界上大约30%的土地。虽然Perlin噪声(或类似的基于分形的技术)经常用于地形并且相当逼真,但它并没有提供太多控制结果的数量,大小和位置,我想从游戏角度来看。

Perlin noise

第二种选择是从随机定位的单瓦种子开始(我正在处理瓷砖网格),确定大陆的所需大小,每个回合添加一个水平或垂直相邻的瓷砖现有大陆,直到达到所需的大小。重复其他大陆。这种技术是文明4中使用的算法的一部分。问题在于,在放置了前几个大陆之后,可以选择一个被其他大陆包围的起始位置,因此不适合新的大陆。此外,它有一种将大陆太靠近的趋势,导致看起来更像河流而不是大陆。

Random expansion

是否有人碰巧知道在基于网格的地图上生成逼真大陆的良好算法,同时保持对其数量和相对大小的控制?

12 个答案:

答案 0 :(得分:37)

您可以从nature获取提示并修改您的第二个想法。一旦你生成了你的大陆(大小都相同),让他们随机移动和旋转,相互碰撞和变形,彼此分开。 (注意:这可能不是最容易实现的。)

编辑:以下是另一种方法,完成了实施 - Polygonal Map Generation for Games

答案 1 :(得分:11)

我建议你备份

  1. 想想是什么让“好”的大陆。
  2. 编写一个算法,可以从糟糕的大陆布局中分辨出良好的大陆布局。
  3. 优化算法,以便量化良好布局的效果。
  4. 一旦你有了这个,你就可以开始实现一个应该这样形成的算法:

    • 生成蹩脚的大陆,然后改善它们。

    为了改进,您可以尝试各种标准优化技巧,无论是模拟退火,遗传编程,还是完全 ad hoc ,例如移动随机选择的边缘方块,而不是它在大陆上到大陆质量中心对面的边缘。但关键是能够编写一个程序,它可以告诉好大陆不好的大陆。从手绘的大陆和测试大陆开始,直到你得到你喜欢的东西。

答案 2 :(得分:10)

我写了类似于你所追求的文明1的自动屏幕保护程式克隆的东西。为了记录我在VB.net中写了这个但是因为你在你的问题中没有提到关于语言或平台的任何内容我将保持抽象。

“地图”指定了大陆的数量,大陆的大小差异(例如,1.0将使所有大陆保持相同的近似土地面积,低至0.1将允许大陆以最大大陆的质量的1/10存在),产生的最大土地面积(以百分比表示)和中央土地偏向。 “种子”在每个大陆的地图周围随机分布,按照中心偏差向地图中心加权(例如,低偏差产生的分布大陆与地球更相似,其中高中心偏差将更像是盘古)。然后,对于每次增长迭代,“种子”根据分布算法(稍后更多)分配地块,直到达到最大陆地面积。

土地分配算法可以尽可能精确,但我发现了应用各种遗传算法和滚动骰子的更有趣的结果。康威的“生命游戏”是一个非常简单的开始。你需要添加一些全局意识的逻辑,以避免像大陆相互影响的事情,但大多数事情都要照顾好自己。我发现更多基于分形的方法(这是我的第一个倾向)的问题是结果看起来太过模式化,或导致太多场景需要hacky-feeling workaround规则来获得仍然感觉不够动态的结果。根据您使用的算法,您可能希望对结果应用“模糊”传递,以消除诸如丰富的单方形海洋瓷砖和方格海岸线之类的东西。如果像大陆一样被其他几个人包围并且无处可生的大陆,将种子重新定位到地图上的新点并继续增长。是的,它可能意味着你有时会比计划更多的大陆,但如果它真的是你坚定不想要的东西,那么另一种方法来帮助避免它是偏向增长算法,所以他们倾向于增长的方向与最接近其他种子。在最坏的情况下(我认为无论如何),当种子无处生长并生成新地图时,您可以将系列标记为无效。只要确保设置了最大尝试次数,如果指定了任何不切实际的东西(比如在10x10板上安装50个均匀加权的大陆),它就不会花费太多时间来寻找有效的解决方案。

我无法保证Civ等如何做到这一点,当然也没有涵盖气候,土地年龄等问题,但通过使用种子增长算法,您可以获得类似于大陆,群岛等的非常有趣的结果你也可以用同样的方法来生产“有机”的河流,山脉等。

答案 3 :(得分:10)

我在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>

答案 4 :(得分:8)

Polygonal map generation文章描述了一步一步地图生成unsing Voronoi多边形。

这家伙也提供所有源代码。它是Flash(ActionScript 3 / ECMAScript),但可以转换为任何其他面向对象的语言

或尝试使用某些分形环境软件中实施的算法,例如TerraJ

答案 5 :(得分:5)

在这里想一下袖口:

选择一些起点,并为每个起点分配一个随机绘制的(希望的)大小。如果需要,您可以为计划的大陆和计划的岛屿维护单独的大小绘制。

环绕陆地元素,并且它们尚未达到计划大小的位置添加一个方格。但有趣的部分是权衡每个相邻元素的概率。一些可能因素的建议事项:

  1. 距离最近的“其他”土地的距离。进一步更好地产生广阔的海洋空间。更接近更好地制造狭窄的渠道。你必须决定是否要让比特合并。
  2. 与种子的距离。越近越好意味着紧凑的土地质量,越远越好意味着长长的位置
  3. 毗邻的现有土地广场数量。对许多相邻的广场进行加权可以为您提供平滑的海岸线,而且很少有人能够为您提供大量的入口和半岛。
  4. 附近是否有“资源”广场?取决于游戏规则,当你生成资源平方时,如果你想让它变得容易。
  5. 你会允许比特接近或加入两极吗?
  6. ???不知道还有什么
  7. 继续直到所有土地群已达到规划的大小或由于某种原因不能再增长。

    请注意,将参数赋予这些加权因子可以调整生成的世界类型,这是我喜欢的一些Civs的功能。

    这样你就需要分别对每个位进行地形生成。

答案 6 :(得分:3)

我有一个类似于构造板块答案的地图创建的想法。它是这样的:

  1. 扫过网格方格,如果rnd <= 0.292(地球上干旱地的实际百分比),每个方格都有一个“土地”方格。
  2. 将每个地块迁移到最近的较大邻居。如果邻居是等距的,那么去更大的块。如果块大小相等,则随机选择一个。
  3. 如果两个陆地方块接触,将它们组合成一个块,从现在起将所有方块移动为一个。
  4. 从步骤2开始重复。连接所有块时停止。
  5. 这类似于重力在3D空间中的工作方式。这很复杂。一个更简单的算法可以满足您的需求:

    1. 以随机的x,y位置和彼此可接受的距离落入n个起始地面。这些是你的大陆的种子。 (使用毕达哥拉斯定理确保种子与其他种子之间的距离最小。)
    2. 如果那个方向是海洋广场,
    3. 从一个现有的土地广场以随机方向产生一块土地广场。
    4. 重复步骤2.当土地正方形占地图总面积的30%时停止。
    5. 如果大陆彼此足够接近,可根据需要掉落陆桥以模拟巴拿马型效应。
    6. 根据需要放入较小的随机岛屿,以获得更自然的外观。
    7. 对于您添加的每个额外“岛屿”广场,使用相同的算法反向切割来自各大陆的内陆海域和湖泊广场。这将使土地百分比保持在所需的数量。
    8. 让我知道这是如何解决的。我自己从未尝试过。

      PS。我看到这与你的尝试类似。除了它在开始之前立刻设置所有种子,所以大陆将相距足够远,并且当地图充分填充时将停止。

答案 7 :(得分:3)

您可以尝试钻石方形算法或perlin噪声来生成类似高度图的内容。然后,将范围值分配给地图上显示的值。如果你的“身高”从0到100,那么制作0 - 20水,20 - 30海滩,30 - 80草,80 - 100山。我认为缺口在minicraft中做了类似的事情,但我不是专家,我只是在最终使它工作后才处于钻石方形的心态。

答案 8 :(得分:2)

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

  

首先解决小问题并结合起来   巧妙地解决更大的解决方案   问题

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" ;)

看一些“图形布局算法”

会非常好

您可以根据自己的目的修改这些内容。

答案 9 :(得分:2)

这就是我在思考的问题,因为我正在实施类似于开发游戏的东西。 :

世界分为几个区域。根据世界的大小,它将决定多少个地区。在这个例子中,我们假设一个中等大小的世界,有6个区域。每个网格区域分为9个网格区域。那些网格区域分成9个网格。 (这不是用于角色移动,而是仅用于地图创建)网格用于生物群系,网格区域用于过度拱形的土地特征(大陆与海洋),区域用于整体气候。网格分解成瓷砖。

随机生成,区域被分配逻辑气候集。例如,网格区域被随机分配给;海洋或陆地。网格根据其网格区域和气候随机分配生物群,其中包括森林,沙漠,平原,冰川,沼泽或火山。一旦分配了所有这些基础知识,就可以使用填充图块集的基于随机百分比的函数将它们混合在一起。例如;如果你有一个森林生物群系,在沙漠生物群系旁边,你有一个算法可以减少瓷砖的可能性。&#34; foresty&#34;并且增加了它将会变成沙漠。&#34;因此,在他们之间的大约一半时间,你会看到一种混合的影响,将两个生物群系结合起来,使它们之间的平滑过渡。从一个网格区域到下一个网格区域的过渡可能需要更多的工作来确保逻辑大陆的形成。例如,像一个网格区域中的生物群系接触来自另一个的生物群系,而不是基于接近度的简单切换百分比。例如,从生物群系的中心到生物群系的边缘有50个瓦片,这意味着从边缘有50个瓦片接触到下一个生物群系的中心。从逻辑上讲,这将从一个生物群落到下一个生物群落发生100%的变化。因此,当瓷砖越来越靠近两个生物群系的边界时,百分比会缩小到约60%左右。我认为,过于远离边界的生物群落的可能性太大是不明智的,但是你希望边界有点混合。对于网格区域,百分比变化将更加明显。它不会降低到60%左右,而只会下降到80%左右。然后必须进行二次检查以确保在海洋旁边的陆地生物群系中间没有随机水瓦片而没有一些逻辑。因此,要么将水瓦连接到海洋块以形成通道来解释水瓦,或者完全将其移除。使用岩石露头等更容易解释水基生物群落中的土地。

噢,有点傻,对不起。

答案 10 :(得分:1)

我根据你知道“工作”的一些布局(例如2x2网格,钻石等,有一些抖动)放置分形地形,但是高斯分布阻尼峰向下朝向大陆中心的边缘。将水位降低,使其大部分落地,直至靠近边缘。

答案 11 :(得分:1)

我实际上没有尝试过,但它的灵感来自David Johnstone关于构造板块的答案。我尝试在我的旧文明项目中实现它,当涉及到处理碰撞时,我有了另一个想法。每个大陆都由节点组成,而不是直接生成瓦片。为每个节点分配质量,然后使用2D元球方法生成一系列“blob”大陆。只需移动节点,构造和大陆漂移就会非常容易“伪造”。根据您想要的复杂程度,您甚至可以应用电流来处理节点移动并生成与板块边界重叠相对应的山脉。可能不会在游戏玩法方面增加太多东西,但它可以从纯粹的学术角度制作一个有趣的地图生成:)

如果您之前没有与他们合作过,那么对元球的一个很好的解释是:

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