随机游走算法简单图
by ahmad abdolsaheb
艾哈迈德·阿卜杜勒·塞哈卜
如何使用随机游走算法对自己的程序地牢图生成器进行编码 (How to code your own procedural dungeon map generator using the Random Walk Algorithm)
As technology evolves and game contents become more algorithmically generated, it’s not difficult to imagine the creation of a life-like simulation with unique experiences for each player.
随着技术的发展以及游戏内容的算法化程度越来越高,不难想象创建具有每个玩家独特体验的栩栩如生的模拟游戏。
Technological breakthroughs, patience, and refined skills will get us there, but the first step is to understand procedural content generation.
技术上的突破,耐心和精湛的技能将使我们到达那里,但第一步是了解过程内容的生成 。
Though many out-of-the-box solutions for map generation exist, this tutorial will teach you to make your own two-dimensional dungeon map generator from scratch using JavaScript.
尽管存在许多现成的地图生成解决方案,但本教程将教您使用JavaScript从头开始制作自己的二维地牢地图生成器。
There are many two-dimensional map types, and all have the following characteristics:
二维地图类型很多,都具有以下特征:
1. Accessible and inaccessible areas (tunnels and walls).
1.无障碍区域(隧道和墙壁)。
2. A connected route the player can navigate.
2.玩家可以导航的连接路线。
The algorithm in this tutorial comes from the Random Walk Algorithm, one of the simplest solutions for map generation.
本教程中的算法来自随机游走算法 ,它是地图生成的最简单解决方案之一。
After making a grid-like map of walls, this algorithm starts from a random place on the map. It keeps making tunnels and taking random turns to complete its desired number of tunnels.
绘制完网格状的墙图后,此算法从地图上的随机位置开始。 它不断制作隧道并随机转弯以完成所需数量的隧道。
To see a demo, open the CodePen project below, click on the map to create a new map, and change the following values:
要查看演示,请打开下面的CodePen项目,单击地图以创建新地图,然后更改以下值:
Dimensions: the width and height of the map.
尺寸:地图的宽度和高度。
MaxTunnels: the greatest number of turns the algorithm can take while making the map.
MaxTunnels:制作地图时算法可以转弯的最大次数。
MaxLength: the greatest length of each tunnel the algorithm will choose before making a horizontal or vertical turn.
MaxLength:在进行水平或垂直转弯之前,算法将选择的每个隧道的最大长度。
Note: the larger the maxTurn is compared to the dimensions, the denser the map will be. The larger the maxLength is compared to the dimensions, the more “tunnel-y” it will look.
注意: maxTurn与尺寸相比越大,地图将越密集。 与尺寸相比, maxLength越大,看起来越“隧道y”。
Next, let’s go through the map generation algorithm to see how it:
接下来,让我们看一下地图生成算法,看看它是如何进行的:
- Makes a two dimensional map of walls 制作墙壁的二维图
- Chooses a random starting point on the map 在地图上选择一个随机起点
- While the number of tunnels is not zero 虽然隧道数量不为零
- Chooses a random length from maximum allowed length 从最大允许长度中选择一个随机长度
- Chooses a random direction to turn to (right, left, up, down) 选择一个随机方向(右,左,上,下)
- Draws a tunnel in that direction while avoiding the edges of the map 沿该方向绘制隧道,同时避开地图的边缘
Decrements the number of tunnels and repeats the while loop
减少隧道数并重复while循环
- Returns the map with the changes 返回具有更改的地图
This loop continues until the number of tunnels is zero.
该循环一直持续到隧道数量为零为止。
代码中的算法 (The Algorithm in Code)
Since the map consists of tunnel and wall cells, we could describe it as zeros and ones in a two-dimensional array like the following:
由于地图由隧道和墙单元组成,因此我们可以将其描述为二维数组中的零和一,如下所示:
map = [[1,1,1,1,0], [1,0,0,0,0], [1,0,1,1,1], [1,0,0,0,1], [1,1,1,0,1]]
Since every cell is in a two-dimensional array, we can access its value by knowing its row and column such as map [row][column].
由于每个单元格都在二维数组中,因此我们可以通过了解其行和列(例如map [row] [column])来访问其值。
Before writing the algorithm, you need a helper function that takes a character and dimension as arguments and returns a two-dimensional array.
在编写算法之前,您需要一个辅助函数,该函数需要一个字符和一个维作为参数并返回一个二维数组。
createArray(num, dimensions) { var array = []; for (var i = 0; i < dimensions; i++) { array.push([]); for (var j = 0; j < dimensions; j++) { array[i].push(num); } } return array; }
To implement the Random Walk Algorithm, set the dimensions of the map (width and height), themaxTunnels
variable, and themaxLength
variable.
要实现随机游走算法,请设置地图的尺寸(宽度和高度), maxTunnels
变量和maxLength
变量。
createMap(){ let dimensions = 5, maxTunnels = 3, maxLength = 3;
Next, make a two-dimensional array using the predefined helper function (two dimensional array of ones).
接下来,使用预定义的辅助函数创建一个二维数组(一个二维数组)。
let map = createArray(1, dimensions);
Set up a random column and random row to create a random starting point for the first tunnel.
设置随机列和随机行以为第一个隧道创建随机起点。
let currentRow = Math.floor(Math.random() * dimensions), currentColumn = Math.floor(Math.random() * dimensions);
To avoid the complexity of diagonal turns, the algorithm needs to specify the horizontal and vertical directions. Every cell sits in a two-dimensional array and could be identified with its row and column. Because of this, the directions could be defined as subtractions from and/or additions to the column and row numbers.
为了避免对角线转弯的复杂性,该算法需要指定水平和垂直方向。 每个单元格都位于二维数组中,并可以通过其行和列进行标识。 因此,可以将方向定义为列号和行号的减去和/或增加。
For example, to go to a cell around the cell [2][2], you could perform the following operations:
例如,要转到单元格[2] [2]周围的单元格,可以执行以下操作:
to go up, subtract 1 from its row [1][2]
向上 ,从其行[1] [2]中减去1
to go down, add 1 to its row [3][2]
向下移动 ,在其行[3] [2]上加1
to go right, add 1 to its column [2][3]
要右移 ,请在其列[2] [3]中添加1
to go left, subtract 1 from its column [2][1]
向左走 ,在其列[2] [1]中减去1
The following map illustrates these operations:
下图说明了这些操作:
Now, set the directions
variable to the following values that the algorithm will choose from before creating each tunnel:
现在,在创建每个隧道之前,将directions
变量设置为算法将从中选择的以下值:
let directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
Finally, initiate randomDirection
variable to hold a random value from the directions array, and set the lastDirection
variable to an empty array which will hold the older randomDirection
value.
最后,启动randomDirection
变量以保存lastDirection
数组中的随机值,并将lastDirection
变量设置为一个空数组,该数组将保留较旧的randomDirection
值。
Note: the lastDirection
array is empty on the first loop because there is no older randomDirection
value.
注意:由于没有较旧的randomDirection
值,因此lastDirection
数组在第一个循环中为空。
let lastDirection = [], randomDirection;
Next, make sure maxTunnel
is not zero and the dimensions and maxLength
values have been received. Continue finding random directions until you find one that isn’t reverse or identical to lastDirection
. This do while loop helps to prevent overwriting the recently-drawn tunnel or drawing two tunnels back-to-back.
接下来,确保maxTunnel
不为零,并且已经接收到维度和maxLength
值。 继续寻找随机方向,直到找到与lastDirection
反向或相同的lastDirection
。 这种do while循环有助于防止覆盖最近绘制的隧道或背对背绘制两个隧道。
For example, if your lastTurn
is [0, 1], the do while loop prevents the function from moving forward until randomDirection
is set to a value that is not [0, 1] or the opposite [0, -1].
例如,如果lastTurn
为[0,1],则do while循环将阻止函数向前移动,直到randomDirection
设置为非[0,1]或相反的[0,-1]的值为止。
do { randomDirection = directions[Math.floor(Math.random() * directions.length)]; } while ((randomDirection[0] === -lastDirection[0] && randomDirection[1] === -lastDirection[1]) || (randomDirection[0] === lastDirection[0] && randomDirection[1] === lastDirection[1]));
In the do while loop, there are two main conditions that are divided by an || (OR) sign. The first part of the condition also consists of two conditions. The first one checks if the randomDirection
’s first item is the reverse of the lastDirection
’s first item. The second one checks if the randomDirection
’s second item is the reverse of the lastTurn
’s second item.
在do while循环中,有两个主要条件由||除。 (OR)标志。 条件的第一部分还包括两个条件。 第一个检查是否randomDirection
的第一项是相反lastDirection
的第一项。 第二个检查是否randomDirection
的第二项是相反lastTurn
的第二项。
To illustrate, if the lastDirection
is [0,1] and randomDirection
is [0,-1], the first part of the condition checks if randomDirection
[0] === — lastDirection
[0]), which equates to 0 === — 0, and is true.
为了说明这一点,如果lastDirection
为[0,1]且randomDirection
为[0,-1],则条件的第一部分将检查randomDirection
[0] === — lastDirection
[0])是否等于0 == = — 0,并且为true。
Then, it checks if (randomDirection
[1] === — lastDirection
[1]) which equates to (-1 === -1) and is also true. Since both conditions are true, the algorithm goes back to find another randomDirection
.
然后,它检查是否等于(-1 === -1)的( randomDirection
[1] === — lastDirection
[1])是否成立。 由于两个条件都成立,因此该算法返回以查找另一个randomDirection
。
The second part of the condition checks if the first and second values of both arrays are the same.
条件的第二部分检查两个数组的第一和第二值是否相同。
After choosing a randomDirection
that satisfies the conditions, set a variable to randomly choose a length from maxLength
. Set tunnelLength
variable to zero to server as an iterator.
选择满足条件的randomDirection
后,设置一个变量以从maxLength
随机选择一个长度。 将tunnelLength
变量设置为零,以便服务器作为迭代器。
let randomLength = Math.ceil(Math.random() * maxLength), tunnelLength = 0;
Make a tunnel by turning the value of cells from one to zero while the tunnelLength
is smaller than randomLength
. If within the loop the tunnel hits the edges of the map, the loop should break.
在tunnelLength
小于randomLength
通过将像元的值从1变为零来tunnelLength
randomLength
。 如果在环路内,隧道撞到了地图的边缘,则环路应断开。
while (tunnelLength < randomLength) { if(((currentRow === 0) && (randomDirection[0] === -1))|| ((currentColumn === 0) && (randomDirection[1] === -1))|| ((currentRow === dimensions — 1) && (randomDirection[0] ===1))|| ((currentColumn === dimensions — 1) && (randomDirection[1] === 1))) { break; }
Else set the current cell of the map to zero using currentRow
and currentColumn.
Add the values in the randomDirection
array by setting currentRow
and currentColumn
where they need to be in the upcoming iteration of the loop. Now, increment the tunnelLength
iterator.
否则,使用currentRow
和currentColumn.
将地图的当前单元格设置为零currentColumn.
通过设置currentRow
和currentColumn
在循环的即将到来的迭代中需要的位置,将这些值添加到randomDirection
数组中。 现在,增加tunnelLength
迭代器。
else{ map[currentRow][currentColumn] = 0; currentRow += randomDirection[0]; currentColumn += randomDirection[1]; tunnelLength++; } }
After the loop makes a tunnel or breaks by hitting an edge of the map, check if the tunnel is at least one block long. If so, set the lastDirection
to the randomDirection
and decrement maxTunnels
and go back to make another tunnel with another randomDirection
.
循环通过敲打地图的边缘制作隧道或破坏隧道后,检查隧道是否至少有一个街区长。 如果是这样,请将lastDirection
设置为randomDirection
并递减maxTunnels
,然后返回以使用另一个randomDirection
创建另一个隧道。
if (tunnelLength) { lastDirection = randomDirection; maxTunnels--; }
This IF statement prevents the for loop that hit the edge of the map and did not make a tunnel of at least one cell to decrement the maxTunnel
and change the lastDirection
. When that happens, the algorithm goes to find another randomDirection
to continue.
此IF语句可防止for循环触及地图的边缘,并且不会在至少一个单元格的通道上减小maxTunnel
并更改lastDirection
。 发生这种情况时,算法将继续查找另一个randomDirection
。
When it finishes drawing tunnels and maxTunnels
is zero, return the resulting map with all its turns and tunnels.
当完成绘制隧道并且maxTunnels
为零时,返回带有所有转弯和隧道的结果地图。
} return map;};
You can see the complete algorithm in the following snippet:
您可以在以下代码段中看到完整的算法:
Congratulations for reading through this tutorial. You are now well-equipped to make your own map generator or improve upon this version. Check out the project on CodePen and on GitHub as a react application.
祝贺您通读本教程。 现在您已经具备了制作自己的地图生成器或在此版本上进行改进的能力。 检出CodePen和GitHub上的项目作为React应用程序。
Don’t forget to share your projects in the comments section. If you liked this project, please give it some claps and following me for similar tutorials.
不要忘记在评论部分共享您的项目。 如果您喜欢这个项目,请给它一些鼓掌,并跟随我获得类似的教程。
Special thanks to Tom (@moT01 on Gitter) for co-writing this article.
特别感谢Tom( Gitter上的@ moT01)共同撰写本文。
随机游走算法简单图