A procedurally generated city (2d)-Part 4 : Tiles bit masking

Now that we are able to lay out our nodes, we should give it a city-like feel by adding the correct graphics to it. For this, we are going to be using bit masking.

Bit masking is greatly explained here: http://www.angryfishstudios.com/2011/04/adventures-in-bitmasking/. It is basically a process to offset the Id of each one of your tiles.

For this, we need to load our tile information into our game using the CityTiles class. But before that, we need to define all the types of graphical tiles that we are going to be using. We will be using the following:

  • roads
  • blocks
  • 2 different kind of buildings
  • 2 different kind of roofs
public enum CellType{  
   Block,
   Road,
   Building1,
   Building2,
   Roof1,
   Roof2;

   public byte factor;

   CellType(){
      factor = (byte)ordinal();
   }

   public static EnumSet<CellType> Buildings = EnumSet.of(CellType.Building1, CellType.Building2);
   public static EnumSet<CellType> Roofs = EnumSet.of(CellType.Roof1, CellType.Roof2);
}

If you read the tutorial / explanation about about bit masking, the factor above represents that "center value" for each celltype, which in practical terms means the offset in bits for the give cell type.

Now, let's start building our CityTiles class:

public class CityTiles {

    private final CellType[] roofs;
    private final CellType[] buildings;
    private TiledMapTileSet groundMasks;
    private TiledMapTileSet buildingsMasks;


    // tile masks bits
    private int N ;
    private int E ;
    private int S ;
    private int W ;
    private int C ;

    private static final byte ROAD_FACTOR = CellType.Road.factor;

    TiledMap map;

    public CityTiles(TiledMap map, CellType[] buildings, CellType[] roofs){
        groundMasks = new TiledMapTileSet();
        groundMasks.setName("groundMasks");
        this.map = map;

        int count = CellType.values().length;
        N = (int)Math.pow(count, 0);
        E = (int)Math.pow(count, 1);
        S = (int)Math.pow(count, 2);
        W = (int)Math.pow(count, 3);
        C = (int)Math.pow(count, 4);

        this.buildings = buildings;
        this.roofs = roofs;
    }
}

Then we will need to assign the actual Ids to each tile:

private void createTilesets() {  
  Array<Texture> textures = new Array<Texture>();
  Texture tilesTexture = new Texture("citytileset.png");
  textures.add(tilesTexture);
  map.setOwnedResources(textures);
  TextureRegion[][] tiles = TextureRegion.split(tilesTexture, 
  32, 32);
}

These lines allows us to dispose the loaded texture(s?) used by our tilemap once the tilemap is disposed.

We will need to assign a texture region for each tile (you should be aware of the position of each of your tiles in your tileset), in my case, the terrain tiles are next to each other to easier see how to arrange them

  StaticTiledMapTile topLeftBlock = new StaticTiledMapTile(tiles[][]);
  //... load all of them, depending on the type
    createBlockTiles(topLeftBlock, topBlock, topRightBlock, etc..);

The following is the tricky part, you need to realize which tiles combinations are possible within your game, in this example, we will consider only the parts required for the "block" type cells, omitting the roads and buildings (they follow the same process though).

Consider the following example:
Block adyacent tiles

Our blocks can have roads, buildings and roofs next to them. We need to consider each on of them.
Here's the code to cover above sample

  private void createBlockTiles(StaticTiledMapTile topLeft, top, topRight, left, center, right, bottomLeft, bottomRight){

    int NORTH_BLOCK = N * CellType.Block.factor;
    int SOUTH_BLOCK = S * CellType.Block.factor;
    int EAST_BLOCK = E * CellType.Block.factor;
    int WEST_BLOCK = W * CellType.Block.factor;

    final int NORTH_ROAD = N * CellType.Road.factor;
    final int EAST_ROAD = E * CellType.Road.factor;
    final int SOUTH_ROAD = S * CellType.Road.factor;
    final int WEST_ROAD = W * CellType.Road.factor;

    int val = C * CellType.Block.factor;

    //normal cases, when not on screen border
    groundMasks.putTile(val + SOUTH_BLOCK + EAST_BLOCK + WEST_BLOCK + NORTH_ROAD, top);
    groundMasks.putTile(val + WEST_BLOCK + NORTH_BLOCK + SOUTH_BLOCK + EAST_ROAD, right);
    groundMasks.putTile(val + NORTH_BLOCK + WEST_BLOCK + EAST_BLOCK + SOUTH_ROAD, bottom);
    groundMasks.putTile(val + EAST_BLOCK + NORTH_BLOCK + SOUTH_BLOCK + WEST_ROAD, left);
    groundMasks.putTile(val + SOUTH_BLOCK + EAST_BLOCK + WEST_BLOCK + NORTH_BLOCK, center);
    groundMasks.putTile(val + NORTH_BLOCK + WEST_BLOCK + SOUTH_ROAD + EAST_ROAD, bottomRight);
    groundMasks.putTile(val + NORTH_BLOCK + EAST_BLOCK + SOUTH_ROAD + WEST_ROAD, bottomLeft);
    groundMasks.putTile(val + SOUTH_BLOCK + WEST_BLOCK + NORTH_ROAD + EAST_ROAD, topRight);
    groundMasks.putTile(val + SOUTH_BLOCK + EAST_BLOCK + NORTH_ROAD + WEST_ROAD, topLeft);

        // buildings will only ever be located on top, left or right from our blocks. Never below, becase we will have roofs rather than buildings below or top side of the block
    for (int i = 0; i < buildings.length; i++) {
        CellType building = buildings[i];
        int NORTH_BLD = N * building.factor;
        int EAST_BLD = E * building.factor;
        int WEST_BLD = W * building.factor;
        groundMasks.putTile(val + WEST_BLD + NORTH_BLOCK + SOUTH_BLOCK + EAST_ROAD, right);
        groundMasks.putTile(val + NORTH_BLD + WEST_BLOCK + EAST_BLOCK + SOUTH_ROAD, bottom);
        groundMasks.putTile(val + EAST_BLD + NORTH_BLOCK + SOUTH_BLOCK + WEST_ROAD, left);
        groundMasks.putTile(val + NORTH_BLD + EAST_BLOCK + SOUTH_BLOCK + WEST_BLOCK, center);
        groundMasks.putTile(val + NORTH_BLOCK + EAST_BLOCK + SOUTH_BLOCK + WEST_BLD, center);
        groundMasks.putTile(val + NORTH_BLOCK + EAST_BLD + SOUTH_BLOCK + WEST_BLOCK, center);
    }

    // roofs on the on other hand, can be only be below, left and right because of the same reason, buildings will be next to the bottom part of our block
    for (CellType roof : CellType.Roofs) {
        int SOUTH_ROOF = S * roof.factor;
        int WEST_ROOF = W * roof.factor;
        int EAST_ROOF = E * roof.factor;

        groundMasks.putTile(val + SOUTH_ROOF + EAST_BLOCK + WEST_BLOCK + NORTH_ROAD, top);
        groundMasks.putTile(val + WEST_ROOF + NORTH_BLOCK + SOUTH_BLOCK + EAST_ROAD, right);
        groundMasks.putTile(val + EAST_ROOF + NORTH_BLOCK + SOUTH_BLOCK + WEST_ROAD, left);

        groundMasks.putTile(val + NORTH_BLOCK + EAST_BLOCK + SOUTH_BLOCK + WEST_ROOF, center);
        groundMasks.putTile(val + NORTH_BLOCK + EAST_ROOF + SOUTH_BLOCK + WEST_BLOCK, center);
    }
}

This seem quite complicated, and it kind of is until you have made it at least once, once you get the grasp of how to auto-assign the Ids according to their adjacent cells, it is quite simple.

This is the last post on the procedural city generation topic, this should set you in the right path. Adding more things, like grass areas (parks, parking lots, etc) is just a matter of masking aditional tiles, as well as adding more conditions on the city generation (maybe, after generating the layout, you can have a method populate, which could add doors and windows to your buildings).

Here's a screenshot of the current state of my city generator (where now I also add grass / parks):
Current state of city generator

comments powered by Disqus