# Do i make long/complex code logic or more art assets to simplify code?

## Recommended Posts

From what i understand with your reply, i should do the "L" shape with two " I " pieces? Or did i misunderstand, and you were actually suggesting the opposite?

Yes.
You have a bunch of pieces.  Since you aren't modeling the entire environment and instead are building it out of pieces, then use actual pieces.

The combinations for a wall aren't that complex.  You've got the straight line, and you've got the corners. If you have a T junction that is two corners.  You write about having curved corner walls, so really you've got this:

Four building blocks, all the curvy walls you want. You might sometimes need to have them back-to-back, but they should still work just fine for game purposes.

##### Share on other sites

From what i understand with your reply, i should do the "L" shape with two " I " pieces? Or did i misunderstand, and you were actually suggesting the opposite?

Yes.
You have a bunch of pieces.  Since you aren't modeling the entire environment and instead are building it out of pieces, then use actual pieces.

The combinations for a wall aren't that complex.  You've got the straight line, and you've got the corners. If you have a T junction that is two corners.  You write about having curved corner walls, so really you've got this:

Four building blocks, all the curvy walls you want. You might sometimes need to have them back-to-back, but they should still work just fine for game purposes.

Okay so my current approach is the right one it seems then, not sure what you mean by having them back to back?

##### Share on other sites
Back to back would be when you have two curves opposite each other, on either side of the barrier.

The biggest drawback to this approach is the potentially high polygon count and draw call count. It is generally better to have modelers build the whole level paying attention to layout. If you must use segments like this, do all you can to keep them as rendered instances so they have minimal performance impact. A large number of parts will add to an expensive rendering cost if you aren't careful.

##### Share on other sites

Back to back would be when you have two curves opposite each other, on either side of the barrier.

The biggest drawback to this approach is the potentially high polygon count and draw call count. It is generally better to have modelers build the whole level paying attention to layout. If you must use segments like this, do all you can to keep them as rendered instances so they have minimal performance impact. A large number of parts will add to an expensive rendering cost if you aren't careful.

It's a level editor so i am not sure i can see a way to avoid it. I guess i could try to merge the meshes into single meshes once in the level but thats another complication to worry about in future :P

##### Share on other sites
Nobody saw the elephant in the room. He is using a code driven approach where a data driven approach would be a superior alternative. Even if I had a single wall piece I'd have added a world transform to the wall data itself and none of that if-else business.
However, I would indeed have more variety of pieces too as drawing larger meshes in one go is faster.

##### Share on other sites

What do you mean by data driven approach ?

##### Share on other sites
Posted (edited)
What do you mean by data driven approach ?

Instead of code, use data to express the decisions, as it is easier to change / extend / update / fix.

Edited by Alberth

##### Share on other sites

Are you referring to using tile bit masking ? Because if so i already have that but it doesn't really make the code any easier.

##### Share on other sites
Posted (edited)

I am answering your general question "what do you mean by data driven approach?".

It literally means you store decisions, in whatever form, in data rather than in code, since data can be loaded at runtime, modified after shipping, modified by non-experts etc. I was not referring to anything specific.

You seem quite stuck. Have you tried approaching it from the other end?

Assume you have to use simple code at runtime. No complicated long if/else code. Assume simple plain draw code, like "for (data d : drawdatas) { draw(d); }". Can't get simpler than that. One wall piece, one data. What data do you need then to make that work?

Likely it's not the ideal solution, but you seem to need a new way to approach the problem. Once you get going again, see if you can improve the approach. Tile bit masking was too far, my "one data, one piece" is perhaps not far enough. Perhaps the sweet spot is somewhere in the middle?

Edited by Alberth

##### Share on other sites

I am answering your general question "what do you mean by data driven approach?".

It literally means you store decisions, in whatever form, in data rather than in code, since data can be loaded at runtime, modified after shipping, modified by non-experts etc. I was not referring to anything specific.

You seem quite stuck. Have you tried approaching it from the other end?

Assume you have to use simple code at runtime. No complicated long if/else code. Assume simple plain draw code, like "for (data d : drawdatas) { draw(d); }". Can't get simpler than that. One wall piece, one data. What data do you need then to make that work?

Likely it's not the ideal solution, but you seem to need a new way to approach the problem. Once you get going again, see if you can improve the approach. Tile bit masking was too far, my "one data, one piece" is perhaps not far enough. Perhaps the sweet spot is somewhere in the middle?

I don't follow how draw(d) would be stored in the tile to know what game objects to draw?

For example my code is currently like this which feels similar to your draw(d) idea:


{
// bottom left corner
WallManager.NewWall(WallType.Corner, tile.GameObject.transform, Vector3.zero);
}
{
// top left corner
WallManager.NewWall(WallType.Corner, tile.GameObject.transform, new Vector3(0, 90, 0));
}
{
//top right corner
WallManager.NewWall(WallType.Corner, tile.GameObject.transform, new Vector3(0, 180, 0));
}
else if (...
// straight pieces & columns to check & combinations of corners/straight pieces and colums per tile
// else if ... 100+ lines and counting.


I'm not even half way and its getting quite long and feels inelegant for implementing given the amount of checks and combinations involved. I can't seem to find blogs or information on other approaches for simplifying this code.

##### Share on other sites
Posted (edited)

Ah code, now we can talk solutions :)

I'm not even half way and its getting quite long and feels inelegant for implementing given the amount of checks and combinations involved.

I agree this looks wrong. It looks like you are testing for a few specific bits. You generally do this with bit masking. Terms you are looking for are bit masking, bit arithmetic, and more generally, bit operations.

Can you tell what  the mask is? That is, how did you derive the "if (tile.Mask == 22 || tile.Mask == 18 || tile.Mask == 55 || tile.Mask == 23 || tile.Mask == 54 || tile.Mask == 19)" line ?

I tried to reverse engineer it, but the result is wrong

print(bin(18)) # prints 00010010
print(bin(55)) # prints 00110111
print(bin(23)) # prints 00010111
print(bin(54)) # prints 00110110
print(bin(19)) # prints 00010011

If you check each column, and replace the value by * (dont care) if you  compare both a 0 and a 1 sometimes, I get

00010110
00010010
00110111
00010111
00110110
00010011

00*10*1*

There are 3 * bits there, which means there should be 8 cases, but you have only 6, so something is missing. That information should be in the description of your mask.

The trick here is that the * bits are not interesting, so you force them to 0 with an 'and 0' operation. The bits you care about must be preserved, so you  force them to stay the same with an 'and 1' operation. That means the bitmask here seems to be [except for the missing cases]:

00*10*1* and 11011010
// second number is the same as the first, with * replaced by 0, and (0/1) replaced by 1

The result of this operation is then 00010010 (again the first number, but only the * replaced by 0)


You can convert 11011010 back to decimal, I prefer hexadecimal, which is 0xDA.

Similarly, 00010010 is 0x12 in hexadecimal number system.

If you try that for all numbers 0..255:

for n in range(0, 256):
if (n & 0xDA) == 0x12:
print(n)
Output:
18
19
22
23
50
51
54
55

Values 50 and 51 are missing in your 'if' statement. I don't know why.

Can you find out?

[ you can likely split things out even more, if the Vec3 value is a separate part in the tile mask, you can convert that separately, reducing stuff by a factor 2 or 4. ]

Edited by Alberth

##### Share on other sites

Okay so the way i derived my if statements was placing the tiles in my world, clicking them to get their mask value and then knowing visually what wall it needed i was adding their mask value to the if statements by editing the script as i went...it was a slow and tedious task and i had not got through all combinations which would explain why i have missing values.

The code was basically - incomplete.

##### Share on other sites

Huh, you don't know what the mask value means? It is set somewhere, isn't it? (Or used somewhere other than while rendering, perhaps.)

##### Share on other sites

Huh, you don't know what the mask value means? It is set somewhere, isn't it? (Or used somewhere other than while rendering, perhaps.)

I never took computer science so i don't know much about the lower level binary and hexadecimal stuff, i'm learning as i go - but often i don't know what i don't know if you understand what i mean.

I came across the masking from a blog and followed how to set the tile mask value based on each tile's neighbours since someone suggested the idea to me, but it didn't really cover the next step of connecting the integer value to the wall type needed.

This is where it is set:

public void SetTileMask()
{
for (int i = 0; i < Tile_List.Count; i++)
{
int value = 0;
Tile tile = Tile_List[i];
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.left + Vector3.forward))
value += 1;
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.forward))
value += 2;
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.forward + Vector3.right))
value += 4;
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.left))
value += 8;
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.right))
value += 16;
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.back + Vector3.right))
value += 32;
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.back))
value += 64;
if (Tile_Dictionary.ContainsKey(tile.Position_XY + Vector3.back + Vector3.left))
value += 128;

}
}


##### Share on other sites

I never took computer science so i don't know much about the lower level binary and hexadecimal stuff, i'm learning as i go - but often i don't know what i don't know if you understand what i mean.
Binary and hexadecimal number systems are actually closer to math, as in they are different ways to write a number. It's a bit like writing "house" in different languages. If you compare the strokes of the pen what they wrote, it seems vastly different, but it's simply the same thing written in other ways.

A number in the decimal number system (what we normally use) is thus the same as a number in the binary number system, and the same as a number in a hexadecimal number system. Binary and hexadecimal stuff is not different than any math you do now. You can add, subtract, and multiply in those number systems just as easily as you can in the decimal system.

The only reason to use binary numbers is that 'and' and 'or' work on bits, and there is a one-to-one mapping between bits and digits in a number written in the binary number system. This makes manual computation of the result of an 'and' operation much much much simpler. Hexadecimal number systems are popular, because binary zeroes and ones get very long and error-prone very quickly. A single hexadecimal digit can express 4 binary digits, so I reduce writing by a factor 4, by using hexadecimal notation. Experienced users of the hexadecimal number system however never look at the digits, they see the underlying bit patterns of each digit. This much alike you not seeing 1, 3, 6 if I write 136.

Practical application of binary number systems are not very common in CS, you find it much more in electro-mechanical control systems, ie in computers controlling some device. Small computer systems like Raspberry Pi, and pretty much every device driver is filled with it. Digital systems within Electrical Engineering use it a lot.

Enough number theory, back to the problem:

I came across the masking from a blog and followed how to set the tile mask value based on each tile's neighbours since someone suggested the idea to me, but it didn't really cover the next step of connecting the integer value to the wall type needed.
Ah, the joys of random Internet blogs giving incomplete pictures of everything :)

If you see a value as a sum of power-of-two values (3 == 1 + 2,  98 = 64 + 32 + 2, etc), each power of two is a single bit (= a position in a number written in the binary number system), so you can easily test for such values using an 'and'  ((value & 2) == 2) or ((value & 2) == 0). In that way you can disect your value back to your 8 cases, in 8 if statements.

You can also check for combinations, eg the "right" vector occurs in 4, 16, and 32. You can test for that like

if ((value & 4) != 0 || (value & 16) != 0 || (value & 32) != 0)

or together

if ((value & (4+16+32)) != 0)

You may want to read bit arithmetic and bit masking at wikipedia.

An alternative direction is to ditch the bit masks, and invent a data description that better fits your needs in the rendering. That comes back to my previous suggestion, look at what simple code you want in rendering, then work your way back to what data values you want to have.

##### Share on other sites
You can also check for combinations, eg the "right" vector occurs in 4, 16, and 32

You kind of lost me here. Going clockwise from north east to north and so on, the numbers i have are: 1,2,4,16,128,64,32,8

So wouldn't a right wall piece be only the value of 16?

Like if you see this example:

http://i.imgur.com/E5tZhlN.png

All those corner pieces values are: 22, 23, 151, 183, 55 though i am missing some combinations when i draw it out. If i was to use the if statements to simplify it, what exactly am i checking for in these values to detect they all simply are the same visual tile ? The if(value & .. ) is bit confusing how i connect that to all the possible values.

Like say i have if((value & 2) != 0, am i simply checking to see if that given tile requires a wall piece along it's north edge? Since a north tile neighbour is + 2 to the value? Its kinda hard to connect the numbers to what the end result it.

##### Share on other sites

It seems you have very little idea about bits, numbers, and how they relate it seems.

I would need a few hours face-to-face at least to explain how numbers work, how binary numbers work, how you can convert back and forth between various number systems (such as decimal, binary, and perhaps hexadecimal), how binary numbers are related to bits, how computers use bits to store integers, the bit operations 'and', 'or', and 'xor', and how you can use them to manipulate integers as a collection of bits. From there we can discuss bitmasks and their uses. It is not terribly difficult, but it's a lot, and there are a few deep ideas in there that you need to grasp or you get nowhere.

In this single post, I have no chance whatsoever to explain all that to you :(

To get forward, I would suggest that you forget about bitmasks. It's too complicated to understand at this time. Instead make several variables in the tile, one for each thing that you must draw. Then in a function like SetTileMask (but not that one, as it has the wrong name), set the variables.

##### Share on other sites

It seems you have very little idea about bits, numbers, and how they relate it seems.

I would need a few hours face-to-face at least to explain how numbers work, how binary numbers work, how you can convert back and forth between various number systems (such as decimal, binary, and perhaps hexadecimal), how binary numbers are related to bits, how computers use bits to store integers, the bit operations 'and', 'or', and 'xor', and how you can use them to manipulate integers as a collection of bits. From there we can discuss bitmasks and their uses. It is not terribly difficult, but it's a lot, and there are a few deep ideas in there that you need to grasp or you get nowhere.

In this single post, I have no chance whatsoever to explain all that to you :(

To get forward, I would suggest that you forget about bitmasks. It's too complicated to understand at this time. Instead make several variables in the tile, one for each thing that you must draw. Then in a function like SetTileMask (but not that one, as it has the wrong name), set the variables.

Yeah it appears i need to learn this stuff. I don't want to take up all your time, so i'll go seek out a book on it.

Thanks for the advice though, it is much appreciated !