- Home /
How to handle long loops
Hi there
I am currently using a for loop with a fairly large amount of code in it to generate procedural terrain. Using a 65 x 65 size terrain it is OK and works fine with about 5460 iterations. However when i make that terrain 129x129 unity stops responding and it will never end (about 20000 iterations). I hope to use this code for terrains as large as 1025x1025.
How can i make a for loop more efficient? Is there a method of making it either loop faster, or keep control so it doesn't overload the pc and crash unity.
Cheers, Greenie
Impossible to really tell without seeing how that loop works. The number of iterations isn't the issue, it's what it DOES for each iteration.
Here is the loop, it does a fair bit
for(var p = 0; p<=steps-1; p++){
//print(width);
//diamond
heights[xAnchor+width/2,yAnchor+height/2] = (((terrainDat.GetHeight(xAnchor,yAnchor)+terrainDat.GetHeight(xAnchor+width,yAnchor)+terrainDat.GetHeight(xAnchor,yAnchor+height)+terrainDat.GetHeight(xAnchor+width,yAnchor+height))/4.0)-this.gameObject.transform.position.x)/(terrainDat.size.x)+ Random.Range($$anonymous$$Noise,maxNoise);
terrainDat.SetHeights(0,0,heights);
//square
heights[xAnchor+width/2,yAnchor] = ((((terrainDat.GetHeight(xAnchor,yAnchor)+terrainDat.GetHeight(xAnchor+width,yAnchor))/2.0)-this.gameObject.transform.position.x)/(terrainDat.size.x))+ Random.Range($$anonymous$$Noise,maxNoise);
terrainDat.SetHeights(0,0,heights);
heights[xAnchor,yAnchor+height/2] = ((((terrainDat.GetHeight(xAnchor,yAnchor)+terrainDat.GetHeight(xAnchor,yAnchor+height))/2.0)-this.gameObject.transform.position.x)/(terrainDat.size.x))+ Random.Range($$anonymous$$Noise,maxNoise);
terrainDat.SetHeights(0,0,heights);
heights[xAnchor+width/2,height] = ((((terrainDat.GetHeight(xAnchor+width,yAnchor+height)+terrainDat.GetHeight(xAnchor+width,yAnchor))/2.0)-this.gameObject.transform.position.x)/(terrainDat.size.x))+ Random.Range($$anonymous$$Noise,maxNoise);
terrainDat.SetHeights(0,0,heights);
heights[width,yAnchor+height/2] = ((((terrainDat.GetHeight(xAnchor+width,yAnchor+height)+terrainDat.GetHeight(xAnchor,yAnchor+height))/2.0)-this.gameObject.transform.position.x)/(terrainDat.size.x))+ Random.Range($$anonymous$$Noise,maxNoise);
terrainDat.SetHeights(0,0,heights);
//check next
if(xAnchor+width >= origW && yAnchor+height >=origH){
//print("y");
width = width/2;
height = height/2;
step = width;
xAnchor=0;
yAnchor=0;
}
else{
//print("n");
if(xAnchor+width >=origW){
yAnchor = yAnchor + height;
xAnchor = 0;
}
else{xAnchor = xAnchor + width;}
}
}
All the calculations aren't the issue, that's fine. 20k iterations of those should be okay. I'm not familiar with terrains, but the calls to setheights could be trouble. Does that method access mesh data? If it does reads and writes to data on large meshes, it will generate huge memory overhead, which could cripple the loop.
It accesses terraindata which I assume is the same thing just a fancy name for unity terrain. Is there any way I could make it more efficient?
Sorry I went awol yesterday - I was on my way to bed. ;) $$anonymous$$y point was pretty much identical to Owen Reynold's reply below. I think it is, as I feared, because of the SetHeights methods.
Like I said, I don't have any experience with Unity's terrains, but a tentative guess is that they are a wrapper around either the $$anonymous$$esh class or a Texture2D for storing height data. I think the exposed methods such as SetHeights manipulate either the vertices of a $$anonymous$$esh or the pixels of a height map texture in ways that make sense for a terrain.
For a $$anonymous$$esh, a common mistake (or oversight, at least) is liberal use of those of the $$anonymous$$esh's properties that access its data arrays, that is, its .vertices, .uvs, .normals, and so on. The trick with these is that they do not actually return a reference to the array that stores the data, they ins$$anonymous$$d return a fresh copy of the entire contents of the array that stores all the data. This makes each individual access to its data highly expensive and means it must be done sparsely.
For a Texture2D, the common mistake is liberal use of SetPixels and Texture2D.Apply, the latter of which is the worst because it has to transfer the updated texture data over the bus from main memory to video memory. When textures are large, they can constitute quite a lot of data, which, like $$anonymous$$esh data, means its access is expensive and must be done sparsely.
Now, your code does not access and set this data sparsely. :) And that's your problem. You call SetHeights very liberally; in fact you call it multiple times with individual updates inside each iteration of your loop. I think the most significant optimization you could do with this code is to perform your calculations on a separate, static array of height data you manage yourself, and then, AFTER all height calculations are complete, call SetHeights on the actual TerrainData ONCE, and ONCE ONLY, with the finished data.
Calculations which take place on static arrays solely in main memory are incredibly fast. You're getting killed by bus bandwidth right now.
Answer by Owen-Reynolds · Jan 13, 2015 at 05:24 AM
What do you think all those terrainDat.SetHeights(0,0,heights);
lines are doing? You have one after each time you change a height value, so maybe you think they copy that one value into Unity's heightmap? But if they did that, wouldn't it look more like (x,y,heights[x,y])
?
The actual line looks more like it takes your entire array heights -- after all, it's SetHeights
, plural -- and copies all 10,000+ values to the terrain, starting at xBase and yBase. If that was the case, doing it over and over and over and over again, would be incredibly slow and pointless.
So I should be going "terrainDat.SetHeights(0,0,heights[x,y]);"?
Ok just got home from work and moved the set height command out of the loop. Worked like a dream. Also thanks to Juice-Tin for the idea to pull from a 2d array ins$$anonymous$$d of using getHeight. Terrains are generating in a couple of seconds and looking good!
Answer by Juice-Tin · Jan 13, 2015 at 07:40 AM
Method 1: Surely your player can't see the entire map at once?
It could be possible to first create a small section that the player spawns in, then overtime continue to slowly modify the terrain outwards as they play.
Method 2: Alternatively, "terrainDat.GetHeight" should be completely removed. If you're creating the terrain yourself, then you have no need to access the terrain's properties. Instead create the entire terrain data inside a simple 2D array, then loop through it any only set the terrain. If you need to get info, pull it from the 2D array you made, not the actual terrain itself.
Thanks mate, using this and Owen Reynolds i got it working pretty good :)
Answer by Kiwasi · Jan 12, 2015 at 11:21 PM
As a cheap fix you could make is a coroutine and throw in a yield return null every few iterations.
Another alternative would be to use smaller chunks of terrain, then stitch them together.
Yeah what iv'e done is throw in coroutine that yields until fixed update every 50 iterations. Doesn't freeze unity but takes a few $$anonymous$$utes to do a 128x128 terrain. I'm currently experimenting with other terrain gen formulas.
Would stitching them together be faster? surely this would be the same thing just more spread out?