在《创建程序化的游戏世界(1)》中,我们介绍了使用多种方法来程序化创建顶部图层,例如:使用柏林噪音和随机游走。在这篇文章,我们将介绍使用程序化生成功能创建洞穴的方法,从而给予开发者一些变化的可能性启发。

本文探讨的内容在ProceduralPatterns2D项目内实现。你可以下载这些资源并尝试使用程序化算法。
下载地址:https://github.com/UnityTechnologies/ProceduralPatterns2D

122717xw7ummxwz6t7lctu.png

现在继续创建地图的剩余部分。
[C#] 纯文本查看 复制代码 for (int y = 1; y < map.GetUpperBound(1); y++) { if (rand.Next(0, 100) > roughness) { int widthChange = Random.Range(-maxPathWidth, maxPathWidth); tunnelWidth += widthChange; if (tunnelWidth < minPathWidth) { tunnelWidth = minPathWidth; } if (tunnelWidth > maxPathWidth) { tunnelWidth = maxPathWidth; } } if (rand.Next(0, 100) > curvyness) { int xChange = Random.Range(-maxPathChange, maxPathChange); x += xChange; if (x < maxPathWidth) { x = maxPathWidth; } if (x > (map.GetUpperBound(0) - maxPathWidth)) { x = map.GetUpperBound(0) - maxPathWidth; } } for (int i = -tunnelWidth; i <= tunnelWidth; i++) { map[x + i, y] = 0; } } return map; }

接下来,生成一个随机数来检查我们的粗糙度数值,如果这个随机数大于该数值,我们可以修改路径的宽度。我们也会检查宽度是否太小。有了下一部分代码,我们将会继续处理地图并制作通道。


下面的每一步,我们都会做这些事情:

生成一个新随机数,用来检查曲线值。就如之前的检查一样,如果随机数大于这个数值,我们可以修改路径的中心点。我们也会通过检查确保不会离开地图边界。 最后我们会在创建的新区域创造通道。


最终实现结果如下:
122731wjlqb5b7lkhk5bll.png

邻域的规则如下:
为邻域检查每个方向。 如果一个邻域是个活跃瓦片,为其周围增加一个瓦片。 如果一个邻域并非活跃瓦片,则什么也不做。 如果这个元胞有四个以上的邻近瓦片,将这个元胞转变为活跃瓦片。 如果这个元胞正好有四个邻近瓦片,就不管这个瓦片。 重复以上步骤,直到完成检查地图上的每个瓦片。


检查摩尔邻域的函数代码如下:
[C#] 纯文本查看 复制代码static int GetMooreSurroundingTiles(int[,] map, int x, int y, bool edgesAreWalls) { int tileCount = 0; for(int neighbourX = x - 1; neighbourX <= x + 1; neighbourX++) { for(int neighbourY = y - 1; neighbourY <= y + 1; neighbourY++) { if (neighbourX >= 0 neighbourX < map.GetUpperBound(0) neighbourY >= 0 neighbourY < map.GetUpperBound(1)) { if(neighbourX != x neighbourY != y) { tileCount += map[neighbourX, neighbourY]; } } } } return tileCount; }

在检查完瓦片之后,我们会将得到的信息用在平滑函数中。就如同初始元胞自动机生成过程一样,我们可以设置地图的边界是否有墙。
[C#] 纯文本查看 复制代码public static int[,] SmoothMooreCellularAutomata(int[,] map, bool edgesAreWalls, int smoothCount) { for (int i = 0; i < smoothCount; i++) { for (int x = 0; x < map.GetUpperBound(0); x++) { for (int y = 0; y < map.GetUpperBound(1); y++) { int surroundingTiles = GetMooreSurroundingTiles(map, x, y, edgesAreWalls); if (edgesAreWalls (x == 0 x == (map.GetUpperBound(0) - 1) y == 0 y == (map.GetUpperBound(1) - 1))) { map[x, y] = 1; } else if (surroundingTiles > 4) { map[x, y] = 1; } else if (surroundingTiles < 4) { map[x, y] = 0; } } } } return map; }

在这个函数中要注意,我们会在整个地图通过for循环以固定次数来进行平滑处理。这个循环结束后会给我们如下更好的地图:

122733q79i4284pukup966.png

这个邻域的规则如下:
检查直接相邻的瓦片,不包括斜线部分。 如果这个元胞是活跃的,给计数器加一。 如果这个元胞不是活跃的,什么都不做。 如果我们拥有超过二个邻域,使当前元胞变为活跃。 如果我们拥有的邻域少于二个,使当前元胞变得不活跃。 如果我们正好有二个邻域,不要修改当前元胞。


第二个结果和第一个结果的规则一样,但是会拓展邻域区域。

我们会通过以下函数检查邻域:
[C#] 纯文本查看 复制代码static int GetVNSurroundingTiles(int[,] map, int x, int y, bool edgesAreWalls) { int tileCount = 0; if(edgesAreWalls (x - 1 == 0 x + 1 == map.GetUpperBound(0) y - 1 == 0 y + 1 == map.GetUpperBound(1))) { tileCount++; } if(x - 1 > 0) { tileCount += map[x - 1, y]; } if(y - 1 > 0) { tileCount += map[x, y - 1]; } if(x + 1 < map.GetUpperBound(0)) { tileCount += map[x + 1, y]; } if(y + 1 < map.GetUpperBound(1)) { tileCount += map[x, y + 1]; } return tileCount; }

在得到已有邻域数量后,我们可以移动到数组的平滑部分。和之前一样,我们有一个for循环来在指定输入数量的平滑计数迭代。
[C#] 纯文本查看 复制代码public static int[,] SmoothVNCellularAutomata(int[,] map, bool edgesAreWalls, int smoothCount) { for (int i = 0; i < smoothCount; i++) { for (int x = 0; x < map.GetUpperBound(0); x++) { for (int y = 0; y < map.GetUpperBound(1); y++) { int surroundingTiles = GetVNSurroundingTiles(map, x, y, edgesAreWalls); if (edgesAreWalls (x == 0 x == map.GetUpperBound(0) - 1 y == 0 y == map.GetUpperBound(1))) { map[x, y] = 1; } else if (surroundingTiles > 2) { map[x, y] = 1; } else if (surroundingTiles < 2) { map[x, y] = 0; } } } } return map; }


这样得到的结果比摩尔邻域方法得到的更为分散,如下图所示:
122734uqwtka8izjaniilp.gif

和摩尔领域一样,我们可以基于生成结果运行一个额外脚本,从而为地图各领域之间提供更好的连接。

小结
希望这篇文章能启发开发者在项目中开始使用一些程序化生成形式。如果你想要了解更多关于程序化生成地图的信息,请查看Roguebasin.com。更多Unity技术内容请访问Unity官方中文论坛(UnityChina.cn)
unity, 地图, 程序化, 自动 本主题由 admin 于 2018-7-10 03:37 解除置顶锐亚教育

锐亚教育 锐亚科技 unity unity教程