Search Unity

Godus like procedural terrain generation

Discussion in 'Works In Progress - Archive' started by ExSoax, Mar 31, 2016.

  1. ExSoax

    ExSoax

    Joined:
    Nov 27, 2014
    Posts:
    11
    Hi guys,
    I'm making a video game inspired by Godus mobile game. I did a terrain generator and it works fine but it is really slow. Can someone help me for optimizing my code?

    This is how it looks:





    The algorithm is based on the generation of layered deformed circumferences, starting from top ( small range shapes ) to bottom ( high range shapes ). This is the function that i would like to optimize:

    Code (CSharp):
    1.    
    2.  
    3. delegate float SinusoidalTerm(float x);
    4.  
    5. public static void DrawBlob(int MaxX, int MaxY, int CenterX, int CenterY, int OriginalRadius, float Height, ref float[,] Heights, ref float[,,] AlphaMap)
    6.     {
    7.         int TermCount = Random.Range(1,5);
    8.  
    9.         float[] TermOffsets = Enumerable.Range(0, TermCount)
    10.                               .Select(r => Random.Range(0f, 2 * Mathf.PI))
    11.                               .ToArray();
    12.  
    13.         float[] TermWeights = Enumerable.Range(0, TermCount)
    14.                         .Select(r => Random.Range(0f, 1f))
    15.                         .ToArray();
    16.  
    17.         SinusoidalTerm Term = (x) =>
    18.         {
    19.             float Sum = 0;
    20.             for (int i = 0; i < TermCount; i++)
    21.                 Sum += TermWeights[i] * Mathf.Sin(TermOffsets[i] + 2 * Mathf.PI * x);
    22.             return Sum;
    23.         };
    24.  
    25.         float Radius = OriginalRadius;
    26.         float RadiusTo2 = Radius * Radius / 4;
    27.  
    28.         for (float oy = -Radius; oy <= Radius; oy++)
    29.             for (float ox = -Radius; ox <= Radius; ox++)
    30.             {
    31.                 double Eq1 = ox * ox + oy * oy;
    32.                 double Eq2 = RadiusTo2 - RadiusTo2 * 0.25f * Term(ox / Radius);
    33.                 if (
    34.                    Eq1 <= Eq2
    35.                 )
    36.                 {
    37.                     int x = CenterX + (int)ox, y = CenterY + (int)oy;
    38.                     if (x < MaxX && x > 0 && y < MaxY && y > 0 && Height > Heights[x, y])
    39.                     {
    40.                         Heights[x, y] = Height;
    41.                         try
    42.                         {
    43.                             AlphaMap[x, y, 0] = 1f - 10 * Height;
    44.                             AlphaMap[x, y, 1] = Mathf.Abs(Height - 0.5f);
    45.                             AlphaMap[x, y, 2] = Height * 2;
    46.                         }
    47.                         catch { }
    48.                     }
    49.                 }
    50.             }
    51.     }
    52.  
    Thanks in advance.
     
  2. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,448
    Do yo need that try-catch there?

    Did you check with Profiler if any other part is slowing down? (like, for example applying the heightmap to terrain is quite slow)
     
  3. ExSoax

    ExSoax

    Joined:
    Nov 27, 2014
    Posts:
    11
    Do u think that the try catch block can slow a bit? I will check with the profiler thanks.
     
  4. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,448
  5. ExSoax

    ExSoax

    Joined:
    Nov 27, 2014
    Posts:
    11
    I did some tries... well, removing in the:
    Code (CSharp):
    1. double Eq2 = RadiusTo2 - RadiusTo2 * 0.25f * Term(ox / Radius)
    this term:
    Code (CSharp):
    1. RadiusTo2 * 0.25f * Term(ox / Radius)
    will speed incredibly up the generation.
    With that term it tooks something like 59 seconds while without it tooks 5 seconds... That code is usefull for deforming the circles... Without that it looks like this:
     
  6. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,448
    other things i've heard:
    - Mathf. is slower then System.Math
    - Using LINQ is slower
    - Use bit-shifting in calculations where possible
    - Random.range() is bit slow
    - Sine/Cosines are bit slow (if you are doing lots of them)

    there's also other tips listed here (see comments)
    http://unitycoder.com/blog/2014/04/23/unity-optimization-tips/
     
  7. NightAvail

    NightAvail

    Joined:
    Feb 23, 2015
    Posts:
    13
    I also had some issues with slow terrain loading as the content for my game is dynamic and loads maps based on colormaps, splatmaps, normalmaps, etc. Initially my load time was 2 min and 32 sec to load up everything, not ideal... However after some playing around I got the load time down to just 4 sec (no caching) which is a huge difference when loading a ton of data.

    I'd suggest making clever use of arrays. Don't rely on Texture.GetPixel32 and instead save the pixels into a Color32[x,y] array if you need access to it.

    List<> was a huge factor in my slow processing. A simple assignment would cost me 5 seconds... Don't get me wrong, initializing List<> objects are fast, but when it came to modifying them I experienced some issues; and so I created a new type of List<> object which acts more like a Dictionary now than a list, and is super fast on lookups.

    Replace every string concatenation you may have with a StringBuilder and make use of string.Format(). Watch your memory usage however. I ended up creating something I call a ByteReader which is more memory-efficient due to the fact that I'm loading in a ton of custom scripts that can contain Unicode characters. However StringBuilder is great and will satisfy most conditions.

    Utilize logging of some sort. I created my own custom profiler and I'm constantly saving as much as possible into log files to identify places I can improve.

    Identify places where you can use multi-threading (especially with logging). This is another reason to move the Texture.GetPixel32 data into its own array so you can use it in separate thread later on. Unity's rendering-based classes are not available for use in separate threads, however structures like Vector, Color32, etc are available for use. I believe this was said before but definitely use Color32 over Color.

    Speaking of Color32, I noticed a slight increase in performance when I created my own IsEqual method and checked the bytes myself...

    Finally, after everything is processed use a database like SQLite to cache your results. After caching my results my terrain processes in around 2 sec. I also compress some of my results (textures that I created dynamically) into blobs.

    I hope that helps and good luck!