I’ve been trying to implement an auto tile feature in code instead of using the Unity in-house features,
for studying purposes.
I’ve been using this tutorial How to Use Tile Bitmasking to Auto-Tile Your Level Layouts | Envato Tuts+
and even with ChatGPT, I’m not getting any closer to the goal. I’ve spent several days trying to solve this problem.
I have some misunderstanding about how to make this work programmatically.
I have these methods in my Furniture class. It is meant to assign and calculate bitmask values.
The values are consistent enough, meaning it is possible that I could assign the bitmask values each a sprite, and it does work.
But I want the values to be the same as in the tutorial.
private List<Furniture> furnList = new List<Furniture>();
public enum Direction
{
North,
East,
West,
South,
}
// This method changes the bitmask value of the current
// furn obj, NOT the neighbor furn obj that its looking
// for by using the HasNeighbor method..
public void CalculateBitmaskValue()
{
int maskValue = 0;
this.Bitmask = 0;
if (HasNeighbor(Direction.South))
{
maskValue |= 1; // The current tile is the N neighbor of the S tile.
Debug.Log($"Tile ({tile.X}, {tile.Z}) has bitmask value: {maskValue} after checking {Direction.South}");
}
if (HasNeighbor(Direction.East))
{
maskValue |= 2; // The current tile is the W neighbor of the E tile.
Debug.Log($"Tile ({tile.X}, {tile.Z}) has bitmask value: {maskValue} after checking {Direction.East}");
}
if (HasNeighbor(Direction.West))
{
maskValue |= 4; // The current tile is the E neighbor of the W tile.
Debug.Log($"Tile ({tile.X}, {tile.Z}) has bitmask value: {maskValue} after checking {Direction.West}");
}
if (HasNeighbor(Direction.North))
{
maskValue |= 8; // The current tile is the S neighbor of the N tile.
Debug.Log($"Tile ({tile.X}, {tile.Z}) has bitmask value: {maskValue} after checking {Direction.North}");
}
this.Bitmask = maskValue;
}
private bool HasNeighbor(Direction direction)
{
int deltaX = 0, deltaZ = 0;
switch (direction)
{
case Direction.North:
deltaZ = 1;
break;
case Direction.West:
deltaX = -1;
break;
case Direction.East:
deltaX = 1;
break;
case Direction.South:
deltaZ = -1;
break;
default:
throw new ArgumentOutOfRangeException(nameof(direction), "Unknown direction");
}
Tile neighboringTile = World.current.GetTileAt(tile.X + deltaX, tile.Z + deltaZ);
if (neighboringTile == null)
{
return false; // No neighboring tile in this direction.
}
return neighboringTile.furniture != null && neighboringTile.furniture.objectType == "Stockpile";
}
public void UpdateBitmasksForTileAndNeighbors(Tile tile)
{
// Add the current furniture to be calculated
furnList.Add(tile.furniture);
// Calculate bitmask for neighboring furniture
foreach (Direction dir in Enum.GetValues(typeof(Direction)))
{
Tile neighboringTile = GetNeighboringTile(tile, dir);
if (neighboringTile != null && neighboringTile.furniture != null && neighboringTile.furniture.objectType == "Stockpile")
{
// Add the neighboring furniture to be calculated
furnList.Add(neighboringTile.furniture);
}
}
foreach (var item in furnList)
{
item.CalculateBitmaskValue();
}
}
public Tile GetNeighboringTile(Tile tile, Direction direction)
{
int deltaX = 0, deltaZ = 0;
switch (direction)
{
case Direction.North:
deltaZ = 1;
break;
case Direction.West:
deltaX = -1;
break;
case Direction.East:
deltaX = 1;
break;
case Direction.South:
deltaZ = -1;
break;
default:
Debug.LogWarning("Unhandled direction: " + direction);
return null;
}
return World.current.GetTileAt(tile.X + deltaX, tile.Z + deltaZ);
}
static public Furniture PlaceInstance(Furniture proto, Tile tile)
{
if (proto.funcPositionValidation(tile) == false)
{
return null;
}
Furniture obj = proto.Clone();
//Furniture obj = new Furniture(proto);
obj.tile = tile;
// FIXME: This assumes we are 1x1!
if (tile.PlaceFurniture(obj) == false)
{
return null;
}
if (obj.linksToNeighbour)
{
Tile t;
int x = tile.X;
int z = tile.Z;
t = World.current.GetTileAt(x, z + 1); // Northern neighbour
if (t != null && t.furniture != null && t.furniture.cbOnChanged != null && t.furniture.objectType == obj.objectType)
{
t.furniture.cbOnChanged(t.furniture);
}
t = World.current.GetTileAt(x - 1, z); // Western neighbour
if (t != null && t.furniture != null && t.furniture.cbOnChanged != null && t.furniture.objectType == obj.objectType)
{
t.furniture.cbOnChanged(t.furniture);
}
t = World.current.GetTileAt(x + 1, z); // Eastern neighbour
if (t != null && t.furniture != null && t.furniture.cbOnChanged != null && t.furniture.objectType == obj.objectType)
{
t.furniture.cbOnChanged(t.furniture);
}
t = World.current.GetTileAt(x, z - 1); // Southern neighbour
if (t != null && t.furniture != null && t.furniture.cbOnChanged != null && t.furniture.objectType == obj.objectType)
{
t.furniture.cbOnChanged(t.furniture);
}
}
return obj;
}
The callbacks are being used to send the furniture instance to the FurnitureSpriteController script
each time a new furniture is made, only controller scripts inherit Monobehaviour in this case.
In the FurnitureSpriteController script, I’m calling the UpdateBitmasksForTileAndNeighbors that is connected to the Furniture instance so all the furniture objects can be updated.
At the moment, I’m only dealing with the four cardinal directions to keep things simple, and trying to make sure I get the same values.

These are the values that I get.
0 1 0
0 10 4
0 0 0
N neighbour has a bitmask of 1
W neighbour has a bitmask of 4
Middle furniture object ends up with 10, it should really end up with 5, which suggests that there is a huge logical error that I’m making,
I’m very much misunderstanding the logic and how to implement it. There are also no tutorials that I can find, on this topic.
The project I’m working on is based on Project Porcupine, a tutorial series by quill18. I also realise that the DRY principle is not really being adhered to in these methods.
Any help or suggestions will be appreciated.