程序结构生成
Procedural structure generation
我现在有一个基于体素的游戏正在开发中,到目前为止,我通过使用 Simplex Noise 来生成我的世界。现在我想生成一些其他结构,如河流、城市和其他东西,这些东西不容易生成,因为我将我的世界(实际上是无限的)分成 64x128x64 的块。我已经生成了树(叶子可以长成相邻的块),通过为一个块生成树,加上它周围的 8 个块的树,所以树叶不会丢失。但是如果我进入更高的维度会变得困难,当我必须计算一个块时,考虑半径为 16 个其他块的块。
有没有更好的方法来做到这一点?
根据生成结构所需的复杂性,您可能会发现首先在单独的数组中生成它很有用,甚至可能是地图(位置到内容字典,在高度稀疏的情况下很有用),然后将结构转移到世界上?
至于自然地貌,您可能想google如何在景观生成中使用分形。
我在一本书上读到了一些关于这方面的内容,他们在这些情况下所做的是根据应用程序对块进行更精细的划分,即:如果你要生长非常大的物体,它可能对有另一个单独的逻辑分区,例如 128x128x128,仅用于此特定应用。
本质上,数据都在同一个地方,只是逻辑分区不同而已。
说实话,没做过体素,所以别把我的回答当真,抛砖引玉。顺便说一下,这本书是游戏引擎gems 1,他们在那里有一个关于体素引擎的gem。
关于河流,能不能给水定个等级,让河流在山边山阶梯中自动生成?为避免将水置于山地警告中,您可以执行光线投射以检查它是否空闲 N 块。
我知道这个帖子很旧,我不擅长解释,但我会分享我的方法。
例如 5x5x5 棵树。你想要的是让你的噪声函数在 5x5 块的区域中 return 相同的值,这样即使在块之外,你仍然可以检查是否应该生成树。
// Here the returned value is different for every block
float value = simplexNoise(x * frequency, z * frequency) * amplitude;
// Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;
现在我们要种一棵树。为此,我们需要检查当前块相对于树的起始位置的 x y z 位置,以便我们可以知道该块是树的哪一部分。
if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
// Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
int startY = height(startX + 2, startZ + 2) + 1;
int relx = x - startX; // block pos relative to starting position
int relz = z - startZ;
for(int j = startY; j < startY + 5; j++) {
int rely = j - startY;
byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
tiles[i][j][k] = tile;
}
}
这里的tree 3d array差不多就是一个"prefab"的树,你可以通过它知道相对于起点的位置要设置什么方块。 (天哪,我不知道该如何解释,将英语作为我的第五语言也对我没有帮助;-;请随时改进我的答案或创建一个新答案)。我已经在我的引擎中实现了它,它完全可以工作。结构可以随心所欲地变大,不需要预加载块。这种方法的一个问题是我们几乎会在一个网格内生成树或结构,但这可以很容易地通过具有不同偏移量的多个八度音阶来解决。
所以回顾一下
for (int i = 0; i < 64; i++) {
for (int k = 0; k < 64; k++) {
int x = chunkPosToWorldPosX(i); // Get world position
int z = chunkPosToWorldPosZ(k);
// Here the returned value is different for every block
// float value = simplexNoise(x * frequency, z * frequency) * amplitude;
// Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;
if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
// Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
int startY = height(startX + 2, startZ + 2) + 1;
int relx = x - startX; // block pos relative to starting position
int relz = z - startZ;
for(int j = startY; j < startY + 5; j++) {
int rely = j - startY;
byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
tiles[i][j][k] = tile;
}
}
}
}
所以 'i' 和 'k' 在块内循环,而 'j' 在结构内循环。这几乎就是它应该如何工作的。
关于河流,我个人还没有做过,我不确定为什么在生成它们时需要在块周围设置块(你可以只使用 perlin worms,它会解决问题),但这几乎是相同的想法,对您的城市也是如此。
我现在有一个基于体素的游戏正在开发中,到目前为止,我通过使用 Simplex Noise 来生成我的世界。现在我想生成一些其他结构,如河流、城市和其他东西,这些东西不容易生成,因为我将我的世界(实际上是无限的)分成 64x128x64 的块。我已经生成了树(叶子可以长成相邻的块),通过为一个块生成树,加上它周围的 8 个块的树,所以树叶不会丢失。但是如果我进入更高的维度会变得困难,当我必须计算一个块时,考虑半径为 16 个其他块的块。
有没有更好的方法来做到这一点?
根据生成结构所需的复杂性,您可能会发现首先在单独的数组中生成它很有用,甚至可能是地图(位置到内容字典,在高度稀疏的情况下很有用),然后将结构转移到世界上?
至于自然地貌,您可能想google如何在景观生成中使用分形。
我在一本书上读到了一些关于这方面的内容,他们在这些情况下所做的是根据应用程序对块进行更精细的划分,即:如果你要生长非常大的物体,它可能对有另一个单独的逻辑分区,例如 128x128x128,仅用于此特定应用。
本质上,数据都在同一个地方,只是逻辑分区不同而已。
说实话,没做过体素,所以别把我的回答当真,抛砖引玉。顺便说一下,这本书是游戏引擎gems 1,他们在那里有一个关于体素引擎的gem。
关于河流,能不能给水定个等级,让河流在山边山阶梯中自动生成?为避免将水置于山地警告中,您可以执行光线投射以检查它是否空闲 N 块。
我知道这个帖子很旧,我不擅长解释,但我会分享我的方法。
例如 5x5x5 棵树。你想要的是让你的噪声函数在 5x5 块的区域中 return 相同的值,这样即使在块之外,你仍然可以检查是否应该生成树。
// Here the returned value is different for every block
float value = simplexNoise(x * frequency, z * frequency) * amplitude;
// Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;
现在我们要种一棵树。为此,我们需要检查当前块相对于树的起始位置的 x y z 位置,以便我们可以知道该块是树的哪一部分。
if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
// Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
int startY = height(startX + 2, startZ + 2) + 1;
int relx = x - startX; // block pos relative to starting position
int relz = z - startZ;
for(int j = startY; j < startY + 5; j++) {
int rely = j - startY;
byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
tiles[i][j][k] = tile;
}
}
这里的tree 3d array差不多就是一个"prefab"的树,你可以通过它知道相对于起点的位置要设置什么方块。 (天哪,我不知道该如何解释,将英语作为我的第五语言也对我没有帮助;-;请随时改进我的答案或创建一个新答案)。我已经在我的引擎中实现了它,它完全可以工作。结构可以随心所欲地变大,不需要预加载块。这种方法的一个问题是我们几乎会在一个网格内生成树或结构,但这可以很容易地通过具有不同偏移量的多个八度音阶来解决。
所以回顾一下
for (int i = 0; i < 64; i++) {
for (int k = 0; k < 64; k++) {
int x = chunkPosToWorldPosX(i); // Get world position
int z = chunkPosToWorldPosZ(k);
// Here the returned value is different for every block
// float value = simplexNoise(x * frequency, z * frequency) * amplitude;
// Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;
if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
// Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
int startY = height(startX + 2, startZ + 2) + 1;
int relx = x - startX; // block pos relative to starting position
int relz = z - startZ;
for(int j = startY; j < startY + 5; j++) {
int rely = j - startY;
byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
tiles[i][j][k] = tile;
}
}
}
}
所以 'i' 和 'k' 在块内循环,而 'j' 在结构内循环。这几乎就是它应该如何工作的。
关于河流,我个人还没有做过,我不确定为什么在生成它们时需要在块周围设置块(你可以只使用 perlin worms,它会解决问题),但这几乎是相同的想法,对您的城市也是如此。