Example loading of Tiled TMX map in D language
module tiledMap; | |
/* | |
Tiled TMX map loader example in D | |
Copyright (c) 2014, gdm85 - https://github.com/gdm85 | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions are met: | |
1. Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright | |
notice, this list of conditions and the following disclaimer in the | |
documentation and/or other materials provided with the distribution. | |
3. Neither the name of the author nor the names of its contributors may | |
be used to endorse or promote products derived from this software without | |
specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY gdm85 ''AS IS'' AND ANY EXPRESS OR IMPLIED | |
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
IN NO EVENT SHALL gdm85 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |
OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
/* | |
This example uses kxml for XML parsing of a Tiled TMX map and DSFML for the graphics side. | |
It is very simple (supports only 1 tileset and 1 layer, fixed tile size etc) but you can | |
expand it second your needs. | |
NOTE: tileset must be 1 tile wide, thus extending only in height. see also https://gist.github.com/gdm85/9924314 | |
You can find them at: | |
* https://github.com/opticron/kxml | |
* https://github.com/Jebbs/DSFML/ | |
Many thanks to the authors of these libraries and also to author of Tiled: | |
* https://github.com/bjorn/tiled/ | |
NOTE: the C binding of DSFML can be troublesome to compile, you need SFML 2 and then | |
DSFML-C in order to get it working. But it's worth it ;) | |
*/ | |
import kxml.xml; | |
import std.conv; | |
import std.path; | |
import std.string; | |
import std.zlib; | |
import std.stdio; | |
import std.base64; | |
import std.bitmanip; | |
import std.system; | |
import dsfml.graphics; | |
immutable uint FLIPPED_HORIZONTALLY_FLAG = 0x80000000; | |
immutable uint FLIPPED_VERTICALLY_FLAG = 0x40000000; | |
immutable uint FLIPPED_DIAGONALLY_FLAG = 0x20000000; | |
immutable uint TILE_SZ = 32; | |
immutable uint SCREEN_WIDTH = 800; | |
immutable uint SCREEN_HEIGHT = 600; | |
class TiledMap { | |
@property | |
{ | |
uint mapWidth() const | |
{ | |
return cast(uint)map.length; | |
} | |
} | |
@property | |
{ | |
uint mapHeight() const | |
{ | |
// empty map not supported, thus it's always valid | |
return cast(uint)map[0].length; | |
} | |
} | |
@property | |
{ | |
uint tilesCount() const | |
{ | |
return tileSet.getSize().y / TILE_SZ; | |
} | |
} | |
private { | |
int m_mapWidth; | |
int m_mapHeight; | |
int firstGid; | |
Texture tileSet; | |
Sprite[][] map; | |
} | |
this(string filename) { | |
string xmlSource = cast(string)std.file.read(filename); | |
// build DOM tree | |
auto doc = xmlSource.readDocument(); | |
XmlNode[] searchList = doc.parseXPath("map"); | |
// get number of tiles in map (w/h) | |
m_mapWidth = to!int(searchList[0].getAttribute("width")); | |
m_mapHeight = to!int(searchList[0].getAttribute("height")); | |
auto tileWidth = to!int(searchList[0].getAttribute("tilewidth")); | |
auto tileHeight = to!int(searchList[0].getAttribute("tileheight")); | |
if (tileWidth != TILE_SZ || tileHeight != TILE_SZ) | |
throw new Exception("Tile size mismatch"); | |
searchList = doc.parseXPath("map/tileset"); | |
firstGid = to!int(searchList[0].getAttribute("firstgid")); | |
tileWidth = to!int(searchList[0].getAttribute("tilewidth")); | |
tileHeight = to!int(searchList[0].getAttribute("tileheight")); | |
if (tileWidth != TILE_SZ || tileHeight != TILE_SZ) | |
throw new Exception("Tile size mismatch"); | |
searchList = doc.parseXPath("map/tileset/image"); | |
auto tileSetSource = searchList[0].getAttribute("source"); | |
auto tileSetWidth = to!int(searchList[0].getAttribute("width")); | |
if (tileSetWidth != TILE_SZ) | |
throw new Exception("Tile size mismatch (tileset)"); | |
auto tileSetHeight = to!int(searchList[0].getAttribute("height")); | |
tileSet = new Texture(); | |
if (!tileSet.loadFromFile(buildPath(dirName(filename), baseName(tileSetSource)))) | |
throw new Exception("Cannot load tileset source"); | |
if (tileSet.getSize().x != TILE_SZ || tileSet.getSize().y != tileSetHeight) | |
throw new Exception("Tileset size mismatch"); | |
// get layer | |
searchList = doc.parseXPath("map/layer"); | |
if (searchList.length != 1) | |
throw new Exception("Invalid number of layers in map"); | |
auto layerWidth = to!int(searchList[0].getAttribute("width")); | |
auto layerHeight = to!int(searchList[0].getAttribute("height")); | |
if (layerWidth != m_mapWidth || layerHeight != m_mapHeight) | |
throw new Exception("Invalid layer size"); | |
searchList = doc.parseXPath("map/layer/data"); | |
auto dataEncoding = searchList[0].getAttribute("encoding"); | |
auto dataCompression = searchList[0].getAttribute("compression"); | |
if (dataEncoding != "base64" || dataCompression != "zlib") | |
throw new Exception("Invalid data encoding/compression"); | |
// this single call will use large memory buffers that will be | |
// opportunistically garbage collected | |
map = createSprites(decodeAsUints(cast(ubyte[])uncompress(Base64.decode(searchList[0].getInnerXML().strip())))); | |
} | |
private { | |
uint[] decodeAsUints(ubyte[] buffer) { | |
if (buffer.length != m_mapWidth * m_mapHeight * 4) | |
throw new Exception("Data mismatches map size"); | |
uint[] result = new uint[buffer.length / 4]; | |
for(int i=0;i<result.length;i++) { | |
auto slice = buffer[(i*4)..((i+1)*4)]; | |
uint gid = slice.read!(uint, Endian.littleEndian); | |
result[i] = gid; | |
} | |
return result; | |
} | |
Sprite[][] createSprites(uint[] mapData) { | |
Sprite[][] result = new Sprite[][m_mapWidth]; | |
for(int x=0;x<m_mapWidth;x++) { | |
result[x] = new Sprite[m_mapHeight]; | |
for(int y=0;y<m_mapHeight;y++) { | |
auto global_tile_id = mapData[x + y * m_mapWidth]; | |
// Read out the flags | |
bool flipped_horizontally = (global_tile_id & FLIPPED_HORIZONTALLY_FLAG) != 0; | |
bool flipped_vertically = (global_tile_id & FLIPPED_VERTICALLY_FLAG) != 0; | |
bool flipped_diagonally = (global_tile_id & FLIPPED_DIAGONALLY_FLAG) != 0; | |
// clear tail bits | |
global_tile_id &= ~(FLIPPED_HORIZONTALLY_FLAG | | |
FLIPPED_VERTICALLY_FLAG | | |
FLIPPED_DIAGONALLY_FLAG); | |
if (global_tile_id == 0) | |
continue; | |
global_tile_id -= firstGid; | |
if (global_tile_id > tilesCount()) | |
throw new Exception("Tile overflow: " ~ to!string(global_tile_id) ~ " >= " ~ to!string(tilesCount())); | |
float angle = 0; | |
if (flipped_horizontally) | |
angle += 360; | |
if (flipped_vertically) | |
angle += 180; | |
//TODO: diagonal flip using transformations | |
if (flipped_diagonally) | |
throw new Exception("Diagonal flip not implemented"); | |
if (angle > 360) | |
angle = 360; | |
auto sprite = new Sprite(tileSet); | |
sprite.textureRect = IntRect(0, global_tile_id * TILE_SZ, TILE_SZ, TILE_SZ); | |
sprite.rotation = angle; | |
result[x][y] = sprite; | |
} | |
} | |
return result; | |
} | |
void drawTile(RenderWindow window, int mapX, int mapY, int screenX, int screenY) { | |
auto sprite = map[mapX][mapY]; | |
if (sprite is null) | |
return; | |
// it's safe to overwrite this position | |
sprite.position = Vector2f(screenX * TILE_SZ, screenY * TILE_SZ); | |
window.draw(sprite); | |
} | |
} | |
void drawOn(RenderWindow window, int viewportCenterX, int viewportCenterY) { | |
// clear window | |
window.clear(); | |
auto viewportTilesWidth = SCREEN_WIDTH / TILE_SZ + 2; | |
auto viewportTilesHeight = SCREEN_HEIGHT / TILE_SZ; | |
// create centered viewport | |
auto viewportX = viewportCenterX - viewportTilesWidth / 2; | |
auto viewportY = viewportCenterY - viewportTilesHeight / 2; | |
auto viewportX2 = viewportCenterX + viewportTilesWidth / 2; | |
auto viewportY2 = viewportCenterY + viewportTilesHeight / 2; | |
// check for corner | |
if (viewportX2 > m_mapWidth) { | |
viewportX2 = m_mapWidth; | |
viewportX = viewportX2 - viewportTilesWidth; | |
} | |
if (viewportY2 > m_mapHeight) { | |
viewportY2 = m_mapHeight; | |
viewportY = viewportY2 - viewportTilesHeight; | |
} | |
// make sure no negative viewport coordinates exist | |
if (viewportX < 0) | |
viewportX = 0; | |
if (viewportY < 0) | |
viewportY = 0; | |
// here render the map! | |
for(int x=0;x < viewportX2 - viewportX;x++) { | |
for(int y=0;y < viewportY2 - viewportY;y++) { | |
drawTile(window, viewportX + x, viewportY + y, x, y); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment