Tile-Engine mit XML und D

In diesem Beitrag werde ich zeigen, wie im letzten Artikel schon angedeutet, wie man mit D und XML eine kleine Tile-Engine bastelt um eine auf Tiles basierende Spielkarte zu erstellen.
Ich werde hierbei mit Tiled arbeiten (Download) um daraus mithilfe dieses Tilesets (Achtung groß)

Zeige Tileset »

Tileset

eine Karte zu erstellen und im XML Format abzuspeichern.
Danach werden wir mit Hilfe des XML Parsers und der Image Klasse das Tileset laden, in 16 * 16 Pixel große Kacheln zerschneiden, abspeichern, die mit Tiled erstellte und in XML abgespeicherte Karte laden, und daraus dann ein Bild zu erstellen, welches wir in das Fenster zeichnen lassen.

Ich setze vorraus, dass D auf eurem System installiert ist und ihr die einfache Handhabung kennt, Derelict nutzt und eine passende IDE besitzt, ansonsten empfehle ich vorerst die Tutorials auf http://d.whosme.de/, von Einführung ins D Programmieren bis Code::Blocks: Konfigurieren für Derelict, wobei es Geschmackssache ist, ob ihr später mit Code::Blocks oder mit Eclipse/Descent arbeitet; ich arbeite mit Code::Blocks, da man dort die Keywords manuell hinzufügen kann, da einige dem Editor nicht bekannt sind (Beispiele wären “string” & “invariant”) und ich keine Erweiterung brauche, wie Eclipse z.B. Descent benötigt.
Ersteres ist bei Eclipse nicht der Fall, aber das ist, wie bereits angemerkt, reine Geschmackssache.

Viele Worte, wenige Taten … bisher ;)

Zunächst einmal müssen wir uns eine Karte mit Tiled erstellen.
Dazu entpacken wir die herunter geladene .zip und öffnen per Doppel-Klick auf tiled.exe das Programm.
Zunächst einmal werden wir nun dafür sorgen, dass unsere Karte später auch wirklich in XML abgespeichert wird, also gehen wir auf Bearbeiten -> Einstellungen und ändern dort die Einstellungen bis es so aussieht wie hier Einstellungen.
Wenn das getan ist, erstellen wir eine neue Karte indem wir auf Datei -> Neu gehen und uns eine Dialog-Box entgegen springt.
Dort ändern wir unter Kartengröße die Breite und Höhe auf 15 und unter Kachelgröße die Breite und Höhe je auf 16.
Neue Karte
Was heißt das genau?
Diese angaben heißen, dass wir eine Karte erstellen, deren Tiles 16 pixel hoch und breit sind und die insgesamt 15 Tiles hoch und 15 Tiles breit ist.
Weiter im Text. Ihr werdet nun wohl erstmal kein sichtbares Resultat sehen, aber keine Sorge, das lässt sich ganz leicht beheben, es liegt daran, dass per default kein Raster eingestellt ist.
Was das heißt? Dass die Raster bzw. “Umrisse” eurer Tiles (welche noch nicht existent sind) und de fakto auch eure Karte nicht zu sehen ist.
Wie lässt sich das beheben?
Ihr geht auf Ansicht und setzt den Haken bei Zeige Raster, schon seht ihr etwas, wie unten im Hintergrund; im Vordergrund das offene Menü mit angekreuzten Haken. Achtet bitte darauf, dass ebenfalls bei Tilesets und bei Ebenen ein Haken ist, wie hier zu sehen.
Raster

Nun wird es Zeit das entsprechende Tileset zu laden, dazu geht ihr im Menü auf Karte und dort auf Neues Tileset und ein entsprechendes Dialog Fenster wie dieses hier sollte sich auf tun.
Tileset hinzufügen.
Mit Durchsuchen sucht ihr euch das Tileset raus und klickt auf Ok, die Kachelbreite und -höhe lasst ihr so, die sind von unseren Voreinstellungen bei der Erstellung der Karte übernommen worden und damit korrekt.
Nachdem ihr auf OK gedrückt habt, müsstet ihr nun eine solche Übersicht am linken, oberen Rand bekommen.
Tilese Übersicht

Nun zu den Ebenen, ihr wollt sicher verschiedene Ebenen auf eurer Karte haben, vielmehr: es bietet sich geradezu an.
Auf der einen soll man gehen können, auf der anderen nicht, auf der einen soll man sogenannte “items” aufnehmen können, auf der anderen sind (Gebäude)Eingänge verzeichnet.
Per default ist unter Ebenen eine Standard Ebene vordefiniert: Ebene 1
default Ebenen
Klickt sie an, geht im Menü auf Ebenen und dort auf Ebene löschen.
Nun könnt ihr eure eigenen Ebenen hinzufügen.
Klickt dafür wieder im Menü auf Ebenen und dort auf Kachelebene hinzufügen.
Ebene hinzufügen
Euch sollte eine solche Dialog-Box entgegen kommen
Neue Ebene Dialog - Box
So könnt ihr nach euren Ermessen Ebenen hinzufügen, ich halte es für den Anfang meist mit diesen drei Ebenen:

  1. Ground
  2. items
  3. Border

Allerdings überdeckt die jeweils überstehende Ebene die unterstehende, wenn beide auf derselben Position zeichnen, sprich sich ein Tile teilen.
Dies kann im Menü unter Ebenen und dort per Ebene anheben/Ebene absenken manuell angepasst werden.
So sollte zwar die Ebene “items” über “Ground”, aber unter “Border” stehen, um eine natürliche Rangordnung zu schaffen, so dass die späteren “items” – Tiles aufder “Ground” Ebene, allerdings unter der “Border” Ebene gezeichnet/”geblittet” werden.

Nun nachdem diese Vorbereitungen abgeschlossen sind, könnt ihr auf ein Tile unter Tilesets klicken und dann auf euer Karte bzw. in eurem Karten Raster zeichnen.
Dies lässt sich auch gleich mit mehreren Tiles auf einmal machen, indem ihr entweder auf ein Tile klickt, strg gedrückt haltet und weitere auswählt, oder indem ihr auf ein Tile klickt, die linke Muastaste gedrückt haltet und weitere Tiles markiert indem ihr mit dem Mauszeiger über sie geht.

Hier einmal meine Karte in den drei verschiedenen Phasen:
– einmal nur der Grund, sprich die “Ground” Ebene:
Map Grund

– danach mit “items” auf der “items” Ebene:
Map items

– und schließlich und letzlich die gesamte Karte:
Map

Karte & Tileset downloaden

Fast geschafft ;)
Nun speichert ihr eure Karte unter map.tmx und eurem Programmier Ordner und erstellt euch eine main.d Datei.

Hier schreibt ihr nun folgendes rein:

module main;
 
import std.stdio;
import std.c.stdlib : exit;
 
import derelict.sdl.sdl;
 
import XMLTiles;
 
void main() {
	DerelictSDL.load();
 
	if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
		writeln("SDL konnte nicht initalisiert werden");
 
		exit(0);
	}
 
	SDL_Surface * screen = SDL_SetVideoMode(
		640, 480, 32,
		SDL_SWSURFACE
	);
 
	if (!screen) {
		writeln("Es konnte kein Fenster erstellt werden");
 
		exit(0);
	}
 
	SDL_WM_SetCaption("Tile Map", null);
 
	scope XMLParser parser = new XMLParser("map.tmx");
 
	//SDL_BlitSurface(parser.getTileset().getTile(29), null, screen, null);
	SDL_BlitSurface(parser.getMap(), null, screen, null);
 
	SDL_Flip(screen);
	SDL_Delay(2000);
}

Hier müsst bzw. könnt ihr lediglich in Zeile 32 den Kartennamen anpassen, sofern ihr einen anderen als map.tmx gewählt habt, oder diese Datei in einem anderen Ordner gespeichert habt, als den, indem euer main.d Script liegt.
Nun müsst ihr euch nur noch diese beiden Klassen herunterladen (XMLTiles sowie meine eigene, kleine Image Klasse, welche von XMLTiles benötigt wird), packt die beiden entpackten Dateien auch in eurem Programmier Ordner, kompiliert das ganze mit eurer gewählten IDE (zur Erinnerung: entweder Code::Blocks oder Eclipse mit Descent), führt die .exe datei aus, und schon könnt ihr so etwas in eurem Fenster sehen:

Show »

Resultat

Der nächste Artikel/das nächste Tutorial baut auf diesen auf.
Dort werde ich euch zeigen, wie man auf dieser Karte nun eine bewegliche Figur (ein sogenanntes “Sprite“) zeichnet, welches über die Karte läuft.

Nachtrag:

Mittlerweile existiert eine neuere Fassung, die ich nicht vorenthalten will:

Show »

module Dgame.XMLTileMapParser;
 
private import std.xml;
private import std.stdio;
 
class XMLTileMapParser {
private:
	ushort[ushort[2]][string] _layer;
	ubyte[ushort[2]][string]  _alpha;
 
	ubyte[string] _layer_rank;
 
	string _tileset_file;
 
    ushort[2] _map_size;
	ubyte[2] _tile_size;
 
public:
	this(const string filename) {
	    if (!std.file.exists(filename)) {
			throw new Exception("Map-Datei existiert nicht: " ~ filename);
		}
 
	    scope (failure) writeln("Try to load...");
 
		string xml_content = cast(string) std.file.read(filename);
 
        scope (failure) writeln("XML Content loaded...");
        /*
        try {
            std.xml.check(xml_content);
        } catch (CheckException e) {
            // print the stacktrace
            writeln(e);
 
            throw new Exception("XML Dokument ist nicht korrekt.");
        }
        */
        scope (failure) writeln("Checked.");
 
		string layer;
		ubyte alpha;
		ushort column, line;
 
		scope (failure) writeln("Search for items...");
		auto items = new std.xml.Document(xml_content);
        scope (failure) writeln("Finish searching...");
 
		bool filled = false;
 
		scope (failure) writeln("Beginning with Main-loop...");
 
		ubyte rank = 0;
		foreach (item; items.elements) {
			//scope (failure) writeln(item);
			if (item.isEmptyXML()) {
				continue;
			}
 
			if ("name" in item.tag.attr) {
				if ("width" in item.tag.attr && !this._map_size[0]) {
					this._map_size[0] = std.conv.to!(ushort)(item.tag.attr["width"]);
					this._map_size[1] = std.conv.to!(ushort)(item.tag.attr["height"]);
				}
 
				layer = std.conv.to!(string)(item.tag.attr["name"]);
				line  = column = 0;
 
				if ("opacity" in item.tag.attr) {
					float opacity = std.conv.to!(float)(item.tag.attr["opacity"]);
					alpha = cast(ubyte) std.math.round(255 * opacity);
				} else {
					alpha = 0;
				}
			}
 
			if ("tilewidth" in item.tag.attr) {
				this._tile_size = [
					std.conv.to!(ubyte)(item.tag.attr["tilewidth"]),
					std.conv.to!(ubyte)(item.tag.attr["tileheight"])
				];
			}
 
			foreach (item_sub; item.elements) {
				if ("source" in item_sub.tag.attr) {
                    if (item_sub.tag.attr["source"].length == 0) {
                        throw new Exception("Source for Tileset has no length.");
                    }
 
                    scope (failure) writeln("Creating the Tileset...");
 
					//this._tileset = new Tileset(item_sub.tag.attr["source"], this._tile_size);
					this._tileset_file = std.string.format("%s", item_sub.tag.attr["source"]);
					//assert(this._tileset !is null, "Tileset can't be load.");
 
					scope (failure) writeln("Tileset created...");
				}
 
				foreach (item_child; item_sub.elements) {
					if (layer.length > 0) {
						ushort gid = std.conv.to!(ushort)(item_child.tag.attr["gid"]);
 
						if (gid > 0) {
							gid -= 1;
 
							const ushort[2] pos = [
								cast(ushort) (column * this._tile_size[0]),
								cast(ushort) (line * this._tile_size[1])
							];
 
							if (!filled) filled = true;
 
                            scope (failure) writeln("Save the gid...");
 
                            const string cur_layer = std.string.toLower(layer);
 
                            if (cur_layer !in this._layer_rank) {
                                this._layer_rank[cur_layer] = rank++;
                            }
 
							this._layer[cur_layer][pos] = gid;
							this._alpha[cur_layer][pos] = alpha;
 
                            scope (failure) writeln("Gid saved...");
						}
 
						column += 1;
						if (column >= this._map_size[0]) {
							column = 0;
							line += 1;
						}
					}
				}
			}
		}
 
		scope (failure) writeln("Ending with Main-loop...");
 
		if (!filled) {
			this._layer = ["" : [[0, 0] : 0]];
		}
	}
 
	ushort[ushort[2]][string] get_layer() {
		return this._layer;
		//return ["foo" : [[0, 0] : 1]];
	}
 
	ubyte get_layer_count() const {
        ubyte number = 0;
        foreach (nr; this._layer_rank) {
            number++;
        }
 
        return number;
	}
 
	ubyte[string] get_layer_ranking() {
        return this._layer_rank;
	}
 
	string get_layer_by_rank(const ubyte rank) const {
        foreach (name, nr; this._layer_rank) {
            if (nr == rank) {
                return name;
            }
        }
 
        return "";
    }
 
	ubyte get_layer_rank(const string layer) const {
        if (layer in this._layer_rank) {
            return this._layer_rank[layer];
        }
 
        return cast(ubyte) this._layer_rank.length;
    }
 
	string[] get_layer_names() const {
        return this._layer_rank.keys;
	}
 
	ubyte[ushort[2]][string] get_alpha() {
		return this._alpha;
		//return ["foo" : [[0, 0] : 1]];
	}
 
	string get_tileset_file() const {
		return this._tileset_file;
	}
 
	ubyte[2] get_tile_size() const {
		return this._tile_size;
	}
 
	ushort[2] get_map_size() const {
        return this._map_size;
	}
}

Add a comment »One comment to this article

  1. Sprites « Code – Arts

*

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress