Nach häufiger Nachfrage gibts jetzt auch mal ein ein Tilemap-Tutorial von mir. Gleich vorneweg, dieses Tutorial behandelt wirklich ausschließlich das Laden und Speichern von Tilemaps, grundlegendes Wissen für die Spieleentwicklung wird hier nicht vermittelt.
Dann wollen wir mal anfangen. Bevor wir uns ums Laden und Speichern Gedanken machen können, müssen wir erst einmal festlegen, wie wir die Daten innerhalb des Programmes darstellen wollen. Ich habe hier mal ein extrem vereinfachtes Modell genommen, damit wir uns wirklich auf das eigentliche Problem fixieren können. Da eine Tilemap aus Tiles zusammengesetzt ist, ist es logisch, auch eine Klasse Tile zu erstellen. Diese Klasse besitzt bei mir die Eigenschaften X, Y und ID (ich denke es sollte klar sein, für was diese stehen).
VB
Dann brauchen wir natürlich auch unsere Spielfigur. Diese ist bei mir ebenfalls eine Klasse.
Und zu guter Letzt wäre da natürlich auch noch die Map selber.
Bei mir sieht es so aus, dass die Map-Klasse einen Spieler, ein zweidimensionales Array von Tiles und auch ihre Breite und Höhe speichert. Zusätzlich soll man eine Map aus einer Datei erstellen und in eine Datei speichern können.
VB
C#
Nun gilt es, den Konstruktor und die Save-Methode zu implementieren.
Jetzt können wir uns dem Format widmen, mit dem wir unsere Daten speichern wollen. Ich habe es hier wieder sehr einfach gehalten.
Ich habe mich für ein binäres Format entschieden. Dieses kann zwar nicht per Hand bearbeitet werden (theoretisch schon, mit einem HexEditor), sondern braucht einen Map-Editor (einen solchen sollte man aber sowieso immer haben, da es die Dinge ungemein vereinfacht), aber ist dafür leichter für das Programm zu lesen und spart außerdem Speicherplatz.
Die Datei kann man in zwei Teile zerlegen, den ersten Teil, der immer die selbe Länge hat, und den zweiten mit variabler Länge. Hier muss ich dazu sagen, dass dies wirklich die Variante für Arme ist. Man könnte die Datei auch in Chunks unterteilen, mit Headern und allem drum und dran, wodurch man um einiges flexibler wird, das wäre aber nicht mer anfängerfreundlich.
Im ersten Teil werden die Mapgröße und die Startposition der Spielfigur gespeichert. Dies sind immer 4 Werte und in unserem Fall Integer (also 4 Byte). Ich werde übrigens überall in der Datei Integer verwenden. Wenn ihr sehr große Dateien bei eurem Spiel erwartet, dann könnt ihr das auch je nach Bedarf zu 2 oder 1 Byte ändern. Geht dabei aber mit Bedacht vor, hab ihr euch einmal entschieden, gibt es kein Zurück, ändert ihr es so müsst ihr alle Dateien aufwändig konvertieren, oder sie werden unbrauchbar. Das genaue Pattern ist:
Im zweiten Teil werden die Tiles gespeichert. Um Speicherplatz zu sparen speichere ich nur die Tiles, die auch tatsächlich belegt sind. Tiles mit der ID 0 (also Luft) werden nicht gespeichert sondern nachher vom Programm automatisch generiert. Für einen einzelnen Tile sieht das Pattern so aus:
Da wir wissen, wie lang ein Tile ist (12 Byte bzw 4 Integer), können wir die Tiles ohne Probleme hintereinander in die Datei schreiben. Der Integer nach der ID von Tile 1 ist also die PositionX von Tile 2:
Das komplette Pattern für die Datei sieht damit so aus:
Das wars auch schon, jetzt können wir mit dem eigentlichen Programmieren anfangen.
Um die Datei auszulesen verwenden wir am besten einen BinaryReader, denn wir speichern unsere Daten ja auch binär. Nun sieht es schon mal so aus:
Den ersten Teil der Datei können wir sehr einfach auslesen. Einfach 4 mal
VB
Jetzt bleiben nur noch die Tiles. Dafür müssen wir solange 3 Integer lesen, bis nicht mehr genug Bytes für 3 Integer in der Datei übrig sind. Man könnte auch einfach lesen, bis die Datei zu Ende ist, aber ich gehe hier lieber auf Nummer sicher. Das lässt sich ganz einfach mit einer Schleife lösen. Die 12 kommt daher, dass die Angaben in Bytes sind und ein Integer 4 Bytes besitzt, also 3 * 4 = 12.
VB
C#
Jetzt dürfen wir aber nicht vergessen, dass wir noch alle restlichen Tiles, die nicht in der Datei gespeichert waren, mit Luft (also Tiles der ID 0) gefüllt werden müssen. Dafür gehen wir alle Arrayelemente durch und prüfen, ob diese Nothing/Null sind, und erstellen dann gegebenenfalls die Tiles:
VB
C#
Damit wäre das Einlesen auch schon geschafft, ihr seht also, gar nicht so viel Code.
Jetzt haben wir die Daten im Programm, wir wollen sie von dort aber auch wieder zurück in eine Datei bekommen.
Dafür gehen wir jetzt den umgekehrten Weg. Statt einem StreamReader nehmen wir diesmal einen StreamWriter, denn wir wollen ja was in die Datei schreiben. Dann können wir den ersten Teil wieder direkt erstellen, indem wir 4 mal
VB
C#
Damit geht auch dieses Tutorial zu Ende. Ich hoffe ich konnte euch alles gut vermitteln, bei Fragen stehe ich gerne zur Verfügung.
Anbei findet ihr auch noch die originale C#-Projektmappe.
Dann wollen wir mal anfangen. Bevor wir uns ums Laden und Speichern Gedanken machen können, müssen wir erst einmal festlegen, wie wir die Daten innerhalb des Programmes darstellen wollen. Ich habe hier mal ein extrem vereinfachtes Modell genommen, damit wir uns wirklich auf das eigentliche Problem fixieren können. Da eine Tilemap aus Tiles zusammengesetzt ist, ist es logisch, auch eine Klasse Tile zu erstellen. Diese Klasse besitzt bei mir die Eigenschaften X, Y und ID (ich denke es sollte klar sein, für was diese stehen).
VB.NET-Quellcode
- Public Class Tile
- Private _id As Integer, _x As Integer, _y As Integer
- Public Property ID() As Integer
- Get
- Return _id
- End Get
- Set
- _id = value
- End Set
- End Property
- Public ReadOnly Property X() As Integer
- Get
- Return _x
- End Get
- End Property
- Public ReadOnly Property Y() As Integer
- Get
- Return _y
- End Get
- End Property
- Public Sub New(id As Integer, x As Integer, y As Integer)
- _id = id
- _x = x
- _y = y
- End Sub
- End Class
Dann brauchen wir natürlich auch unsere Spielfigur. Diese ist bei mir ebenfalls eine Klasse.
Und zu guter Letzt wäre da natürlich auch noch die Map selber.
Bei mir sieht es so aus, dass die Map-Klasse einen Spieler, ein zweidimensionales Array von Tiles und auch ihre Breite und Höhe speichert. Zusätzlich soll man eine Map aus einer Datei erstellen und in eine Datei speichern können.
VB.NET-Quellcode
- Public Class Map
- Private _player As Player
- Private _width As Integer, _height As Integer
- Private tiles As Tile(,)
- Public ReadOnly Property Player() As Player
- Get
- Return _player
- End Get
- End Property
- Public ReadOnly Property Width() As Integer
- Get
- Return _width
- End Get
- End Property
- Public ReadOnly Property Height() As Integer
- Get
- Return _height
- End Get
- End Property
- Public Default ReadOnly Property Item(x As Integer, y As Integer) As Tile
- Get
- Return tiles(x, y)
- End Get
- End Property
- Public Sub New(path As String)
- End Sub
- Public Sub Save(path As String)
- End Sub
- End Class
C-Quellcode
- public class Map
- {
- Player _player;
- int _width, _height;
- Tile[,] tiles;
- public Player Player
- {
- get
- {
- return _player;
- }
- }
- public int Width
- {
- get
- {
- return _width;
- }
- }
- public int Height
- {
- get
- {
- return _height;
- }
- }
- public Tile this[int x, int y]
- {
- get
- {
- return tiles[x, y];
- }
- }
- public Map(string path)
- {
- }
- public void Save(string path)
- {
- }
- }
Jetzt können wir uns dem Format widmen, mit dem wir unsere Daten speichern wollen. Ich habe es hier wieder sehr einfach gehalten.
Ich habe mich für ein binäres Format entschieden. Dieses kann zwar nicht per Hand bearbeitet werden (theoretisch schon, mit einem HexEditor), sondern braucht einen Map-Editor (einen solchen sollte man aber sowieso immer haben, da es die Dinge ungemein vereinfacht), aber ist dafür leichter für das Programm zu lesen und spart außerdem Speicherplatz.
Die Datei kann man in zwei Teile zerlegen, den ersten Teil, der immer die selbe Länge hat, und den zweiten mit variabler Länge. Hier muss ich dazu sagen, dass dies wirklich die Variante für Arme ist. Man könnte die Datei auch in Chunks unterteilen, mit Headern und allem drum und dran, wodurch man um einiges flexibler wird, das wäre aber nicht mer anfängerfreundlich.
Im ersten Teil werden die Mapgröße und die Startposition der Spielfigur gespeichert. Dies sind immer 4 Werte und in unserem Fall Integer (also 4 Byte). Ich werde übrigens überall in der Datei Integer verwenden. Wenn ihr sehr große Dateien bei eurem Spiel erwartet, dann könnt ihr das auch je nach Bedarf zu 2 oder 1 Byte ändern. Geht dabei aber mit Bedacht vor, hab ihr euch einmal entschieden, gibt es kein Zurück, ändert ihr es so müsst ihr alle Dateien aufwändig konvertieren, oder sie werden unbrauchbar. Das genaue Pattern ist:
Im zweiten Teil werden die Tiles gespeichert. Um Speicherplatz zu sparen speichere ich nur die Tiles, die auch tatsächlich belegt sind. Tiles mit der ID 0 (also Luft) werden nicht gespeichert sondern nachher vom Programm automatisch generiert. Für einen einzelnen Tile sieht das Pattern so aus:
Da wir wissen, wie lang ein Tile ist (12 Byte bzw 4 Integer), können wir die Tiles ohne Probleme hintereinander in die Datei schreiben. Der Integer nach der ID von Tile 1 ist also die PositionX von Tile 2:
Das komplette Pattern für die Datei sieht damit so aus:
Das wars auch schon, jetzt können wir mit dem eigentlichen Programmieren anfangen.
Um die Datei auszulesen verwenden wir am besten einen BinaryReader, denn wir speichern unsere Daten ja auch binär. Nun sieht es schon mal so aus:
Den ersten Teil der Datei können wir sehr einfach auslesen. Einfach 4 mal
ReadInt32()
aufgerufen und wir haben die ersten 4 Integer aus der Datei. Diese können wir dann auch gleich auswerten.VB.NET-Quellcode
Jetzt bleiben nur noch die Tiles. Dafür müssen wir solange 3 Integer lesen, bis nicht mehr genug Bytes für 3 Integer in der Datei übrig sind. Man könnte auch einfach lesen, bis die Datei zu Ende ist, aber ich gehe hier lieber auf Nummer sicher. Das lässt sich ganz einfach mit einer Schleife lösen. Die 12 kommt daher, dass die Angaben in Bytes sind und ein Integer 4 Bytes besitzt, also 3 * 4 = 12.
VB.NET-Quellcode
- Public Sub New(path As String)
- Dim fi = New FileInfo(path)
- Using fs = fi.OpenRead()
- Using br = New BinaryReader(fs)
- _width = br.ReadInt32()
- _height = br.ReadInt32()
- _player = New Player(br.ReadInt32(), br.ReadInt32())
- tiles = New Tile(_width - 1, _height - 1) {}
- Do While fs.Length - fs.Position >= 12
- Dim posX As Integer = br.ReadInt32()
- Dim posY As Integer = br.ReadInt32()
- tiles(posX, posY) = New Tile(br.ReadInt32(), posX, posY)
- Loop
- End Using
- End Using
- End Sub
C-Quellcode
- public Map(string path)
- {
- var fi = new FileInfo(path);
- using (var fs = fi.OpenRead())
- {
- using (var br = new BinaryReader(fs))
- {
- _width = br.ReadInt32();
- _height = br.ReadInt32();
- _player = new Player(br.ReadInt32(), br.ReadInt32());
- tiles = new Tile[_width, _height];
- while (fs.Length - fs.Position >= 12)
- {
- int posX = br.ReadInt32();
- int posY = br.ReadInt32();
- tiles[posX, posY] = new Tile(br.ReadInt32(), posX, posY);
- }
- }
- }
- }
Jetzt dürfen wir aber nicht vergessen, dass wir noch alle restlichen Tiles, die nicht in der Datei gespeichert waren, mit Luft (also Tiles der ID 0) gefüllt werden müssen. Dafür gehen wir alle Arrayelemente durch und prüfen, ob diese Nothing/Null sind, und erstellen dann gegebenenfalls die Tiles:
VB.NET-Quellcode
- Public Sub New(path As String)
- Dim fi = New FileInfo(path)
- Using fs = fi.OpenRead()
- Using br = New BinaryReader(fs)
- _width = br.ReadInt32()
- _height = br.ReadInt32()
- _player = New Player(br.ReadInt32(), br.ReadInt32())
- tiles = New Tile(_width - 1, _height - 1) {}
- While fs.Length - fs.Position >= 12
- Dim posX As Integer = br.ReadInt32()
- Dim posY As Integer = br.ReadInt32()
- tiles(posX, posY) = New Tile(br.ReadInt32(), posX, posY)
- End While
- For x As Integer = 0 To _width - 1
- For y As Integer = 0 To _height - 1
- If tiles(x, y) Is Nothing Then
- tiles(x, y) = New Tile(0, x, y)
- End If
- Next
- Next
- End Using
- End Using
- End Sub
C-Quellcode
- public Map(string path)
- {
- var fi = new FileInfo(path);
- using (var fs = fi.OpenRead())
- {
- using (var br = new BinaryReader(fs))
- {
- _width = br.ReadInt32();
- _height = br.ReadInt32();
- _player = new Player(br.ReadInt32(), br.ReadInt32());
- tiles = new Tile[_width, _height];
- while (fs.Length - fs.Position >= 12)
- {
- int posX = br.ReadInt32();
- int posY = br.ReadInt32();
- tiles[posX, posY] = new Tile(br.ReadInt32(), posX, posY);
- }
- for (int x = 0; x < _width; x++)
- {
- for (int y = 0; y < _height; y++)
- {
- if (tiles[x, y] == null)
- tiles[x, y] = new Tile(0, x, y);
- }
- }
- }
- }
- }
Damit wäre das Einlesen auch schon geschafft, ihr seht also, gar nicht so viel Code.
Jetzt haben wir die Daten im Programm, wir wollen sie von dort aber auch wieder zurück in eine Datei bekommen.
Dafür gehen wir jetzt den umgekehrten Weg. Statt einem StreamReader nehmen wir diesmal einen StreamWriter, denn wir wollen ja was in die Datei schreiben. Dann können wir den ersten Teil wieder direkt erstellen, indem wir 4 mal
Write()
aufrufen. Die Tiles müssen wir wieder mit einer Schleife durchgehen und alle Tiles, die eine ID <> 0 besitzen, speichern wir. Die Save-Methode sieht demnach dann so aus:VB.NET-Quellcode
- Public Sub Save(path As String)
- Dim fi = New FileInfo(path)
- Using fs = fi.Open(FileMode.Create, FileAccess.Write)
- Using bw = New BinaryWriter(fs)
- bw.Write(_width)
- bw.Write(_height)
- bw.Write(_player.X)
- bw.Write(_player.Y)
- For x As Integer = 0 To _width - 1
- For y As Integer = 0 To _height - 1
- If tiles(x, y).ID <> 0 Then
- bw.Write(tiles(x, y).X)
- bw.Write(tiles(x, y).Y)
- bw.Write(tiles(x, y).ID)
- End If
- Next
- Next
- End Using
- End Using
- End Sub
C-Quellcode
- public void Save(string path)
- {
- var fi = new FileInfo(path);
- using (var fs = fi.Open(FileMode.Create, FileAccess.Write))
- {
- using (var bw = new BinaryWriter(fs))
- {
- bw.Write(_width);
- bw.Write(_height);
- bw.Write(_player.X);
- bw.Write(_player.Y);
- for (int x = 0; x < _width; x++)
- {
- for (int y = 0; y < _height; y++)
- {
- if (tiles[x, y].ID != 0)
- {
- bw.Write(tiles[x, y].X);
- bw.Write(tiles[x, y].Y);
- bw.Write(tiles[x, y].ID);
- }
- }
- }
- }
- }
- }
Damit geht auch dieses Tutorial zu Ende. Ich hoffe ich konnte euch alles gut vermitteln, bei Fragen stehe ich gerne zur Verfügung.
Anbei findet ihr auch noch die originale C#-Projektmappe.
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()