Wir basteln uns einen Karteneditor

In der Informatik-AG am Hochrhein-Gymnasium entsteht ein neues Spiel – diesmal soll es ein RPG werden – und dazu kann man einen Karteneditor gebrauchen.

Der Tilemap-Karteneditor in Version 0.2 mit der Karte der Insel Mondia (Screenshot)
Der Tilemap-Karteneditor in Version 0.2 mit der Karte der Insel Mondia (Screenshot)

Auch dieses Jahr (2023/2024) steht in der Informatik-AG am Hochrhein-Gymnasium wieder Python-Programmierung an. Diesmal hatten wir aber mit HTML/CSS/JavaScript begonnen, bevor wir dann wieder zu Python und jetzt PyGame gekommen sind.

Diesmal soll das Beispiel-Game unserer Arbeitsgemeinschaft ein RPG1RPG ist die Abkürzung für „Role Playing Game“ – das beliebte Genre bei Computerspielen, wo Spieler als Helden durch eine Fantasy-Welt wandeln und dabei Abenteuer erleben. werden, wozu wir jetzt Level-Karten erstellen. Diese sind zunächst einmal als einfache Textdateien konzipiert, aus dem je nach Textzeichen unterschiedliche Feldtypen festgelegt werden. (Das ist zugegeben sehr simpel und wir werden eventuell später noch verbessern, aber für den Anfang reicht es.)

Nun ist es zugegeben etwas dröge, die Textdateien in einem Texteditor von Hand einzugeben, selbst wenn man Visual Studio Code benutzt, weshalb der Informatik-Lehrer die Gelegenheit genutzt hat, ein kleines Beispielprogramm zu schreiben.

Die jeweils neueste Version des Karten-Editors findet sich auf der Niarts-Webseite:

https://www.niarts.de/software/infoag/tilemap/mapeditor.html

Der Karteneditor (Version 0.2)

Das Beispielprogramm benutzt mit voller Absicht HTML/CSS/JavaScript und nicht PyGame, worin das spätere Spiel entstehen soll. Damit rückt der Karteneditor nämlich einerseits nicht in Konkurrenz zum AG-Programm, andererseits kann man sich daraus ja für den Python-Code inspirieren lassen und noch ein bisschen HTML/CSS/JavaScript vertiefen.

Weil es ein Lehrprogramm ist, wurde auf abenteuerlichere Konstruktionen verzichtet und der Quelltext sollte möglichst kurz sein. Das HTML-Gerüst, die CSS-Styles und das eigentliche JavaScript nutzen sich gegenseitig. Mit dem JavaScript wird natürlich das HTML-Dokument bearbeitet. Letzteres besteht neben erklärenden Texten vor allem aus zwei Tabellen, eine mit der ID „toolbox“ und eine mit der ID „map“.

Die Toolbox als Tabelle

Die beiden Tabellen "toolbox" und "map" in der Webbrowser-Ansicht (Screenshot)
Die beiden Tabellen „toolbox“ und „map“ in der Webbrowser-Ansicht (Screenshot)

Die Toolbox-Tabelle dient als Werkzeugleiste für die einzelnen Feldtypen des Karteneditors. Jede Zelle hat ensprechend eine eigene CSS-Klasse und eine ID, die den Buchstaben enthält, der später das Tabellenfeld im Textdokument festlegt. Das geht hier als ID, weil es jeden Feldtyp ja nur ein einziges Mal geben wird. (Insofern sichert es auch ab, dass wir nicht einen Buchstaben versehentlich zweimal verwenden, da das ID-Attribut singulär sein muss.)

<table class="toolbox" id="toolbox">
<tr>
<td class="map_eis" id="e">Eis</td>
<td class="map_lava" id="L">Lava</td>
<td class="map_haus" id="H">Haus</td>
<td class="map_weg1" id="1">Weg</td>
<td class="map_wasser" id="W">Wasser</td>
<td class="map_wald1" id="5">Wald</td>
<td class="map_wiese1" id="9">Wiese</td>
<td class="map_sand1" id="§">Sand</td>
<td class="map_wall1" id="#">Mauer</td>
<td class="map_wdoor" id="y">Türe</td>
</tr>
</table>

Damit im Editor die Feldtypen auch schön bunt dargestellt werden, sind in im CSS-Stylesheet die Farbwerte als Klassen hinterlegt und in der Toolbox-Tabelle eingetragen:

.map_eis {
  background-color: rgb(245,245,255);  
}
.map_lava {
  background-color: rgb(255,165,22); 
}
.map_haus {
  background-color: rgb(225,65,62); 
}

Die Kartenfelder als Tabelle

Die eigentlichen Felder der Spielkarte finden sich in der Tabelle „map“. Diese hat, wie wir das in der Informatik-AG einstweilen für unser Spiel festgelegt hatten, 25 x 25 Felder, also 25 Spalten und 25 Zeilen. Die Tabellenzellen stehen für ein Feld. Zunächst ist diese recht große Tabelle einfach als leeres Gerüst in der HTML-Datei vorgegeben, der Style für die <td>-Tags im CSS sorgt dafür, dass sie schön quadratisch aussehen, wie gewünscht.


td {
  background-color:white;
  font-family: 'Segoe UI', Tahoma, Verdana, sans-serif;
  font-size: 6pt;
  width: 20pt;
  height: 20pt;
}

(Natürlich wäre es für später wünschenswert, die ganze Tabelle mit einer Wunschgröße durch das JavaScript zu produzieren, die Ursprungsversion des Karteneditors hat diese fortgeschrittenere Variante aber noch nicht.)

Die Programmlogik als EventListener

Damit wir die einzelnen Kartenfelder setzen können, kommt nun JavaScript zum Zuge. Für die Zellen beider Tabellen setzen wir jeweils einen ähnlich aufgebauten EventListener, der die Zellen modifiziert, wenn sie mit der Maus angeklickt werden. (Touch-Bedienung fehlt also noch, wir werden das später vielleicht noch ändern.) Weil dafür zunächst das gesamt HTML-Dokument geladen sein muss, binden wir die JavaScript-Datei erst am Schluss des HTML-Dokumentes ein, dann wird der Event-Listener installiert in der üblichen Form:


// 1. Allgemeine Variablen
 
var chosentype ="map_wasser"; // Das ist der Feldtyp, gibt die CSS-Klasse an
var chosenname = "Wasser"; // Das ist eine Beschreibung für die Zelle
var chosentitle ="W"; // Dieser Buchstabe wird später in die Karte geschrieben
 
// 2. Eventlistener, um Mausereignisse abzufangen
 
document.addEventListener('DOMContentLoaded', function() {
  let toolbox = document.getElementById('toolbox');
  // Füge einen Event Listener zu jeder Zelle in der Tabelle Toolbox hinzu.
  // Damit kann man die Feldtypen auswählen.
  let zelle = toolbox.getElementsByTagName('td');
  for (let i = 0; i < zelle.length; i++) {
    zelle[i].addEventListener('click', toolboxklick);
  }
 
  // Event Handler-Funktion für den Zellenklick
 
  function toolboxklick() {
    // Wähle das angeklickte Feld aus (Text und Klasse)
    chosenname = this.innerHTML;
    chosentype = this.className;
    chosentitle = this.id;
  }
 
  let tabelle = document.getElementById('map');
 
  // Füge einen Event Listener zu jeder Zelle in der Tabelle Map hinzu.
  // Damit kann man die Felder im Karteneditor setzen.
 
  let zellen = tabelle.getElementsByTagName('td');
  for (let i = 0; i < zellen.length; i++) {
    zellen[i].addEventListener('click', zellenklick);
  }
 
  // Event Handler-Funktion für den Zellenklick
 
  function zellenklick() {
    // Setze den Wert im angeklickten Kartenfeld
    this.innerHTML=chosenname;
    this.setAttribute("class",chosentype);
    this.setAttribute("title",chosentitle);
  }
});

Der Listener für die Toolbox-Zellen wird über eine For-Schleife realisiert, die erst mal die gesamten Zellen der Tabelle durchgeht. In der eigentlichen Funktion werden dann drei Variablen gesetzt, welche den Feldtyp festlegen, diese werden aus den Zellenwerten der jeweiligen Toolbox-Zelle ausgelesen.

Bei den Zellen des eigentlichen Karteneditors müssen wir etwas variieren. Natürlich setzen wir die Zellenklasse (Farbe) über die zuvor ausgelesenen Toolbox-Werte, ebenso die Beschriftung. Den Buchstaben können wir aber nicht wieder als ID schreiben, sonst hätten wir viele gleichnamige IDs, was nicht erlaubt ist. Daher schreiben wir den Buchstaben diesmal nicht in das Zellen-Attribut „id“, sondern in das Attribut „title“.

Die beiden Funktionen zum Umwandeln

Da das spätere Python-Programm natürlich keine HTML-Tabellen liest, sondern eine einfache Textdatei, brauchen wir nun noch zwei Funktionen zum Umwandeln: die primäre Funktion, welche die mit der Tabelle erstellte Karte in einen Textstring umwandelt und – damit es intuitiver wird – eine spiegelverkehrte, womit man bereits erstellte Textstrings wieder in die Tabelle laden kann, falls man noch etwas ändern möchte.

Die beiden Funktionen werden im HTML-Dokument als „onClick“-Ereignis den beiden Schaltflächen unter dem Editor zugewiesen.

Unten im HTML-Dokument des Karteneditors finden sich die beiden Schaltflächen zum Umwandeln und ein Textfeld für den Textstring (Screenshot).
Unten im HTML-Dokument des Karteneditors finden sich die beiden Schaltflächen zum Umwandeln und ein Textfeld für den Textstring (Screenshot).

Die eigentliche Umwandlung gestaltet sich überraschend einfach: Zunächst lesen wir die Werte der einzelnen Zellen aus und schreiben sie dann zunächst in ein JavaScript-Array (Dieser Zwischenschritt ist Absicht, weil wir so versehentlich nicht gesetzte Kartenfelder mit einem default-Wert schreiben können und vor allem, wenn wir das Format später mal erweitern wollen.) Das so frisch erstellte Array joinen2D.h., wir entfernen die Trennzeichen, die sonst vorhanden wären, wenn wir das Array einfach so in ein Textfeld umleiten. wir dann in einen String und weisen diesen einem dafür vorgesehenen Textfeld (<textarea>) zu. Aus diesem kann man dann den Kartenfeldstring für die spätere Verwendung kopieren.


// 3.2 LoadMap() - Dies ist die Funktion,
// um die Felder der Tabelle in Text für den Karteneditor umzuwandeln.
// Der Text wird dann in das Textfeld "maptext" der HTML-Datei geschrieben.
// Die "Karte in Text umwandeln"-Schaltfläche ruft diese Funktion auf.
 
function SaveMap() {
  let tabelle = document.getElementById('map');
  let zellen = tabelle.getElementsByTagName('td');
  let mapfields = [];
  let mapfieldtitle = "";
  for (var i = 0; i < zellen.length; i++) {
    mapfieldtitle = zellen[i].title;
    if (mapfieldtitle != "") { mapfields.push(mapfieldtitle) } else {
      mapfields.push("W");
    };
    }
  let maptext = mapfields.join('');
  document.getElementById("maptext").innerHTML=maptext;
  }
 
// 3.2 LoadMap() - Dies ist die Funktion,
// um Text für den Karteneditor in die Tabelle zu laden.
// Die "Text in Karte umwandeln"-Schaltfläche ruft diese Funktion auf.
 
function LoadMap() {
  let tabelle = document.getElementById('map');
  let zellen = tabelle.getElementsByTagName('td');
  let mapfields = [];
  let mapfieldtitle = "";
  let maptext = document.getElementById("maptext").value;
  for (var j = 0; j < maptext.length; j++){
    mapfields.push(maptext[j]);
  }
  while (mapfields.length < zellen.length) {
    mapfields.push("w");
  }
  for (var i = 0; i < zellen.length; i++) {
    zellen[i].title = mapfields[i];
    zellen[i].innerHTML = mapfields[i];
    zellen[i].className = getFieldColor(mapfields[i]);
    zellen[i].innerHTML = getFieldTxt(mapfields[i]);
    }
  }

Bequemer wäre natürlich, wenn man gleich einen Speichern-unter-Dialog aufruft, aber das ist in JavaScript nicht so trivial, weshalb wir in der Demoversion darauf verzichtet haben.

Die Rückumwandlung funktioniert so ähnlich, hier brauchen wir allerdings noch zwei Hilfroutinen, welche aus der Toolbox-Routine die dem jeweils zugeordneten Buchstaben noch die entsprechende CSS-Klasse und die Beschriftung zuweisen:

// 3.1 Hilfsfunktionen
// Diese Funktion liest zu einem Buchstaben 
// den passenden Klassennamen aus der HTML-Toolbox
 
function getFieldColor(letter){
  return document.getElementById(letter).className;
}
 
// Diese Funktion liest zu einem Buchstaben 
// den passenden Beschreibungstext aus der HTML-Toolbox
 
function getFieldTxt(letter){
  return document.getElementById(letter).innerHTML;
}

Erweiterungsmöglichkeiten

Ein Meisterwerk ist das Programm so noch nicht und sollte es auch nicht sein. Es ist eher eine schnell erstellte Machbarkeitsstudie. Das Programm habe ich kurzfristig in drei Zeitstunden Arbeitszeit an einem Freitagabend nach der Informatik-AG geschrieben, der HTML-Code umfasst 758 Zeilen (wobei die meisten für die beiden Tabellen draufgehen), das CSS-Stylesheet 143 Zeilen und das JavaScript (nur) 106 Zeilen Quelltext.

Die Ursprungsversion des Karteneditors (0.1 und 0.2) weist natürlich noch viel Verbesserungspotential auf. Einerseits ist das Level-Format so noch recht simpel und alles andere als objektorientiert. Naheliegend wäre es daher, statt Text das JSON3JSON steht für „Java Script Object Notation“ und ist ein Standardformat zum Austausch von Daten, das von vielen Programmiersprachen unterstützt wird.-Format zu benutzen, was sowohl JavaScript als auch Python lesen und schreiben können.

Dazu wäre es auch praktisch, wenn wir die Werte für die Tabelle nicht einfach aus der Toolbox-HTML-Tabelle lesen würden, sondern diese Werte separat hinterlegen, dann kann man sie deutlich bequemer anpassen.

Nicht zuletzt wäre es natürlich sowohl für die Toolbox-Tabelle, als auch für die eigentliche Tabelle des Karteneditors wünschenswert, wenn sie dynamisch erstellt werden würde und zwar mit der Option, die Kartengröße nach Wunsch festzulegen. Dies ist aber einer späteren Version des Editors vorbehalten.

Die GUI lässt sich natürlich auch noch verbessern. Im aktuellen Zustand muss man jede Zelle einzeln anklicken, um den Wert zu setzen. Viel intuitiver wäre natürlich, wenn man die Felder mittels MouseOver-Events malen könnte, so wie wir das seinerzeit beim JavaScript-Malprogramm in der AG getan hatten.

Aber ein Beispielprogramm muss nicht perfekt sein, nur verständlich programmiert, also auch nicht zu lange und der Vorteil, wenn man selbst programmiert ist ja, dass man Programme und Spiele nach Lust und Laune verbessern kann, wo der Bedarf besteht.

Update (26.11.2023):

  • In der Version 0.4 wurde die Möglichkeit ergänzt, eigene Größen für die Karten einzugeben und die HTML-Tabelle wird nun dynamisch dazu erstellt.
  • Der Editor wurde von „Tilemap Karteneditor“ in „Chartiles Karteneditor“ umbenannt und die Beispielkarte zur Insel Mondia als Logo gewählt.
  • Um lästige Bugs mit Browsercaching zu beheben, wurde es per entsprechender Befehle für HTML, CSS und Javascript abgestellt.

Die jeweils neueste Version des Karten-Editors findet sich auf der Niarts-Webseite:

https://www.niarts.de/software/infoag/tilemap/mapeditor.html

Glossar

  • 1
    RPG ist die Abkürzung für „Role Playing Game“ – das beliebte Genre bei Computerspielen, wo Spieler als Helden durch eine Fantasy-Welt wandeln und dabei Abenteuer erleben.
  • 2
    D.h., wir entfernen die Trennzeichen, die sonst vorhanden wären, wenn wir das Array einfach so in ein Textfeld umleiten.
  • 3
    JSON steht für „Java Script Object Notation“ und ist ein Standardformat zum Austausch von Daten, das von vielen Programmiersprachen unterstützt wird.
Über Martin Dühning 1438 Artikel
Martin Dühning, geb. 1975, studierte Germanistik, kath. Theologie und Geschichte in Freiburg im Breisgau, arbeitet am Hochrhein-Gymnasium in Waldshut und ist Gründer, Herausgeber und Chefredakteur von Anastratin.de.