Perlin地形页面不匹配:难以捉摸的bug

时间:2012-07-12 14:15:23

标签: c# debugging unity3d terrain perlin-noise

我一直在Unity游戏引擎中开发一款游戏,希望能够将页面地形用于无限世界(如今的常见主题)。我的地形发生器专门使用perlin噪音。但是在这个时候,一个错误严重减缓了开发:页面边缘不匹配,甚至没有关闭。问题出现在我生成地形的代码中,或者可能在我每次生成页面时运行的另一段代码中。但问题绝对不在于统一函数调用,所以即使你不熟悉统一,你也可以帮助我。

可能的原因...... 1)针对每页错误位置采样的Perlin噪声,数学失败。 2)在页面生成之间重新生成随机数生成器。 3)Perlin噪声正在重新生成页面之间。 4)?我不知道。

注意:我几乎排除了数字2和3,看着我所有的其他代码。我只对每个生成器播种一次,而且Unity不会恰好在生成器的运行之间重置Random。

实际上,如果你能描述一种调试方法,这会让这更容易,请指教。从长远来看,它会对我有所帮助。

以下是我为每个页面运行一次的代码,并提供完整的评论,因此您无需了解Unity3D ......

 /* x and y are the indices of the tile being loaded. The game maintains a
    square of pages loaded, where the number of pages per side is equal to
    loadSquares (see below). x and y are the indices of the page to generate
    within that square. */
    public void generate(int x, int y)
    {
         /* pagePos represents the world x and y coordinates of the bottom left
            corner of the page being generated. This is given by...
              new Vector2((-(float)loadSquares / 2.0f + x + xCoord) * tileSize, 
                          (-(float)loadSquares / 2.0f + y + zCoord) * tileSize);
            This is because the origin tile's center is at 0, 0. xCoord represents
            the tile that the target object is on, which is what the loaded square
            is centered around. tileSize is the length of each side of each tile.
         */
        Vector2 pagePos = getPagePos(x, y);
        //Here I get the number of samples x and y in the heightmap.
        int xlim = td[x,y].heightmapWidth;
        int ylim = td[x,y].heightmapHeight;
            //The actual data
        float[,] array = new float[xlim, ylim];
            //These will represent the minimum and maximum values in this tile.
            //I will need them to convert the data to something unity can use.
        float min = 0.0f;
        float max = 0.0f;
        for(int cx = 0; cx < xlim; cx++)
        {
            for(int cy = 0; cy < ylim; cy++)
            {
                //Here I actually sample the perlin function.
                //Right now it doesn't look like terrain (intentionally, testing).
                array[cx,cy] = sample(
                    new Vector3((float)cx / (float)(xlim - 1) * tileSize + pagePos.x, 
                                (float)cy / (float)(ylim - 1) * tileSize + pagePos.y, 
                    122.79f));
                //On the first iteration, set min and max
                if(cx == 0 && cy == 0)
                {
                    min = array[cx,cy];
                    max = min;
                }
                else
                {
                    //update min and max
                    min = Mathf.Min(min, array[cx,cy]);
                    max = Mathf.Max(max, array[cx,cy]);
                }
            }
        }
        //Set up the Terrain object to receive the data
        float diff = max != min ? max - min : 10.0f;
        tr[x,y].position = new Vector3(pagePos.x, min, pagePos.y);
        td[x,y].size = new Vector3(tileSize, diff, tileSize);
        //Convert the data to fit in the Terrain object
     /* Unity's terrain only accepts values between 0.0f and 1.0f. Therefore,
        I shift the terrain vertically in the code above, and I
        squish the data to fit below.
     */
        for(int cx = 0; cx < xlim; cx++)
        {
            for(int cy = 0; cy < ylim; cy++)
            {
                array[cx,cy] -= min;
                array[cx,cy] /= diff;
            }
        }
        //Set the data in the Terrain object
        td[x,y].SetHeights(0, 0, array);
    }
}

有趣且可能很重要的细节:瓷砖与x / z方向上角落相邻的瓷砖正确连接。在任何时候瓦片都没有相同的x-to-z delta时,会有一个采样转换。在下图中,右侧图块是另一个图块的+ x和+ z。具有此关系的所有图块都已正确连接。

enter image description here

这是以zip格式上传的项目文件。告诉我,如果它不起作用或什么...... http://www.filefactory.com/file/4fc75xtd3yzl/n/FPS_zip

要查看地形,请在切换到testScene后按Play,如果它没有从那里开始。 GameObject生成数据和terrain对象(它具有来自scripts / General /附加的RandomTerrain脚本)。您可以从那里将参数修改为perlin噪声。请注意,现在只有第一个perlin八度音阶,o_elevator在地形生成中处于活动状态。为了解决这个问题,所有其他公共perlin八度变量都没有影响。

1 个答案:

答案 0 :(得分:1)

TL; DR:您需要删除getTargetCoords并使用tr[x,y].position = new Vector3(pagePos.y, 0.0f, pagePos.x)

如何实现上述目标:一次一维地工作。这意味着转向(从您附加的统一代码):

            array[cx,cy] = sample(
                new Vector3((float)cx * tileSize / (float)(xlim - 1) + pagePos.x, 0.0f,
                            (float)cy * tileSize / (float)(ylim - 1) + pagePos.y));

进入这个:

            array[cx,cy] = sample(
                new Vector3(
                    (float)cx * tileSize / (float)(xlim - 1) + pagePos.x,
                    0.0f,
                    0.0f
                ) 
            ); 

我很快意识到您的getPagePos出了问题,因为它正在添加getTargetCoords的值,而该函数使用的是getPagePos函数设置的位置!圈子里的圈子,人们。

protected void getTargetCoords()
{
    xCoord = Mathf.RoundToInt(target.position.x / tileSize);
    zCoord = Mathf.RoundToInt(target.position.z / tileSize);
}

所以让我们失去那个功能并引用它。实际上,我们暂时简化getPagePos

protected Vector2 getPagePos(int x, int y)
{
    return new Vector2( 0.0f,0.0f);
}
哦,我的上帝是TAWNOS CRAZY?!嗯,是的,但那不是重点。假设您的样本函数是正确的(我根据您的断言授予您的假设),那么我们应该从获取简单值(零!)开始,并在添加每个额外假设时进行验证。

简化getPagePos后,让我们再看看(float)cx * tileSize / (float)(xlim - 1) + pagePos.x。它想做什么?好吧,它似乎是试图通过从图块的原点偏移来找到当前样本的世界坐标点。让我们验证这是一个真实的假设:xLim个样本分布在tileSize个单位上,因此世界坐标中的每个步长都是tileSize / xLim。乘以当前值将获得我们的偏移量,因此该代码看起来正确。

现在,让我们得到一个简单的瓷砖偏移,看看一切都匹配。我不是试图将掉落放在所有图块的中间,而是要做一个简单的网格偏移(这可以在以后进行调整以恢复对中)。

    return new Vector2( 
        tileSize * (float)x,
        0.0f
    );

当你运行它时,你可能会很快看到问题所在。底部边缘映射其旁边的图块的顶部边缘,而不是与左侧匹配的右边缘。如果不深入挖掘,可能是由于采样方法的工作方式。我不确定。这是更新的功能:

protected Vector2 getPagePos(int x, int y)
{
    float halfTile = loadSquares / 2.0f;
    return new Vector2( 
        (-halfTile * tileSize) + (x * tileSize),
        (-halfTile * tileSize) + (y * tileSize)
    );
}

public void generate(int x, int y)
{
    if(x >= loadSquares || x < 0 || y >= loadSquares || y < 0) return;
    t[x,y] = Terrain.CreateTerrainGameObject(new TerrainData()).GetComponent<Terrain>();
    t[x,y].name = "Terrain [" + x + "," + y + "]";
    td[x,y] = t[x,y].terrainData;
    td[x,y].heightmapResolution = resolution;

    tr[x,y] = t[x,y].transform;
    Vector2 pagePos = getPagePos(x, y);
    //Actual data generation happens here.
    int xLim = td[x,y].heightmapWidth;
    int yLim = td[x,y].heightmapHeight;

    float[,] array = new float[xLim, yLim];
    float min = int.MaxValue;
    float max = int.MinValue;
    for(int cx = 0; cx < xLim; cx++)
    {
        for(int cy = 0; cy < yLim; cy++)
        {
            array[cx,cy] = sample(
                new Vector3(
                    (float)cx * (tileSize / (float)(xLim - 1)) + pagePos.x, 
                    0.0f,
                    (float)cy * (tileSize / (float)(yLim - 1)) + pagePos.y
                ) 
            ); 

            if(min > array[cx,cy]) 
                min = array[cx,cy];
            if(max < array[cx,cy]) 
                max = array[cx,cy];

        }
    }

    //Set up the Terrain object to receive the data
    float diff = max != min ? max - min : 10.0f;
    tr[x,y].position = new Vector3(pagePos.y, min, pagePos.x);
    td[x,y].size = new Vector3(tileSize, diff, tileSize);

    //Convert the data to fit in the Terrain object
    for(int cx = 0; cx < xLim; cx++)
    {
        for(int cy = 0; cy < yLim; cy++)
        {
            array[cx,cy] -= min;
            array[cx,cy] /= diff;
        }
    }
    //Set the data in the Terrain object
    td[x,y].SetHeights(0, 0, array);
}