Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

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,072
    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,072
  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,072
    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!