INI Dateien sind nützliche Textdateien, in denen sich Einstellungen und zahlreiche Informationen speichern lassen. Dieses Tutorial zeigt, wie man mit INI-Dateien im GameMaker Studio 2 umgeht.
Was sind INI Dateien?
Manchmal ist es nützlich, Informationen, die das Spiel braucht, extern zu speichern. Der häufigste Anwendungsfall sind die Settings. Wenn der Spieler Einstellungen wie Bildschirmauflösung, Lautstärke, Steuerung und andere Dinge vornimmt, dann hätte es jeder Spieler gern, wenn das Spiel sich diese Einstellungen merken würde.
Im GameMaker gibt es mehrere Methoden um Dateien zu speichern. Für Einstellungen und Profildaten sind INI-Dateien perfekt geeignet. Dabei handelt es sich um Textdateien, die einem bestimmten Aufbau folgen. Eine Einschränkung im GameMaker liegt in der maximalen Größe der Datei: sie darf nicht größer sein als 64kb. Das klingt nach wenig, ist aber für die erwähnten Einstellungen völlig ausreichend. Das Speichern von Leveldaten (Positionen von Objekten etc.) sollte man aber lieber nicht mit INIs durchführen.
Ein weiteres Problem kann bei sensiblen Daten wie Highscorewerten auftreten. Die GameMaker Hilfe gibt zwar explizit die Speicherung von lokalen Highscores als Anwendungsfall an, aber eine normale INI ist unverschlüsselt. Jeder Spieler kann sie mit einem Texteditor öffnen und beliebig die Werte manipulieren. Bei Einstellungen ist das durchaus so gewollt. Wenn der Spieler versehentlich eine Auflösung einstellt, die nicht funktioniert, dann sollte er die Möglichkeit erhalten, von Außen diese Einstellung zu ändern.
Dies bedeutet: Wenn man sensible Daten in eine INI speichern möchte, dann sollte man sie verschlüsseln. Eine Verschlüsselung wird vom GameMaker nicht mitgeliefert. Diese muss man entweder selbst programmieren oder sich entsprechende Skripte im GM-Marktplatz besorgen. Geht man diesen Weg, dann sollten die Settings dennoch unverschlüsselt gespeichert werden. Wir reden also davon, mehrere INI-Dateien anzulegen.
Unterstützt das Spiel mehrere lokale Spielerprofile, dann kann man für jedes Profil eine eigene INI anlegen.
Aufbau einer INI
Eine INI ist immer in Sektionen (section) und Schlüsselwerten (key) unterteilt. Eine INI kann beispielsweise so aussehen:
1 2 3 4 5 6 | Lang= de" Fullscreen="0" Theme="3"</pre][Settings] Lang="de" Fullscreen="0" Theme="3" |
[Settings], also das Wort in den eckigen Klammern, ist die Sektion. Theoretisch könnte man auch alle Spielerprofile durch Sektionen unterteilen, dies hat aber auch Nachteile. Wenn es beim Speichern Probleme gibt, genau in diesem Moment der PC abstürzt oder andere unerwartete Dinge passieren, würden alle Profilinformationen im Datengrab liegen. Wenn man es brav trennt, nur eine.
Die Werte Lang, Fullscreen und Theme sind die Schlüsselwerte. Die Informationen nach dem Gleichheitszeichen werden vom Spiel eingelesen bzw. gespeichert. Es wird zwischen String und Real unterschieden. Beim Schreiben der Daten gibt es zwar im GM unterschiedliche Befehle, die Auswirkung in der Datei ist aber gleich Null. Auch Zahlen werden in Anführungsstriche gesetzt. Wir können theoretisch alles als String speichern und erst beim Laden der Dateien separieren.
INIs erstellen
Man kann direkt mit GameMaker INI Dateien erstellen. Das ist extrem praktisch. Wenn der Spieler versehentlich die Datei löscht, generiert sie das Spiel beim Neustart wieder und es gibt keine Probleme.
Wer INI Dateien manuell erstellt, um etwa Sprachdateien zu erzeugen, sollte diese unbedingt als UTF-8 speichern, damit die Sonderzeichen nicht verloren gehen. INIs, die von GameMaker erzeugt werden, sind automatisch UTF-8.
Jetzt erzeugen wir mal eine INI und schauen uns den Code an.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Prüft, ob der Ordner Settings existiert. Wenn nicht, wird er erstellt. if !directory_exists(working_directory + „\Settings\\") { directory_create(working_directory + „\Settings") } // INI wird eingelesen. Wenn sie nicht existiert, wird sie erstellt und beschrieben. if !file_exists("Settings\\" + global.iniFile) { file = file_text_open_write("Settings\\" + string(global.iniFile)); // Datei wird erstellt file_text_write_string(file,"[Settings]"); // Section wird angelegt file_text_writeln(file); // Leere Zeile file_text_write_string(file,"Lang="+string(global.activeLanguage)); // Sprache file_text_writeln(file); // Leere Zeile file_text_write_real(file,"Fullscreen="+string(global.isFullscreen)); // Fullscreen/Fenster file_text_writeln(file); // Leere Zeile file_text_write_real(file,"Theme="+string(global.activeTheme)); // Theme file_text_writeln(file); // Leere Zeile file_text_close(file); } |
Wie man sieht, arbeite ich mit globalen Variablen. Das hat sich bei mir auch bei größeren Projekten bewährt.
Zunächst schauen wir, ob das Verzeichnis für die INI existiert. Es ist der Ordner „Settings“ im „working_directory«. Das Arbeitsverzeichnis kann, je nach Windows, Einstellungen und Einstellungen im GM unterschiedlich sein. Es sollte sich aber immer um ein Verzeichnis auf der Benutzerebene handeln, in der der GameMaker Schreibrechte besitzt. In den meisten Fällen haben wir als Entwickler damit nichts zu tun und GMS regelt das selbst. Was wir, aus Gründen der Ordnung, brauchen, ist der Unterordner Settings.
Mit !file_exists(“Settings\\” + global.iniFile) schauen wir, ob bereits eine INI existiert. Der Dateiname ist bei mir immer eine globale Variable, in diesem Fall global.iniFile. Meistens nennt man sie settings.ini. Wenn die Datei noch nicht existiert, wird sie mit den Standardwerten erzeugt.
file = file_text_open_write() erzeugt die Datei. Genau genommen erzeugt der Code nur dann eine Datei, wenn sie noch nicht existiert. Wenn eine Datei mit dem Namen existiert, wird sie zum schreiben geöffnet.
Kleiner Hinweis: Früher musste man die Datei vorher kopieren, bevor man sie bearbeiten konnte. Sie wurde in das Verzeichnis kopiert in dem die EXE lag, manipuliert, zurück kopiert und gelöscht. Das hat sich zum Glück irgendwann erledigt.
Mit file_text_write_string(file,”[Settings]”); legen wir die Sektion an. In unserem Fall heißt sie [Settings].
file_text_writeln(file) legt eine neue Zeile an. Anschließend schreiben wir mit jedem file_text_write_string bzw. file_text_write_real eine neue Zeile in die INI. Der Vorgang ließe sich mit weiteren Einstellungen fortführen.
Am ende wird die Datei mit file_text_close(file) geschlossen und damit auch gespeichert.
Im Prinzip ist es also ganz einfach.
INI laden
1 2 3 4 5 6 7 8 | if (file_exists("Settings\\" + global.iniFile)) { ini_open("Settings\\" + global.iniFile); // INI Datei wird geöffnet global.activeLanguage = ini_read_string("Settings", „Lang", „de"); // Sprache global.isFullscreen = ini_read_real("Settings", „Fullscreen", 1); // Fullscreen global.activeTheme = ini_read_real("Settings", „Theme", 1); // Theme ini_close(); // INI Schließen } |
Das Laden ist ebenso einfach. Erst schauen wir, ob die INI existiert. Dann wird sie geöffnet und der Reihe nach die Keys eingelesen. Wir haben drei Variablen, die wir mit den Werten aus der INI füllen. Hier müssen wir zwingend zwischen String und Real unterscheiden.
Schauen wir uns exemplarisch ini_read_real(“Settings”, „Fullscreen”, 1) an. Das erste Argument zielt auf die Sektion “Settings”. Das zweite Argument schaut nach dem Key “Fullscreen”. Sollte der nicht existieren, wird ein Default-Wert benutzt. In diesem Fall ist das die 1. Beim laden der INI ist somit ganz wichtig, dass wir String und Real unterscheiden und wir immer einen Default-Wert angeben, da ansonsten das Spiel abstürzen kann. Schließlich kann ein Schlaumeier von Spieler auf die Idee kommen, einzelne Zeilen zu entfernen.
INI speichern
1 2 3 4 5 6 7 8 | if file_exists("Settings\\" + global.iniFile) { ini_open("Settings\\" + global.iniFile); // INI Datei wird geöffnet ini_write_string("Settings","Lang",string(global.activeLanguage)); // Sprache ini_write_string("Settings","Fullscreen",string(global.isFullscreen)); // Fullscrenn ini_write_string("Settings","Theme",string(global.activeTheme)); // Theme ini_close(); // INI Schließen } |
Ich persönlich verwende immer ini_write_string. Wie oben beschrieben, ist die Unterscheidung nur beim Lesen wichtig. GMS setzt die Werte ohnehin in Anführungszeichen, egal ob man ini_write_string oder ini_write_real benutzt. Der Unterschied ist nur, dass GMS statt einer 3 ein 3.00000000 schreibt, wenn man Real benutzt.
Das Prinzip sollte aber klar sein. Wir schauen wieder, ob die Datei existiert und wenn ja, wird sie geöffnet und mit drei Zeilen beschrieben. Bei ini_write_string geben wir erneut die Sektion, den Schlüsselwert und zuletzt die Variable an.
Praktische Anwendung
Am besten setzt man die Codes in drei Skripte. scr_ini_create beispielsweise ruft man beim Spielstart auf, gefolgt von scr_ini_load. Es hat sich bewährt, globale Variablen in ein extra Skript zu speichern, so dass beim Spielstart nacheinander die drei Skripte aufgerufen werden.
- scr_global_variables();
- scr_ini_create();
- scr_ini_load();
Wir holen uns erst die ganzen Variablen. Dann wird geschaut, ob die INI existiert und ggf. angelegt. Falls sie existiert, wird die INI ausgelesen und die Einstellungen geladen. Das bedeutet zwar, dass die Variablen doppelt bzw. dreifach beim Spielstart angepackt werden, aber zeitlich macht das keinen spürbaren Unterschied und ist die sicherste Methode.
Bei scr_ini_save gibt es unterschiedliche Ansätze. Manche speichern die Werte erst, wenn das Spiel beendet wird. Das kann nützlich sein, falls das Spiel aufgrund unbedachter Einstellungen abstürzt. Auf der anderen Seite kann ein Spiel auch unabhängig von den Einstellungen abstürzen und dann wurden womöglich wichtige Einstellungen nicht gespeichert.
Beispiel: Das Spiel hat eine komplexe Steuerung. Im Level stürzt das Spiel wegen eines Bugs, oder einfach nur, weil Windows einen schlechten Tag hat, ab. Dann kann der Spieler die ganzen Einstellungen neu vornehmen. Die meisten Spieler treten das Spiel genau in diesem Moment in den Papierkorb. Deshalb: Am besten speichert man das Spiel bei jeder Änderung der Einstellungen ab. Sollte es Probleme geben, kann der Spieler die INI immer noch löschen oder von Hand anpassen. Das ist auf jeden Fall der kleinere Schaden.
Wie das Ganze in der Praxis aussieht, zeige ich noch in einem kurzen Video: