程序结构生成

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,它会解决问题),但这几乎是相同的想法,对您的城市也是如此。