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.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Alternative to bunch of if statements?

Discussion in 'Scripting' started by Emolk, Feb 26, 2020.

  1. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    I have a tileset of edges as seen below:



    Edges are placed on a separate tilemap to the main tilemap of actual blocks.

    I have started an algorithm that determines which edge to place based on the surrounding tiles. This is based on a bunch of ugly if statements. If there is a block below, spawn a straight edge above it etc.

    I'm wondering if anybody has a better solution to determine which edge to place on a tile instead of a bunch of if / switch case statements.

    Bad code:

    Code (CSharp):
    1.    Tile DetermineEdge(Vector2 pos){
    2.  
    3.         Vector3Int tilePos = pos.V2toV3intConvertion();
    4.  
    5.         TileBase tile = blockMap.GetTile(tilePos);
    6.  
    7.         if(blockMap.GetTile(tilePos))
    8.             return null;
    9.  
    10.         Vector3Int tileBelowPos = pos.V2toV3intConvertion();
    11.         tileBelowPos.y -= 1;
    12.         var tileBelow = blockMap.GetTile(tileBelowPos);
    13.  
    14.         Vector3Int tileAbovePos = pos.V2toV3intConvertion();
    15.         tileAbovePos.y += 1;
    16.         var tileAbove = blockMap.GetTile(tileAbovePos);
    17.  
    18.  
    19.         if(tileBelow)
    20.             return edgeBottom;
    21.         if(tileAbove)
    22.             return edgeAbove;
    23.  
    24.         // Continue if statements
    25.  
    26.         return null;
    27.  
    28.  
    29.     }
     
  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Code (csharp):
    1. if(i == something) return whatever;
    2. if(i == something_else) return whatever2;
    3. if(i == something_other) return whatever3;
    is functionally equivalent to switch
    Code (csharp):
    1. switch(i) {
    2.   case something: return whatever; // you normally need a 'break' here, but not with return
    3.   case something_else: return whatever2;
    4.   case something_other: return whatever3;
    5. }
    However, this is not good enough for you, because your condition changes in every clause.
    That's always a problem.

    Either find a way to formalize the condition so that it can be done without repeating, or you'll have to make a fork transformation as you did. Sometimes it has to happen.

    The only thing you can do in such cases is to try and break the code into smaller units to increase readability, but sometimes that's also impossible, and sometimes it will actually degrade readability.

    So it really depends on what you're trying to accomplish, there is no silver bullet answer.
    If it helps you feel better, any kind of logic with square or cubic shapes introduces this kind of brute-force checking.
    There is no easy way out.

    I had a similar problem relatively recently, which was geometric in nature, but had to do with having to work with cubic arrangement of points in many places, and it's not really applicable to your case. But you can also think along the lines of having a hard-coded square-to-linear transformation, which you can use throughout your code, to easily match things, as if they were linear, by your own convention.

    For example, if you are constantly checking whether something is up or down, or left.
    Do the circle of such directions once, and map it to an array that corresponds locally, i.e. [right, up, top, left]

    Next time this crops up, you can compare an array of similar data that was assembled with the same convention in mind. Because you've adopted a convention you can also introduce a variety of tools to help you convert data, or find something in the structure etc.

    In other words, you invent a convention, and work around this problem by applying this convention to all kinds of data that is expected to be arranged the same way.

    By that line of thinking, you can also make struct type that is able to operate on such data, while presenting itself as a square or a cube arrangement.
     
    Emolk likes this.
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Also, notice the pattern of square tiles. You can treat them all as having just right and bottom edge. It will be true for all of them. Top and left are redundant. You can pick some other convention of course, but the core message is the same.
     
  4. ServantOMallard

    ServantOMallard

    Joined:
    Aug 17, 2018
    Posts:
    14
    Emolk likes this.
  5. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    Thanks for responses orion and Servant i will certainty check them out.

    I thought of this solution as i was heating up my dinner, not sure if it will work:

    Create a scriptableobject 'EdgeData' for each edge. Each EdgeData contains 8 bools, signifying if they edge to each of the 8 directions (including corner edges). For example, if the edge tile is the edge for only top and bottom edges, the top and bottom bools will be set to true.

    Then in my DetermineEdge function i simply record if the tile neighbors top, down left etc..

    Then i pass what DetermineEdge records into a new function that searches a list of EdgeData and attempts to match it with an EdgeData that has the same bools checked as the DetermineEdge function and when found, returns the edge (tile) of EdgeData.

    Thoughts on this approach? It seems pretty optimized and should be O(n)?
     
  6. adi7b9

    adi7b9

    Joined:
    Feb 22, 2015
    Posts:
    181
    In programming you cannot escape "if".

    But if you have a lots of if's then you should think about making functions, like:
    Code (CSharp):
    1.  
    2.  
    3. enum eDir
    4. {
    5.   X = 0,
    6.   N,
    7.   S,
    8.   E,
    9.   W,
    10.   NW,
    11.   NE,
    12.   SW,
    13.   SE,
    14.   NSE,
    15.   NSW,
    16.   NEW,
    17.   SEW
    18. }
    19.  
    20. //here you have the direction
    21. eDir WhereIsEmpty(Vector2 currentPos)
    22. {
    23.   //here you have a bunch of ifs but you should
    24.   var countEmpty = GetEmptyCellsByPos(currentXYPos);//you need to make this function
    25.   var dir = GetDir(currentPos, countEmpty);
    26.   return Get_eDir(dir);
    27. }
    28.  
    29. eDir Get_eDir(string dir)
    30. {
    31.     if (String.IsNullOrEmpty(dir))
    32.     {
    33.         return eDir.X;
    34.     }
    35.     List<eDir> directions = new List<eDir>();
    36.     foreach (var e in (eDir[])Enum.GetValues(typeof(eDir)))
    37.     {
    38.         directions.Add(e);
    39.     }
    40.     foreach (var c in dir)
    41.     {
    42.         var newList = directions.FindAll(a => a.ToString().Contains("" + c));
    43.         directions = new List<eDir>(newList);
    44.     }
    45.     if (directions.Count == 0)
    46.     {
    47.         return eDir.X;
    48.     }
    49.     return directions[0];
    50. }
    51.  
    52. string GetDir(Vector2 currentPos, int countEmpty)
    53. {
    54.   string dir = "";
    55.   var count = countEmpty;
    56.   for (int i = -1; i < 1; i++)
    57.   {
    58.     for (int j = -1; j < 1; j++)
    59.     {
    60.       if (cell[i, j] is valid and empty)
    61.       {
    62.          count--;
    63.          dir += getDirection(currentPos, new Vector2(i, j));
    64.       }
    65.       if (count == 0)
    66.       {
    67.         return dir;
    68.        }
    69.     }
    70.   }
    71. }
    72.  
    73. string getDirection(Vector2 currentPos, Vector2 nextPos)
    74. {
    75.   string s = "";
    76.   if (currentPos.X < nextPos.X)
    77.   {
    78.     s += "W";
    79.   }
    80.   else if (currentPos.X < nextPos.X)
    81.   {
    82.     s += "E";
    83.   }
    84.   if (currentPos.Y < nextPos.Y)
    85.   {
    86.     s += "N";
    87.   }
    88.   else if (currentPos.Y < nextPos.Y)
    89.   {
    90.     s += "S";
    91.   }
    92. return s;
    93. }
    94.  
    95.  
     
  7. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,022
    tested something similar here (scroll down middle of the page)
    https://unitycoder.com/blog/2012/10/14/rampart-prototype-seed-fill/

    can get unique value if you add those neighbor cell values together (with the added multiplication),
    so it then covers all the possible combinations, and could use that value without the switch case too.
     
    Emolk and eses like this.
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Consider what mgear showed. When working with tiles this is known as a bitmask, where every bit is arranged by a convention and integrated in one value in the end. This is one of the ways to do it that I covered only abstractly in my post.

    I'm not sure if it helps you, it depends on what exactly you're trying to accomplish, but thinking in terms of bits when you have a rigid repetitive structure with less than 32 individual components can be beneficial.

    I've made logic for orthogonally-rotated cubes based on bitmasks, because it turns out a cube can only end up in 24 unique orientations no matter how you rotate or tumble it.

    This have helped me figure out a system to compare any two arbitrary cubic configurations in microseconds and churn out a Quaternion. Also I include a mirror as well -- so with 4 rotations and a mirror along each axis, it turns out there are just 48 vertex configurations in total. However, I still had to codify these transformations as values.
     
    Emolk likes this.
  9. ServantOMallard

    ServantOMallard

    Joined:
    Aug 17, 2018
    Posts:
    14
    Yeah, this is basically the way the ruletiles in 2d extras handle it.

    You get a nice UI to define set of rules, each rule lets you set which tile to use dependent on which direction adjacent tiles are empty or have a tile.

    Cheers,

    Nathan
     
  10. ServantOMallard

    ServantOMallard

    Joined:
    Aug 17, 2018
    Posts:
    14


    This gets the idea across better than I can put it!
     
    Ryiah, orionsyndrome and Emolk like this.
  11. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    You can see a similar concept employed with marching square/cube algorithms. Check marching squares implementation as well, they had to solve a problem similar to yours.