Mil*_*les 7 c# debugging terrain unity-game-engine 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);
}
}
Run Code Online (Sandbox Code Playgroud)
有趣且可能很重要的细节:瓷砖与x/z方向上角落相邻的瓷砖正确连接.在任何时候瓦片都没有相同的x-to-z delta时,会有一个采样转换.在下图中,右侧图块是另一个图块的+ x和+ z.具有这种关系的所有瓷砖都已正确连接.
这是以zip格式上传的项目文件.告诉我它是否无法正常工作...... http://www.filefactory.com/file/4fc75xtd3yzl/n/FPS_zip
要查看地形,请在切换到testScene后按Play,如果它没有从那里开始.GameObject生成数据和terrain对象(它具有来自scripts/General /附加的RandomTerrain脚本).您可以从那里将参数修改为perlin噪声.请注意,现在只有第一个perlin八度音阶,o_elevator在地形生成中处于活动状态.为了解决这个故障,所有其他公共perlin八度变量都没有影响.
TL;DR:您需要删除 getTargetCoords 并使用tr[x,y].position = new Vector3(pagePos.y, 0.0f, pagePos.x)
.
如何实现上述目标:一次在 1 个维度上工作。这意味着将其转变(来自附加的统一代码):
array[cx,cy] = sample(
new Vector3((float)cx * tileSize / (float)(xlim - 1) + pagePos.x, 0.0f,
(float)cy * tileSize / (float)(ylim - 1) + pagePos.y));
Run Code Online (Sandbox Code Playgroud)
进入这个:
array[cx,cy] = sample(
new Vector3(
(float)cx * tileSize / (float)(xlim - 1) + pagePos.x,
0.0f,
0.0f
)
);
Run Code Online (Sandbox Code Playgroud)
我很快意识到你的有问题getPagePos
,因为它正在添加一个值getTargetCoords
,并且该函数使用了由该函数设置的位置getPagePos
!圈子里的圈子,伙计们。
protected void getTargetCoords()
{
xCoord = Mathf.RoundToInt(target.position.x / tileSize);
zCoord = Mathf.RoundToInt(target.position.z / tileSize);
}
Run Code Online (Sandbox Code Playgroud)
因此,让我们放弃该函数和对它的引用。事实上,我们getPagePos
现在来简化一下:
protected Vector2 getPagePos(int x, int y)
{
return new Vector2( 0.0f,0.0f);
}
Run Code Online (Sandbox Code Playgroud)
天哪,陶诺斯疯了吗?!嗯,是的,但这不是重点。假设您的示例函数是正确的(我根据您的断言授予您的假设),那么我们应该从获取简单值(零!)开始并验证添加的每个附加假设。
简化之后getPagePos
,我们再来看一下(float)cx * tileSize / (float)(xlim - 1) + pagePos.x
。它想做什么?嗯,看起来它试图通过从图块的原点偏移来找到当前样本的世界坐标点。让我们验证这是一个真实的假设:xLim
样本分布在tileSize
各个单位上,因此世界坐标中的每个步长为tileSize / xLim
。将其乘以当前值将得到我们的偏移量,因此这段代码看起来是正确的。
现在,让我们获得一个简单的平铺偏移并查看一切是否匹配。我不会尝试将水滴集中在所有图块的中间,而是要做一个简单的网格偏移(稍后可以调整以恢复居中)。
return new Vector2(
tileSize * (float)x,
0.0f
);
Run Code Online (Sandbox Code Playgroud)
当您运行此命令时,您可能很快就会发现问题所在。底部边缘映射其旁边的图块的顶部边缘,而不是与左侧匹配的右侧边缘。无需深入研究,这可能是由您的采样方法的工作方式引起的。我不知道。以下是更新后的功能:
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);
}
Run Code Online (Sandbox Code Playgroud)