Jump to content
Unity Insider Forum

Save/Load


nordseekrabe

Recommended Posts

Moin, moin

habe annähernd "1000" Tutorials und Artikel zum Thema Save/Load "studiert" und bedingt verstanden. Mit einem für mich plausiblen Script aus dem Internet versuche ich nun, die in einer Szene geänderten Elemente zu speichern. Das sind überwiegend Booleans (aus tuerGeoeffnet = false wird tuerGeoeffnet = true), so ziemlich 60-80 Änderungen. Auch das Hochzählen der aufgenommenen Puzzleteile muss als int gespeichert werden. Mit Debug.Log sehe ich, dass im Speicher nach Aufnahme des Objekts "true" erscheint und anzahlPuzzle auf 1 steigt. Das Script schreibt auch eine Savefile, aber nach dem Laden ist leider wieder der ursprüngliche "Defaultinhalt". Der Inhalt der Savefile sieht so aus:  Puzzle16.dat und PuzzleZahl.dat

Die Komponenten habe ich in einer Serializable Datei "Item" gespeichert; als Beispiel public static int anzahlPuzzle = 0; und public static bool Puzzle16Genommen = false;

Im Anhang die Savedatei. Meine Verständnisfrage ist nun, wie greift diese Save<bool>(Puzzle16Genommen, "Puzzle16") auf den Speicher zu und wie lädt er dann hinterher wieder die Daten  in den Speicher ? Und fehlt da etwas im Script, dass das Ganze noch nicht klappt. Der Versuch, zu speichern, erfolgt unmittelbar nach Aufnahme des Objekts(Inventory.Instance.PlaceItemToInventory(hit.collider.gameObject) mit dem Befehl SaveLoad.save<bool>(Item.Puzzle16Genommen, "Puzzle16") und SaveLoad.save<int>(Item.anzahlPuzzle, "PuzzleZahl").

Wie immer bin ich für ein paar Richtungs weisende Hinweise und Hilfen sehr dankbar.

Grüsse von der heute sehr sonnigen Ostseeküste

nordseekrabbe (Peter) 

Puzzle16.dat PuzzleZahl.dat SaveLoad.cs

Link zu diesem Kommentar
Auf anderen Seiten teilen

Gruß,

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class SaveAndLoad : MonoBehaviour {

    public void Save (Object data, string filename) {
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create(Application.persistentDataPath + "Saves/" + filename + ".dat");

        data.score = score;
        data.levelcount = levelcount;

        bf.Serialize(file, data);
        file.Close();
    }
    public void Load (string filename) {
        if (File.Exists(Application.persistentDataPath + "/playerInfo.dat")) {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(Application.persistentDataPath + "Saves/" + filename + ".dat", FileMode.Open);
            PlayerData data = (PlayerData) bf.Deserialize(file);
            file.Close();

            score = data.score;
            levelcount = data.levelcount;

        }
    }
}

Habe dein Code jetzt nicht runtergeladen, am besten nutzt die Kopie & Paste und bei Beitrag bearbeiten das <> Symbol um deinen Code zu posten.

Diesen hier habe ich im Netz gefunden und dieser Sieht bis auf PlayerData (was ein Custom Script ist) ganz sinnvoll aus, laut dem Schreiber kann man mit diesem Code auf jeder Plattform seine Sachen speichern, vielleicht kann mir jemand dazu eine Info geben.

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 3 weeks later...

Ein gutes Neues Jahr für alle Leser,

noch kann ich nicht aufgeben, vielleicht erbarmt sich ja doch noch jemand, mir einen Hinweis für mein Save-Load-Problem zu geben. Falls man Sorge hat, fremde Dateien zu öffnen(???),

füge ich im Anhang den Code bei. Natürlich habe ich nicht die Hände in den Schoss gelegt und auf Eure Antwort gewartet. Wenn ich den Sachverhalt richtig verstehe, liegt der Fehler darin, dass das zu speichernde Element nicht "serializabel" ist: ich habe eine Klasse "public class Item", die ist [Serializable]. Sie enthält zahlreiche Booleans im Status false ( z.B. Zimmertuer_offen = false). In der Klasse "Object_Kombinieren" wird diese Boolean in true verändert, da die Tuer nun vom Spieler mit einem gefundenen Schluessel geöffnet wurde. Diesen Fortschritt will/muss ich nun speichern, da der Spieler ja nicht ständig die Tür neu begehbar machen will. Dazu nehme ich in der Klasse "Object_Kombinieren" das Property Item.Zimmertuer_offen = true. Das funktioniert nur, wenn Zimmertuer_offen "static" ist. Das darf ich aber nicht (Serialize-Rule). Also muss ich in der Klasse "Object_Kombinieren" einen Objektverweis erstellen (Item tuer_oeffnen;) und "tuer_oeffnen.Zimmertuer_offen = true" eingeben. Und hier, mein ich, liegt der Fehler: "tuer_oeffnen.Zimmertuer_offen = true" ist doch nun nicht mehr serializable !!?? Oder vielleicht doch? Mit Debug.Log sehe ich, dass auf true geändert wurde, aber die Änderung wird in der Speicherung nicht erfasst (siehe Anhang). 

Nach wie vor hoffe ich auf (kleine) Hilfen und wünsche ein erholsames Wochenende

nordseekrabbe (Peter)

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;


public class SaveLoad : MonoBehaviour
{
    public static void SaveToFile<T>(T objectToSave, string saveName)
    {
        
        string path = Application.dataPath + "/SaveGames/";
        
        Directory.CreateDirectory(path);
        BinaryFormatter formatter = new BinaryFormatter();
        FileStream fs = new FileStream(path + saveName + ".dat", FileMode.Create);

        try
        {
            formatter.Serialize(fs, objectToSave);
            //Debug.Log("Der Wert des Objekts ist  " + objectToSave);
            //Debug.Log("was hat fs als Wert ? " + fs.ToString());
        }
        catch(SerializationException exception)
        {
             Debug.Log("Save failed. Error: " + exception.Message);
        }
        finally
        {
            fs.Close();
        }
    }

    public static T Load<T>(string saveName)   //key)
    {
        string path = Application.dataPath + "/SaveGames/";
        BinaryFormatter formatter = new BinaryFormatter();
        FileStream fs = new FileStream(path + saveName + ".dat", FileMode.Open);
        T returnValue = default(T);

        try 
        { 
            returnValue = (T)formatter.Deserialize(fs);
        }
        catch(SerializationException exception)
        {
            Debug.Log("Load faied. Error: " + exception.Message);
        }
        finally
        {
            fs.Close();
        }
        return returnValue;
        
    }

Und die Save.dat : []   yyyy[]       [] []  System.Boolean[]        m_value [] []

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 2 weeks later...

Seit längerer Zeit grübele ich darüber, warum auf manche Anfragen im Forum keine Reaktion erfolgt. Ist die Frage idiotisch? Ist das Thema heikel oder nicht erlaubt? Ist der Fragende einfach nicht "angenommen"?  Oder, oder. Für viele Hobby-Developer muss doch Save and Load ein wichtiges Thema sein ? Nicht umsonst findet man "unendlich" viele (mehr oder weniger) hilfreiche Videos, Tutorials oder Codeschnipsel im Internet.

Einen kleinen Schritt bin ich weiter: Mit dem Code aus dem Buch "Unity Game Development Cookbook" von Paris Buttfield-Addison kann ich jetzt schon einmal die laufende Szene speichern. Das hilft u.a. auch bei einer Pause, die der Spieler machen möchte. Leider funktioniert aber die Objektspeicherung in diesem Skript bei mir nicht. Vermutlich führt auch hier die static/Objektverweis - Konstellation zu keinem erforderlichen MonoBehaviour-Objekt. Das Skript ist für LitJSON und wäre eigentlich eine gute Lösung.

Jetzt einfach noch einmal die Frage: Was ist im obigen Script falsch, was fehlt oder wo ist der falsche Ansatz ? In der Klasse "Item" (Serializable ) sind die Objekte wie "public int anzahlPuzzleTeile" oder "public bool puzzle16Genommen" deklariert. In der Klasse "ObjektNehmen" wird nach Aufnahme des Objekts(Puzzle16) in die Inventory die Variable "anzahlPuzzleTeile" um 1 erhöht,  "puzzle16Genommen" wird true. Für "Item anzahlPuzzleTeile" muss in der Klasse "ObjektNehmen" ein Objektverweis erfolgen (Item puzzleZahl, in Start: puzzleZahl = GetComponent<Item>()  ). Nun Aufruf "puzzleZahl.anzahlPuzzleTeile += puzzleZahl.anzahlPuzzleTeile" und "SaveLoad.SaveToFile<int>(puzzleZahl.anzahlPuzzleTeile, "AnzahlPuzzles"); ". Das war's. Funktioniert leider nicht mit o.g. Save-Skript. Aber warum ? Noch einmal die Bitte, was mache ich falsch ?

Gruß nordseekrabbe (Peter) 

   

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich hatte neulich auch noch ein ähnliches Problem. Bei mir ging es jedoch darum, Listen vom Typen Scriptable Object zu speichern. Es lief ähnlich ab wie bei dir - ich konnte meine Dateien im Savefile speichern, hatte jedoch Schwierigkeiten die Dateien wieder ordnungsgemäß zu importieren, bzw. zu laden. Ich habe mich dazu entschieden, das Problem erstmal bei Seite zu schieben, da ich mich zu lange mit dem Thema aufgehalten habe und schon fast die Freude am Projekt verloren habe. Meine Recherche hat mir zumindest die Antwort gegeben, dass bestimmte Typen nicht serializable sind und man einen kleinen Umweg gehen muss, um das Resultat zu erzielen, was man sich wünscht. Es wurde häufiger vom "Wrapping" und von Helferklassen gesprochen. Ich kann dir leider keine Antwort auf deine Frage geben, wollte dich aber fragen, ob du schon versucht hast, deine Dateien mit JsonUtility zu speichern? Mit JSON habe ich bisher immer gute Erfahrungen gesammelt.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke für Deine Reaktion. Wie oben erwähnt, habe ich mit LitJson die Speicherung der aktuellen Szene erzielen können. Leider funktioniert aber die Speicherung der Objekte nicht. Ich füge 'mal das Skript aus dem genannten Buch bei.

using LitJson;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;

public interface ISaveable
{
    string SaveID { get; }
    JsonData SavedData { get; } 
    void LoadFromData(JsonData data);
    
}

public static class SavingService
{
    private const string ACTIVE_SCENE_KEY = "activeScene";
    private const string SCENES_KEY = "scenes";
    private const string OBJECTS_KEY = "objects";
    private const string SAVEID_KEY = "$saveID";
    
    static UnityEngine.Events.UnityAction<Scene, LoadSceneMode> LoadObjectsAfterSceneLoad;

    public static void SaveGame(string fileName)
    {
        var result = new JsonData();
        //Save the objects   ........................... Dieser Objektteil funktioniert nicht
        var allSaveableObjects = Object.FindObjectsOfType<MonoBehaviour>().OfType<ISaveable>(); 
        
        if (allSaveableObjects.Count() > 0)
        {
            var savedObjects = new JsonData();
            foreach (var saveableObject in allSaveableObjects)
            {
                var data = saveableObject.SavedData;
                if (data.IsObject)
                {
                    data[SAVEID_KEY] = saveableObject.SaveID;
                    savedObjects.Add(data);
                }
                else 
                {
                    var behaviour = saveableObject as MonoBehaviour;
                    Debug.LogWarningFormat(behaviour, "{0} 's save data is not a dictionary. The " +
                        "object was not saved.", behaviour.name);
                }
            }
            result[OBJECTS_KEY] = savedObjects;
        }
        else
        {
            Debug.LogWarningFormat("The scene did not include any saveable objects.");
        }
        
        // save the scene(s)      ..................Dieser Abschnitt funktioniert
        var openScenes = new JsonData();
        var sceneCount = SceneManager.sceneCount;

        for (int i = 0; i < sceneCount; i++)
        {
            var scene = SceneManager.GetSceneAt(i);
            openScenes.Add(scene.name);
        }
        result[SCENES_KEY] = openScenes;
        result[ACTIVE_SCENE_KEY] = SceneManager.GetActiveScene().name;
        
        var outputPath = Path.Combine(Application.dataPath + "/MySaveGames/", fileName);
        var writer = new JsonWriter()
        {
            PrettyPrint = true
        };
        result.ToJson(writer);
        File.WriteAllText(outputPath, writer.ToString());
        Debug.LogFormat("Wrote saved game to {0}", outputPath);
        //result = null;
        System.GC.Collect();
    }

Zum Einlesen ist das zunächst nur der Save-Teil

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 5 weeks later...

Me too! 4 Jahre lang konnte ich meine Sachen nicht laden, weil ich das Thema immer nur vor mir her geschoben habe. 

Playerprefs lehnte ich ab. Und XML war auch nicht so mein Ding. Json ist ganz toll!
Entweder man packt die Class oder auch Struct in eine List. Oder man verwendet Delegate und Events , das dann gleichzeitig alle Klassen die man speichern möchte , speichert.
Vektoren müssen immer in float umgewandelt werden. Farben werden in String umgewandelt. Die Klasse selbst muss auch immer das Attirbut [System.Serializable] haben.

 

 public static void SaveHeroSystem()
   {
      
      FileStream file = new FileStream(Application.persistentDataPath + "/HeroSystem.dat", FileMode.OpenOrCreate);
      BinaryFormatter formatter = new BinaryFormatter();
      formatter.Serialize(file,HerosSystem.Instance.HerosList);
      // hier kannst du dann weitere Klassen hinzufügen
      file.Close();
      
      
   }



private void LoadHeroSystem()
   {
      if (!File.Exists(Application.persistentDataPath + "/HeroSystem.dat"))
         return;
      else
      {
         FileStream file = new FileStream(Application.persistentDataPath + "/HeroSystem.dat",FileMode.Open);
         BinaryFormatter formatter = new BinaryFormatter();
         HerosSystem.Instance.HerosList = (List<Heros>)formatter.Deserialize(file);
         // hier gegebenenfalls weitere Klassen
         file.Close();
      }
   }
           
           
           
 // Hier mein Struct :

using UnityEngine;
namespace Rod
{
   
   [System.Serializable]
   public struct Heros 
   {
      
      public int heroID;
      public string surName;
      public string lastName;
      public string imagePath;
      public int listTypeID;
      
   }
}
         

 


 

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Rod;

public class SaveManager : MonoBehaviour
{
   public static SaveManager Instance;
   
   
   void Akake()
   {
      Instance = this;
      DontDestroyOnLoad(this);
      
   }
   
   void Start()
   {
      
      Load();
   }
   
   
   
   public void Save()
   {
     
      SaveHeroSystem();
      // weitere
   }
   
   public void Load()
   {
      
      LoadHeroSystem();
	  // weitere
   }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin Hermetes,

super, dass Du nochmals auf diese Anfrage reagierst. Habe tatsächlich bei dem beschriebenen Projekt eine Pause eingelegt, da nur schon das komplette Spiel durchzuprüfen, ohne ein Save and Load kaum machbar ist (wenn in der 17.Szene ein Fehler sich findet, den ausbügeln und dann noch einmal alle 17 Szenen durcharbeiten usw. nein, das will ich nicht). Dein oben beschriebener Code lässt sich für mich schlecht als Json-Save-Load erkennen, ich denke , das ist doch eine "normale" Binary-Formatter Serialization. Oder verstehe ich da etwas falsch? 

Aber egal, zum guten Teil gelingt mir ja inzwischen das "Saven". Leider tritt beim Laden das Problem auf, das zwar die gespeicherte Szene im Display gezeigt wird, aber nicht aktiviert werden kann und somit auch die gespeicherten Objekte nicht in der Szene  erscheinen. Wie ich mich belesen habe, ist das wohl ein Problem der noch nicht vollständig geladenen (90%) Szene und dem dann "zu frühen" Versuch bereits Objekt zu laden (oder so ähnlich, kann das nicht fachmännisch erklären). Es gibt hierfür von Unity schon Empfehlungen, wie das im Scripting verarbeitet werden kann. Das setzt aber eine MonoBehaviour-Datei voraus und meine Save/Load ist kein MonoBehaviour-Code. Also habe ich erst einmal "gesteckt" und mache in diesem Projekt eine schöpferische Pause,  bis ich noch mehr gelernt habe. Aber dennoch 1000 Dank für Deinen Versuch, mir zu helfen. Bin immer ziemlich frustriert, wenn bei Anfragen keine Reaktion kommt. So ein Forum ist doch wohl auch für Anfänger gedacht und dann kommen halt ziemlich doofe Fragen, oder? Wenn ich Fachmann wäre, hätte ich viele Kollegen, die ich befragen und das Problem fachmännisch diskutieren könnte. Also ist so eine kleine Anregung unter Umständen sehr nützlich, mit seinem Problem einen Schritt weiterzukommen, auch wenn furchtbar viel Basiswissen und vor allem Erfahrung fehlt.

Nochmals Dank und Gruss

Nordseekrabbe

Link zu diesem Kommentar
Auf anderen Seiten teilen

Moin Nordseekrabbe :) Richtig es ist Binary, natürlich.

Du kannst die Reihenfolge der Scripte sortieren. So das die Awake Methoden eine Ordnung haben. ProjectSettings - Script Execution Order.
Und es gibt eine Methode die man in etwa so aufrufen kann: if (Szene.Loaded) ....weiss leider nicht auswendig. 
Es gibt kein Monobehaviour Code. Das ist nur die Vererbung das mit einem Doppelpunkt gesetzt wird.

public class SaveManager : MonoBehaviour

 

public class SaveManager


Ich denke das Sascha, unser Admin, gerade viel um die Ohren hat. Ohne dieses Forum hätte ich es längst gesteckt. Sascha, Manfred und andere kamen nach 40 Minuten schon mit einer Antwort. Teilweise schrieben sie ganze Scripte für mich. Und dabei kam ich noch mit viel blöderen Fragen daher! Also ich hab die alle schon durch :D  Wenn andere Dir geantwortet hätten, hätte ich mich auch zurück gehalten. Aber so fühlte ich mich richtig zurück....als ich noch auf Antworten hoffte. 

Grüße! :)

 

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...