Konzept zum Speichern & Laden von Kartenaufgaben

In diesem Forum geht es um Algorithmen und Verfahren in der Spieleentwicklung, die sprachunabhängig sind, sowie um allgemeine Konzepte, Verfahren und Techniken aus und in der Spieleentwicklung.
Bradley
Beiträge: 235
Registriert: 03.02.2013 19:05

Konzept zum Speichern & Laden von Kartenaufgaben

Beitragvon Bradley » 26.08.2016 18:29

Ich brauche mal Beratung bei einem Thema.

Für die Karte in Space Opera verwende ich ein 2-Dimensionals Array, das ich mit einer Klasse befülle.
Diese Klasse Speichert mir alle Daten über das Feld, also Koordinaten, was für eine Art von Kartenfeld es ist (Asteroid, Sonne, Leerer Weltraum etc.).
Dazu noch ein paar andere Werte, ob der Spieler schon mal auf dem Feld war, oder ob irgendwelche Strukturen dort sind.
Wie speichere ich so was am sinnvollsten ? und wie lade ich das gespeicherte wieder?

Bradley
Beiträge: 235
Registriert: 03.02.2013 19:05

Re: Konzept zum Speichern & Laden von Kartenaufgaben

Beitragvon Bradley » 28.08.2016 12:36

Okay hab das ganze mal mit einem XML Mapping gelößt.
Gespeichert bekomme ich Daten auch wunder, nur mit dem Auslesen habe ich da einige Problem.
Hier mal der Code vom Prototypen:

Code: Alles auswählen

class Program
    {
        MapField[,] arraytest = new MapField[2,2];

        static void Main(string[] args)
        {
            Program obj = new Program();

            Speichern(obj);

            Laden();

        }

        private static void Laden()
        {
            List<MapField> list = new List<MapField>();

            XmlSerializer xmlSerializer = new XmlSerializer(typeof(MapField));
            StreamReader sr = new StreamReader(@"test.xml");


            list.Add((MapField)xmlSerializer.Deserialize(sr));

            sr.Close();
        }

        private static void Speichern(Program obj)
        {
            for (int x = 0; x < 2; x++)
            {
                for (int y = 0; y < 2; y++)
                {
                    obj.arraytest[x, y] = new MapField();

                    obj.arraytest[x, y].Type = 1;
                    obj.arraytest[x, y].Visibile = false;
                    obj.arraytest[x, y].X = x;
                    obj.arraytest[x, y].Y = y;


                }
            }


            XmlSerializer xmlser = new XmlSerializer(typeof(MapField));
            StreamWriter sw = new StreamWriter("test.xml");

            foreach (var item in obj.arraytest)
            {
                xmlser.Serialize(sw, item);

            }
            sw.Close();
         }
    }

    [XmlRoot(ElementName = "MapField")]
    public class MapField
    {
        [XmlElement(ElementName = "X")]
        public int X { get; set; }
        [XmlElement(ElementName = "Y")]
        public int Y { get; set; }

        [XmlElement(ElementName = "Type")]
        public int Type { get; set; }
        [XmlElement(ElementName = "Visibile")]
        public bool Visibile { get; set; }
    }


In der XML sieht das ganze dann so aus:

XML-Code:

Code: Alles auswählen

<?xml version="1.0" encoding="utf-8"?>
<MapField xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <X>0</X>
  <Y>0</Y>
  <Type>1</Type>
  <Visibile>false</Visibile>
</MapField><?xml version="1.0" encoding="utf-8"?>
<MapField xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <X>0</X>
  <Y>1</Y>
  <Type>1</Type>
  <Visibile>false</Visibile>
</MapField><?xml version="1.0" encoding="utf-8"?>
<MapField xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <X>1</X>
  <Y>0</Y>
  <Type>1</Type>
  <Visibile>false</Visibile>
</MapField><?xml version="1.0" encoding="utf-8"?>
<MapField xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <X>1</X>
  <Y>1</Y>
  <Type>1</Type>
  <Visibile>false</Visibile>
</MapField>


Da die Serialisation von selbst keine Arrays unterstützen, war mein letzter Gedanke der, das ich alles zuerst in eine Liste Packe und von dort aus wieder ein neues Array befüllen.

Leider scheitere ich daran.
Jemand eine Idee wie ich das lösen kann?

Benutzeravatar
NeoArmageddon
Beiträge: 1165
Registriert: 13.02.2012 20:34
Wohnort: Göttingen
Kontaktdaten:

Re: Konzept zum Speichern & Laden von Kartenaufgaben

Beitragvon NeoArmageddon » 28.08.2016 23:24

Heyho Bradley,

dein Ansatz mit XML klappt natürlich und ist zum testen auch ganz praktisch (du kannst einfach die XML-Speicherstände bearbeiten), allerdings bringt das auch einige Probleme mit sich:

  • Der XML Speicherstand muss geparsed werden, was in der Regel langsam ist.
  • Die Speicherstände sind für jeden klar sichtbar, bedeutet ein User kann einfach Schummeln, indem er die Datei editiert (kann auch gewollt sein, Thema Modding).
  • Die Datei enthält viel Zeug was du eigentlich nicht brauchst. Dadurch wird sie recht schnell sehr groß.

Einfaches Beispiel zum letzten Punkt: Du speicherst einen einfachen Int-Wert (z.B. 42), der sollte im Speicher 4 Byte groß sein. Jetzt speicherst du den in deiner XML Datei als <IrgendeinWert>42</IrgendeinWert>. Wie groß ist deine Datei damit? 132 Byte. Warum das? Weil jedes Zeichen einem char mit 4 Byte größe entspricht. Selbst für die 42 bedeutet das, in der Datei wird sie mit 8 statt 4 Byte abgelegt. Noch schlimmer wird es bei float und double. Je nachdem wie groß deine Arrays sind, kann dein Speicherstand schnell mehrere MB groß werden, obwohl die Rohdaten nur wenige kb sind.

Eine gängige Lösung ist es, deine Daten nach einem festen Muster in eine binäre Datei zu schreiben. Du schreibst quasi direkt die Repräsentation deiner Daten aus dem Arbeitsspeicher in eine Datei. Nicht nur ist deine Speicherdatei dann genau so groß, wie sie für die Daten sein muss, sie ist auch recht gut vor ungewünschter Manipulation geschützt und aus ihr kannst du die Daten auch direkt wieder herstellen.

Hier mal ein Beispiel:

Code: Alles auswählen

 private static void Speichern(Program obj)
    {
        int version = 42;
        using (BinaryWriter writer = new BinaryWriter(File.Open("MyMap.mmf", FileMode.Create)))
        {
            writer.Write("MMF"); //Ersten 3 Zeichen in der Datei beschrieben die Datei. Hier ist es "MyMapFile". Dadurch kann man beim lesne schnell prüfen ob die Datei das richtige Format hat.
            writer.Write(version); //Eine Versionsnummer. Du wirst später deine Speicherdaten erweitern wodurch alte nicht mehr ohne Aufwand geladen werden können.
            int xMax = 2;
            int yMax = 2;
            //Schreibe die Grenzen der Karte in die Datei
            writer.Write(xMax);
            writer.Write(yMax);
            for (int x = 0; x < xMax; x++)
            {
                for (int y = 0; y < yMax; y++)
                {
                     writer.Write(1); //Schreibe für jedes Feld ein paar Werte
                   writer.Write(false);
                   writer.Write(x);
                   writer.Write(y);
                   }
            }
      writer.Write('\0');//Ich schreibe noch den Null-Charakter in die Datei. Dann weiß man später das die Datei hier definitiv zu Ende ist.
        }
    }


Und hier wird die Datei wieder geladen:

Code: Alles auswählen

] private static void Speichern(Program obj)
 {
 if (File.Exists("MyMap.mmf"))
        {
            using (BinaryReader reader = new BinaryReader(File.Open("MyMap.mmf", FileMode.Open)))
            {
                var chars = reader.ReadString(); //Ersten drei Zeichen lesen
                if(chars == ""MMF")
                {
                var version = reader.ReadInt32();
                 var xMax = reader.ReadInt32();
                  var yMax = reader.ReadInt32();
                  for (int x = 0; x < xMax; x++)
                   {
                    for (int y = 0; y < yMax; y++)
                    {   
                          var typ = reader.ReadInt32();
                          var abool = reader.Bool();
                          var x = reader.ReadInt32();
                         var y = reader.ReadInt32();
                         //Hier die Feldinfos wieder in ein Array einfügen oder so
                    }
                 }
            }
        }


Der Trick ist: Du hast zwar keine Infos in der Datei, welche Werte dort was darstellen, aber da du sie nach einem festen Schema hineingeschrieben hast, kannst du sie nach dem selben Schema wieder auslesen und verwerten.

Im Grunde funktionieren so alle Dateien, welche kein Textdateien (txt, xml, html, csv, etc) sind.

Hoffe das hilft dir weiter.

Bradley
Beiträge: 235
Registriert: 03.02.2013 19:05

Re: Konzept zum Speichern & Laden von Kartenaufgaben

Beitragvon Bradley » 28.08.2016 23:36

Hi Neo,

erstmal schön das du auch noch da bist :D
Ich möchte das tatsächlich in XML speichern, eben aus Gründen eines irgendwann geplanten Moddings.
Größe der Datei ist für erstmal tatsächlich nebensächlich, auch wenn du recht hast das da sehr schnell ziemlich große MB Zahlen zusammen kommen können.

Mir geht`s auch darum generell zu verstehen wie man eine solche Konstallation wieder ins Spiel zurück bekommt.

Benutzeravatar
NeoArmageddon
Beiträge: 1165
Registriert: 13.02.2012 20:34
Wohnort: Göttingen
Kontaktdaten:

Re: Konzept zum Speichern & Laden von Kartenaufgaben

Beitragvon NeoArmageddon » 29.08.2016 09:35

Mit einem passenden Tool ist natürlich auch die binäre Karte modbar.

Aber zu XML:
Deine Formatierung ist üben kaputt. Jedes Mal wenn du Serialize aufrufst, wird eine Zeichenfolge für EINE XML-Datei geschrieben. Da du das aber für jedes Feld in deinem Array ausführst, bekommst du eine XML-Datei die nicht Standardkonform ist. Wenn du XML nutzen willst, solltest du das vermeiden.

Der Weg um das Problem: Du rufst nur einmal Serialize auf einem einzigen Objekt auf, welches bereits alle Informationen zu deiner gespeicherten Karte enthält. Also statt über deinen MapField-Array zu iterieren, solltest du ein einzelnes Objekt, z.B. Map oder MapState serialisieren. Der große Vorteil: Du kannst in dieser Klasse dann direkt Methoden definieren, welche dir es später erleichtern, wieder auf die Elemente zu zugreifen.

Du könntest das so machen:

Code: Alles auswählen

[XmlRoot(ElementName = "MapState")]
   public class MapState{
      [XmlArray("MapFields")]
      public MapField[] Fields;
      public MapField getField(int x, int y) {//Hier zeug zum Zugriff};
      //Und andere Methoden
   }
   public class MapField
   {
      [XmlElement(ElementName = "X")]
      public int X { get; set; }
      [XmlElement(ElementName = "Y")]
      public int Y { get; set; }

      [XmlElement(ElementName = "Type")]
      public int Type { get; set; }
      [XmlElement(ElementName = "Visibile")]
      public bool Visibile { get; set; }
   }


Da sollte dann am Ende sowas bei raus kommen:

Code: Alles auswählen

<?xml version="1.0" encoding="utf-8"?>
<MapState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <NumFields>4</NumFields>
   <MapFields>
      <MapField>
         <X>0</X>
         <Y>0</Y>
         <Type>1</Type>
         <Visibile>false</Visibile>
      </MapField>
      <MapField>
         <X>0</X>
         <Y>1</Y>
         <Type>1</Type>
         <Visibile>false</Visibile>
      </MapField>
      <MapField>
         <X>1</X>
         <Y>0</Y>
         <Type>1</Type>
         <Visibile>false</Visibile>
      </MapField>
      <MapField>
         <X>1</X>
         <Y>1</Y>
         <Type>1</Type>
         <Visibile>false</Visibile>
      </MapField>
   </MapFields>
</MapState>


Du kannst (und solltest) die MapState-Klasse dann auch um weitere Metainformationen ergänzen, welche dir später beim Wiederherstellen der Karte hift. In meinem Beispiel habe ich zum Beispiel die Anzahl der Felder ergänzt (sinnvoller wäre wohl die Dimension der Karte). Du solltest dann beim Laden zum Beispiel checken, ob die XML genug Felder enthält. Ein User könnte ja einfach einen Eintrag gelöscht haben, z.B. ein Feld mit einem Gegner, den er nicht besiegen konnte. Ohne Behandlung von solchen fällen, würde deine Karte durcheinander kommen, wenn auf einmal nur 3 statt 4 Felder nach dem Laden im Array wären.
Und natürlich kannst du noch andere Sachen wie Spielerposition, andere Schiffe, abgelegte Items, Punktestand, etc in dieser Datei ablegen.

Bradley
Beiträge: 235
Registriert: 03.02.2013 19:05

Re: Konzept zum Speichern & Laden von Kartenaufgaben

Beitragvon Bradley » 29.08.2016 10:04

Okay ich verstehe was du meinst mit dem Serialisieren.
Ich muss mir deinen code nochmal in ruhe anschauen, denke aber das ich das übernehmen werden.

Vielen dank :)


Zurück zu „Algorithmen, Konzepte und Techniken“

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast