Here’s my implementation of a Tiled editor (.TMX) loader. It supports multiple tilesets per layer by creating one big master tileset out of all used tilesets. I’m not sure this is the most elegant implementation but it works. Maybe someone will find this useful.
package
{
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.Dictionary;
import net.flashpunk.Entity;
import net.flashpunk.graphics.Image;
import net.flashpunk.graphics.Tilemap;
import net.flashpunk.World;
import net.flashpunk.FP;
public class TmxWorld extends World
{
private var numTiles:uint;
private var masterTileset:BitmapData;
private var tilesets:Array;
private var data:XML;
public function TmxWorld(rawData:Class)
{
data = FP.getXML(rawData);
// Create master tileset of all used tilesets
RegisterTilesets(data.tileset);
BuildMasterTileset();
// Create the rest of your level here. Use CreateLayerEntity to make the tilemaps.
}
public function CreateLayerEntity(name:String, layer:int = 0):Entity
{
var layerData:String = "";
var tilemap:Tilemap = new Tilemap(masterTileset, data.@width * data.@tilewidth, data.@height * data.@tileheight, data.@tilewidth, data.@tileheight);
for each (var e:XML in data.layer)
{
if (name == e.@name)
{
layerData = e.data;
}
}
if (layerData.length)
{
tilemap.loadFromString(layerData);
}
var ent:Entity = new Entity(0, 0, tilemap);
ent.layer = layer;
return ent;
}
public function RegisterTilesets(data:XMLList):void
{
if (!tilesets)
{
tilesets = new Array();
numTiles = 1; // Tiled offset
}
for each(var e:XML in data)
{
var b:BitmapData = FP.getBitmap(Assets.GetTilesetByName(e.@name))
tilesets.push(b);
numTiles += (b.width / 16) * (b.height / 16);
}
}
public function BuildMasterTileset():void
{
var masterWidth:uint = 16 * 8; // Modify this if you use tiles that are not 16x16;
var masterHeight:uint = Math.ceil(numTiles / (masterWidth/16)) * 16;
var masterWidthTiles:uint = masterWidth / 16;
var masterHeightTiles:uint = masterHeight / 16;
masterTileset = new BitmapData(masterWidth, masterHeight, true, 0x00000000);
var destinationPoint:Point = new Point();
var sourceRectangle:Rectangle = new Rectangle(0, 0, 16, 16);
var masterCounter:uint = 1; // Tiled offset again. Leave the first one blank/black.
for each(var e:BitmapData in tilesets)
{
var sourceWidthTiles:uint = (e.width / 16);
var sourceHeightTiles:uint = (e.height / 16);
var sourceTiles:uint = sourceWidthTiles * sourceHeightTiles;
for (var i:uint = 0; i < sourceTiles; i++)
{
sourceRectangle.x = (uint)(i % sourceWidthTiles) * 16;
sourceRectangle.y = (uint)(i / sourceWidthTiles) * 16;
destinationPoint.x = (uint)(masterCounter % masterWidthTiles) * 16;
destinationPoint.y = (uint)(masterCounter / masterWidthTiles) * 16;
masterTileset.copyPixels(e, sourceRectangle, destinationPoint);
masterCounter++;
}
}
}
}
}
// Assets
package
{
public class Assets
{
[...]
// Tilesets
[Embed(source = "../assets/tilesets/basic.png")] public static const IMG_BASIC:Class;
[Embed(source = "../assets/tilesets/solid.png")] public static const IMG_GRID:Class;
[Embed(source="../assets/tilesets/crypt.png")] public static const IMG_CRYPT:Class;
// Map tileset names used in Tiled to their classes
public static function GetTilesetByName(name:String):Class
{
if (name == "basic") return IMG_BASIC;
if (name == "crypt") return IMG_CRYPT;
if (name == "solid") return IMG_GRID;
return Assets.IMG_BASIC;
}
}
}