OpenFL - Creating the Map

on tutorials rpg

This is the first tutorial in the RPG series. These tutorials will go through the process of creating a role-playing game using OpenFL.

What is a tile-based game?

A tile-based game is a game which utilizes tiles as a building block. It's a great way to speed up development by making it easier to create levels and simplifies gameplay logic like enemy artificial intelligence.

Drawing tiles

The usual way of making a game is to draw every single rock, door, tree and whatever else is needed for every level. Games usually have plenty of levels and it is not practical to draw a new tree or rock every time it is needed. So it makes sense to draw a couple trees and reuse them throughout your game.

This works well except rocks, trees, doors come in many different shapes and sizes. If they can all be within a certain size it will greatly simplify collisions, map creation, drawing and lots of other things.

That's the gist of tile-based games. Put everything inside a tile or a series of tiles. Our tile size is going to be 40 pixels, 40 width and 40 height. I picked this because it scales well and looks good on mobile and desktop.

We can draw all of our tiles in multiple files but many tiles extend past one tile, like a big rock will probably take four tiles. Small rocks will take one tile. So it is easier drawing all of our tiles in one big image this is called a Tilesheet.

posts/creating_the_map/tilesheet.png

Now we have a tilesheet how do we construct an entire world out of it? By numbering each tile it can be easily identified in game. Tiles start from zero and increment up.

Creating a map of numbers

The easiest way to place a large amount of tiles is to create a multi-dimensional array of integers. Inside the array goes the tile ids. Tile 18 is grass and 24 is sand.

var map:Array<Array<Int>> = new Array<Array<Int>>();
map[0] = [18, 18, 18, 18, 18, 18, 18, 18];
map[1] = [18, 18, 18, 18, 18, 18, 18, 18];
map[2] = [18, 18, 18, 18, 24, 24, 24, 18];
map[3] = [18, 18, 18, 18, 24, 24, 24, 18];
map[4] = [18, 18, 18, 18, 18, 18, 18, 18];
map[5] = [18, 18, 18, 18, 18, 18, 18, 18];

Defining tiles

We have a tilesheet and a multi-dimensional array describing our map. The next step is to transform our tilesheet into a more useful format in code.

_tileDatas = new Array<TileData>();
this.tilesheetData = tilesheetData;

var totalXTiles:Int = Math.floor(tilesheetData.width / TILE_SIZE);
var totalYTiles:Int = Math.floor(tilesheetData.height / TILE_SIZE);

for(y in 0...totalYTiles){
  for(x in 0...totalXTiles){
    var tileData:TileData = new TileData();
    tileData.id = _tileDatas.length;

    tileData.bitmapData = new BitmapData(TILE_SIZE, TILE_SIZE);

    var rect:Rectangle = new Rectangle(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
    var point:Point = new Point(0, 0);

    tileData.bitmapData.copyPixels(tilesheetData, rect, point);

    _tileDatas.push(tileData);
  }
}

This will take tilesheetData (our big tilesheet image) and split it up into smaller images. It then places it into _tileDatas for easy access later. Remember to take a look at the OpenFL Docs if are not familiar with any of these classes.

TileData is a class which holds all the data about a tile. Right now it only holds the id and bitmapData. But it is easily extensible to add new attributes like isWalkable.

Placing tiles

I created a TileLayer class which creates and holds all the tiles for a tile map.

bottomLayer = new TileLayer(tilesheet);
bottomLayer.setMap(map);

addChild(bottomLayer);

The bottomLayer instance needs a reference to the tilesheet so it can create the proper tiles. The setMap method will then place all the tiles in the multi-array.

public function setMap(map:Array<Array<Int>>) {
    _tiles = new Array<Array<Tile>>();

    for(y in 0...map.length){
        _tiles[y] = new Array<Tile>();

        for(x in 0...map[0].length){
            var tile:Tile = new Tile();
            tile.bitmap.x = x * Tilesheet.TILE_SIZE;
            tile.bitmap.y = y * Tilesheet.TILE_SIZE;

            _tiles[y][x] = tile;

            setTile(x, y, map[y][x]);

            addChild(tile.bitmap);
        }
    }
}

You should then see something like this.

posts/creating_the_map/single_layer_map.png

It isn't much but it is a first step.

Layers

Creating multiple tile maps and layering them on top of each other is a great way to reduce tile duplication and to place tiles above characters. For example how would you place a rock on sand and grass? You can draw it twice for both backgrounds or you can create a rock with a transparent background and place it on a higher layer.

posts/creating_the_map/layers.png

Lets first create a new class MapData to contain all our map information.

class MapData {

    public var name:String;

    public var bottomMap:Array<Array<Int>>;
    public var middleMap:Array<Array<Int>>;
    public var topMap:Array<Array<Int>>;

    public function new(){

    }

}

Lets add two more layers. The order in which addChild is called is important. topLayer has to be added last to ensure it is on top.

private function loadMap(mapData:MapData) {
    bottomLayer = new TileLayer(tilesheet);
    bottomLayer.setMap(mapData.bottomMap);

    middleLayer = new TileLayer(tilesheet);
    middleLayer.setMap(mapData.middleMap);

    topLayer = new TileLayer(tilesheet);
    topLayer.setMap(mapData.topMap);

    addChild(bottomLayer);
    addChild(middleLayer);
    addChild(topLayer);
}

And you will get something like this:

posts/creating_the_map/layered_map.png

Grabbing the source

You can grab the source code for all the tutorials here.

Next tutorial: Creating the Hero