2d tile bitmasking

In my top-down game I allow players to place objects. Now I got to the part where I want to place roads, water, fences, basically things that need to “connect” seemlessly to each other while the player places/removed them. This is usually done using bitmasking.

Bitmasking requires me to build my own grid, and each element in that grid should know what it contains. So far so good, but I need to place several thing in my game that span across several elements in such a grid (A house might cover 10x12 grid-indices). So due to this I never created a grid and simply used unity colliders together with Mathf.Round to create a snapping effect for the player placing objects:

mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = new Vector2(Mathf.Round(mousePos.x - 0.5f), Mathf.Round(mousePos.y - 0.5f)); //offset since pivot is bottom left.

My question is, since I don’t use a grid, could I do bitmasking using raycast? Or would this be really performance heavy? i.e every time I place/remove for example a piece of road-tile, that tile checks 1 unit out in all 4 directions to see what is there by getting the object attached to the collider. Do you see any issues with this approach?

If I’m not making sense I can draw some things up to explain what I mean.

EDIT:
In the process of building a solution with my own logic using raycasts. I figure it shouldnt be that heavy on performance because its only called when I place/remove a tile. I’ll update this post with my results.

Okay so, some cool progress. I managed to get it working using raycast and it seems fine on performance. When I place a tile I cheack the 8 surrounding tiles, and then I also go to those 8 tiles and update those to fit the new formation.

If I have one complaint it would be the amount or repetetive code I had to write, and maybe I over-simplified things, but does anyone have any clue if this could be done without 48 if-statements? (48 different combinations of tiles/directions when using 8-bit). I can’t really find sample code for it, and my way works, but feel pretty sure that someone smarter than me can figure out a way to reduce these if-statements. Maybe that is what bitmasking does in the sample I linked in my OP, but couldn’t really understand it.

The start of my 48 if-statements:

if(southWest && southEast && northWest && northEast && west && south && east && north)
            {
                   _spriteRenderer.sprite = _tiles[47];
            }
            else if (!southWest && southEast && northWest && northEast && west && south && east && north)
            {
                    _spriteRenderer.sprite = _tiles[46];
            }
//...

Define flags like this:

[Flags]
public enum Directions
{
    East  = 1 << 0, // = 1
    West = 1 << 1, // = 2
    North   = 1 << 2, // = 4
    South   = 1 << 3, // = 8
    // ...
}

Use them like this:

var directions = (east ? Directions.East : 0) | (west ? Directions.West : 0)  | (north ? Directions.North : 0) | (south ? Directions.South : 0); //...
// Now directions contains number 0 - 15 for all 16 possible combinations (256 after you add remaining 4 directions)
_spriteRenderer.sprite = _tiles[directions];

Thanks, you code works, i.e if I print directions it prints all the directions the tile has a neighbor. But I don’t really understand the code, so having a hard time making it fit my solution. For example,

[SerializeField]
private List<Sprite> _tiles;
//...
_spriteRenderer.sprite = _tiles[directions];

The above code wont work, can’t convert Directions to int. Which makes sense. Probably missing a few steps there, maybe you thought those steps were obvious so you left them out, would be great if you could explain some of it :slight_smile: Basically, how can I use the enum Direction to set the correct sprite? It needs to represent an int somehow.

You can cast to int.

Yup… Wast just coming here to edit my post. Then I guess I just need to map the values to the corresponding index in the array somehow, like (direction)255=(index)47. Thanks!

Yeah, as methos5k mentioned, just cast Directions to int like this:

_spriteRenderer.sprite = _tiles[(int)directions];

Directions flags are defined as power of two numbers. 1 for East, 2 for West, 4 for North and so on. Actual flags for each tile is computed as sum of all his neighbours. So if there are for example tiles at the east and at the north, total directions value will be 1 + 4 = 5. Now you just have to assign _tiles[5] (or more readable _tiles[(int)(Directions.East | Directions.North)]) to appropriate sprite and you are done.

Hmm, not really though? Because ‘directions’ will give me values between 0-255, so I cant use that value as the index, I will have to map the values I get in directions to an int between 0-47, that I then can use in the array. See here under “Tile order”

for example, If I place a tile that is surrounded on all 8 directions, then ‘directions’ will return ‘255’

Ok, hold on, I will try to adjust it to your needs.

There’s even a spot on that link that shows the values they saved in a list. If yours is like that, you should use it (or something similar if they differ) :slight_smile:

*If I understood that page well enough =)

Yep, that is what I’m doing now :slight_smile:

:slight_smile:

Ok, here is version same as in the article.

Enum:

[Flags]
public enum Directions
{
    NorthWest  = 1 << 0,
    North = 1 << 1,
    NorthEast   = 1 << 2,
    West   = 1 << 3,
    East  = 1 << 4,
    SouthWest = 1 << 5,
    South   = 1 << 6,
    SouthEast   = 1 << 7,
}

and the method for converting your values to Directions

private static Directions CalculateTileFlags(bool east, bool west, bool north, bool south, bool northWest, bool northEast, bool southWest, bool southEast)
{
    var directions = (east ? Directions.East : 0) | (west ? Directions.West : 0)  | (north ? Directions.North : 0) | (south ? Directions.South : 0);
    directions |= ((north && west) && northWest) ? Directions.NorthWest : 0;
    directions |= ((north && east) && northEast) ? Directions.NorthEast : 0;
    directions |= ((south && west) && southWest) ? Directions.SouthWest : 0;
    directions |= ((south && east) && southEast) ? Directions.SouthEast : 0;
    return directions;
}

I spent ten minutes looking for one missing case, but there is actually 47 values, not 48. You can use mapping from article to map 0-255 to 0-46.

1 Like

Nice solution :slight_smile: Thanks a lot for your effort!

You are welcome.

Seems we dont actually get the same values as the guy in that guide so will have to create my own bindings, but still :slight_smile: Think it should work.

Edit: Nvm…

There are same values, you can use this code to verify it:

bool east, west, north, south, northWest, northEast, southWest, southEast;
var output = new HashSet<Directions>();

for (var i = 0; i <= 255; i++)
{
    var directions = (Directions) i;
    east = (directions & Directions.East) == Directions.East;
    west = (directions & Directions.West) == Directions.West;
    south = (directions & Directions.South) == Directions.South;
    north = (directions & Directions.North) == Directions.North;
    northEast = (directions & Directions.NorthEast) == Directions.NorthEast;
    northWest = (directions & Directions.NorthWest) == Directions.NorthWest;
    southEast = (directions & Directions.SouthEast) == Directions.SouthEast;
    southWest = (directions & Directions.SouthWest) == Directions.SouthWest;

    output.Add(CalculateTileFlags(east, west, north, south, northWest, northEast, southWest, southEast));
}

var counter = 0;

Console.WriteLine("Total items {0}", output.Count);

foreach (var item in output)
{
    Console.WriteLine("Item {0} directions {1}", counter, (int)item);
    counter++;
}

outputs

Total items 47
Item 0 directions 0
Item 1 directions 2
Item 2 directions 8
Item 3 directions 10
Item 4 directions 11
Item 5 directions 16
Item 6 directions 18
Item 7 directions 22
Item 8 directions 24
Item 9 directions 26
Item 10 directions 27
Item 11 directions 30
Item 12 directions 31
Item 13 directions 64
Item 14 directions 66
Item 15 directions 72
Item 16 directions 74
Item 17 directions 75
Item 18 directions 80
Item 19 directions 82
Item 20 directions 86
Item 21 directions 88
Item 22 directions 90
Item 23 directions 91
Item 24 directions 94
Item 25 directions 95
Item 26 directions 104
Item 27 directions 106
Item 28 directions 107
Item 29 directions 120
Item 30 directions 122
Item 31 directions 123
Item 32 directions 126
Item 33 directions 127
Item 34 directions 208
Item 35 directions 210
Item 36 directions 214
Item 37 directions 216
Item 38 directions 218
Item 39 directions 219
Item 40 directions 222
Item 41 directions 223
Item 42 directions 248
Item 43 directions 250
Item 44 directions 251
Item 45 directions 254
Item 46 directions 255
2 Likes