Jump to content
Unity Insider Forum
Sign in to follow this  
Kojote

Größere Datenmengen speichern

Recommended Posts

Grüße, da bin ich wieder! :D

Ich grübel nu schon seit zwei Tagen, wie ich mein Speichern- und Ladescript schreibe. Im Letzten Spiel, habe ich mir eine Ladestructur geschrieben, waren nur 12 Variablen und die dann über BinaryFormatter in eine Dat-Datei geschrieben. 6 mal Positionsdaten und noch bissl Krempelei. Bei diesem Spiel wird das Speichern und Laden etwas anspruchsvoller.

Im Spiel befindet sich ein Spieler und mehrere dutzend Gegner. Für jeden Gegner und auch für den Spieler an sich, muss ich die Position, die Rotation, Lebenspunkte, ect. speichern und dann irgendwann mal wieder laden.

Schnell kommt man auf die Idee einfach hier eine Speicherklasse zu schreiben, jeder Gegner bekommt dann ein GameObjkekt dieser Klasse, in das er seine Zustände schreibt.

Frage ist nun, wenn ich auf Speichern klicke, wie komme ich an alle Daten, wie sammel ich dir mir?

Und im umgekehrten Fall, wie entwirre ich den Haufen Variablen wieder beim Laden?

Grüße von Kojote

Share this post


Link to post
Share on other sites

Die Idee, dass jedes Objekt seine Daten hält, finde ich gut. Hierzu benötigt natürlich jedes Objekt seine eigene Guid oder ID oder was auch immer für die Zuweisung. Beim Speichern gäbe es verschiedene Wege. Du könntest alle Objekte mit https://docs.unity3d.com/ScriptReference/Object.FindObjectsOfType.html suchen (diese müssen zu der Zeit aktiv sein). Du könntest aber auch die Objekte bei dem Speicher-Manager registrieren und beim Request die Daten liefern (gibt aber eine bestimmte Abhängigkeit).

//deine datenstruktur
[Serializable]
public class PlayerData
{
  public int id;
  public posX, posY, posZ;
}

//deine mono behaviour
public PlayerData playerData = new PlayerData();

//speicher manager
var arr = FindObjectsOfType<DeineMonoBehaviourType>();
var savegameList = new List<PlayerData>();
  
foreach(var x in arr)
{
  savegameList.Add(x.playerData);
}
  
//save

Sowas in der Art.

  • Thanks 1

Share this post


Link to post
Share on other sites

Daran hab ich auch schon gedacht, aber würde das nicht zu viele Resourcen futtern, wenn ich im gesamten Spiel die Suche nach bestimmten Objekten starte?

Da kommt mir gerade eine Idee, der Speichermanager wäre denke ich das beste. Man müsste halte im Speichermanager eine List<> haben, Array würde dafür nicht gehen. Sobald ein Objekt bzw. Gegner erstellt wird, meldet er sich beim Speichermanager an, es folgt ein "Add" in die Liste. Wird der Gegener zerstört, "Delete" und meldet sich damit wieder ab.

Beim Speichern müsste man dann nur noch die List<> aberbeiten und die aktuellen Daten holen und diese dann speichern.

Beim Laden wäre es dann genau andersherum. Über den Speichermanager müsste alle Spieleobjekte neu instanzieren.

Wäre denke ich die beste Möglichkeit und dürfte auch wenig Resourcen futtern.

Share this post


Link to post
Share on other sites

Es kommt darauf an, wie oft du speicherst und wie viele Objekte du hast. Aber um mal einen Batzen™ zu speichern, sollte das, was Ressourcen angeht, egal sein.

Share this post


Link to post
Share on other sites

Du musst das Rad aber auch nicht neu erfinden. Gibt eingebaute Xml- oder sonst auch Json-Bilbiotheken, da musst du nur deine Datenklassen mit Attributen bestücken ("das hier soll bitte mitgespeichert werden") und dann knallst du dein Objekt in einen Serializer und der spuckt dir korrektes Xml oder Json oder wasauchimmer aus.

Das hier kann man z.B. benutzen, auch wenn ich gehört habe, dass es inzwischen einige neuere Pakete gibt. So oder so kannst du dir ja mal oben rechts auf der Seite ansehen, wie einfach das dann aussieht.

  • Like 1

Share this post


Link to post
Share on other sites

Microsoft hat für .NET core 3 einen neuen JSON serializer Entwickelt https://docs.microsoft.com/en-us/dotnet/api/system.text.json?view=netcore-3.0 und soll in ASP.NET core 3 newtonsoft JSON ersetzen, ist schneller und weniger GC behaftet.

Wird mal Zeit dass .NET core in Unity einzughält, wäre auch um einiges schneller als Mono.

.NET 5 soll am ende eh Mono, .NET CORE vereinen.

Share this post


Link to post
Share on other sites

Ich wollt mir heute mal JSON ansehen und in ein Testprogramm einfügen. Version wäre Newtonsoft.Json-12.0.2 und bekomme den Error hier:

TCoBFL95.jpg

Habe nun Versucht noch Version 8-11 zu installieren, alles Fehlanzeigen und habe nun das hier gefunden:

https://assetstore.unity.com/packages/tools/input-management/json-net-for-unity-11347

Wäre nun das hier das richtige, oder?

 

Share this post


Link to post
Share on other sites

Hab mich nun mal eine Weile mit JSON beschäftigt, sieht schonmal ganz gut aus:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using System.IO;


[System.Serializable]
public class Chracter{
    public int armor;
    public int power;
}

public class JSONTest : MonoBehaviour {

    private string fileName = "GameData.json";
    private string filePath;

    private static JSONTest instance;

    public Chracter chara;

    public JSONTest Instance {
        get {
            return instance;
        }
    }

    private void Awake() {
        filePath = Application.dataPath + "/Save/" + fileName;
    }

    void Start () {
        Chracter chara = new Chracter();
        chara.armor = 10;
        chara.power = 100;
        SaveGameData();
        LoadGameData();
    }

    public void SaveGameData() {
        string json = JsonUtility.ToJson(chara);
        if (!File.Exists(filePath)) {
            File.Create(filePath).Dispose();
        }
        File.WriteAllText(filePath, json);
    }

    public void LoadGameData() {
        string json;
        if (File.Exists(filePath)) {
            json = File.ReadAllText(filePath);
            chara = JsonUtility.FromJson<Chracter>(json);
        } else {
            Debug.Log("File is missing: " + filePath);
        }
        Debug.Log(chara.armor.ToString() + " " + chara.power.ToString());
    }
}

Frage wäre, wie speichere ich nun alles zusammen ab. Am besten eine Liste, wie ich es mir überlegt habe, wo dann alle Enemys enthalten sind?

Sagen wir, ich leg mir ne List an, mit GameObjects. Jetzt speichere ich ja nur ein GameObject, wie ist dass dann bei vielen?

Umgekehrt, wenn ich wieder lade. Wie erstelle ich dann die gesamten Objekte wieder? Mit Instanziate, sprich Prefab erstellen?

Ich habs mal so probiert:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using System.IO;

namespace JSONTest_2 {
    [System.Serializable]
    public class Charcter {
        public int armor = 0;
        public int power = 0;
    }

    [System.Serializable]
    public class Enemy {
        public int armor = 0;
        public int power = 0;
    }

    [System.Serializable]
    public class ObjectList {
        public List<Charcter> characterList = new List<Charcter>();
        public List<Enemy> enemyList = new List<Enemy>();
    }

    public class JSONTest_2 : MonoBehaviour {
        private string fileName = "GameData.json";
        private string filePath;

        public ObjectList objectList = new ObjectList();


        private void Awake() {
            filePath = Application.dataPath + "/Save/" + fileName;
        }

        void Start() {
            Charcter chara_1 = new Charcter();
            Charcter chara_2 = new Charcter();
            Charcter chara_3 = new Charcter();

            Charcter enemy_1 = new Charcter();
            Charcter enemy_2 = new Charcter();
            Charcter enemy_3 = new Charcter();

            objectList.characterList.Add(chara_1);
            objectList.characterList.Add(chara_2);
            objectList.characterList.Add(chara_3);

            objectList.characterList.Add(enemy_1);
            objectList.characterList.Add(enemy_2);
            objectList.characterList.Add(enemy_3);

            SaveGameData();
            LoadGameData();
        }

        public void SaveGameData() {
            string json = JsonUtility.ToJson(objectList);
            if (!File.Exists(filePath)) {
                File.Create(filePath).Dispose();
            }
            File.WriteAllText(filePath, json);
        }

        public void LoadGameData() {
            string json;
            if (File.Exists(filePath)) {
                json = File.ReadAllText(filePath);
                objectList = JsonUtility.FromJson<ObjectList>(json);
            } else {
                Debug.Log("File is missing: " + filePath);
            }
        }
    }
}

Wenn ich nach dem Laden wieder die vollständige List habe, könnte ich ja eine For-Schleife laufen lassen und abarbeiten. Dabei die Enemys nach und nach erstellen. Was haltet ihr davon?

Share this post


Link to post
Share on other sites

Ich mach mal nen neuen Beitrag auf, sonst wirds unübersichtlich.

Also bisher siehts folgendermaßen aus.

Ich habe mir eine Enemy-Klasse:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace Lions_Dream_SP {
    public enum enemyAnimalTyp : short {
        Deer = 1,
        Pig = 2
    }

    [System.Serializable]
    public class LD_SP_Data_Enemy {
        public int enemyID;
        public string enemyName;
        public int enemyVitaility;
        public int enemyMana;
        public Vector3 enemyPos;
        public Quaternion enymyRotation;
        public enemyAnimalTyp enemyAnimalTyp;


        public void EnemyUpdate() {
            LD_SP_Data_Object_Manager.Instance.EnemyUpdate(this);
        }
    }
}

Und eine Players-Klasse geschrieben:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


namespace Lions_Dream_SP {
    public enum playerAnimalTyp : short {
        Lion = 1,
        Tiger = 2
    }

    [System.Serializable]
    public class Inventory {
        public string itemName;
        public int itemAmount;
        public Sprite itemSprite;
    }

    [System.Serializable]
    public class LD_SP_Data_Player {
        public playerAnimalTyp playerAnimalTyp;
        public int playerID;
        public string playerName;
        public string playTime;
        public int playerVitaility;
        public int playerMana;
        public int playerCoat;
        public int playerPattern;
        public int playerParts;
        public int playerAddition;
        public Vector3 playerPos;
        public Quaternion playerRotation;
        public List<Inventory> playerInventory;


        public void CharacterUpdate() {
            LD_SP_Data_Object_Manager.Instance.CharacterUpdate(this);
        }
    }
}

Speichern würde nun so aussehen:

        public void SaveSavegame() {
            string time = System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss");
            file = path + "/Savegame_" + selectedSavegame + ".json";

            // Display aktualisieren
            savegameStructur[selectedSavegame].savegamePlayerName = LD_SP_Data_Object_Manager.Instance.characterList[selectedSavegame].playerName;
            savegameStructur[selectedSavegame].savegamePlayTime = time;
            savegameStructur[selectedSavegame].savegameExists = true;
            savegameStructur[selectedSavegame].savegameSceneNr = SceneManager.GetActiveScene().buildIndex;

            // Savegamestructure aktualisieren
            WriteSavagameStructure();

            // Aktualisierung aller Daten der Chraktere und Enemys
            LD_SP_Data_Object_Manager.Instance.CharacterListUpdate();
            LD_SP_Data_Object_Manager.Instance.EnemyListUpdate();

            //Savegame schreiben
            string json = JsonUtility.ToJson(LD_SP_Data_Object_Manager.Instance);
            if (!File.Exists(file)) {
                File.Create(file).Dispose();
            }
            File.WriteAllText(file, json);
        }

Zur Erklärung, hab mir dazu folgendes gedacht.

Sobald ein Enemy erzeugt wird, meldet er sich mit der Methode "EnemyAdd" bei der Klasse "LD_SP_Data_Object_Manager" an. Falls mal zerstört, gibs hier dafür auch ne "EnemyDelete".

Wird nun gespeichert, werden die der Methoden der Klasse "LD_SP_Data_Object_Manager" "CharacterListUpdate" und "EnemyListUpdate" für alle Enemys und Charactere aktiv. Sie melden sie nun wiederrum bei "LD_SP_Data_Object_Manager" und führen da die Methode "CharacterUpdate" und "EnemyUpdate" aus, so hab ich von allen wieder die aktuellen Daten.

Danach wird alles in JSON gespeichert.

So weit sogut!

Frage 1: Wäre die Möglichkeit besser, als ständig alle Enemys über die Update-Methode dauerhaft zu aktualisieren? Alternativ, könnte es zu Laggs kommen, wenn sagen wir 50 Enemys ankommen und ihre Daten aktualisieren?

Frage 2: Laden ...

Bisher sieht Laden bei mir so aus:

        public void LoadSavegame() {
            Debug.Log("TODO: Das Laden der Daten muss noch implementiert werden.");
            LD_SP_Menue_Loadscreen.Instance.LoadScene(loadSceneNr);
        }

        private IEnumerator LoadSceneCoroutine(int scene) {
            yield return new WaitForSeconds(1.5f);
            async = SceneManager.LoadSceneAsync(scene, LoadSceneMode.Single);
            async.allowSceneActivation = false;
            do {
                float progress = Mathf.Clamp01(async.progress / 0.9f);
                sliderDisplay.value = progress;
                yield return null;
            } while (!async.isDone);
        }

An welcher Stelle werden nun meine Daten für die Enemys und Charaktere aktualisiert bzw. erzeugt? Ich muss ja irgendwo auf die Karte paar Prefabs mit den Werten der Liste werfen.

EDIT:

Ahja, heraus bekomm ich diesen wunderschönen Blödsinn:

{
  "characterList": [
    {
      "instanceID": -120502
    }
  ],
  "childList": [],
  "enemyList": [
    {
      "instanceID": -68196
    },
    {
      "instanceID": -66434
    },
    {
      "instanceID": -66156
    },
    {
      "instanceID": -65878
    },
    {
      "instanceID": -65154
    }
  ]
}

Müsste er nicht eigentlich alles weitere aufschreiben, wie Mana, Leben und Co und nicht -65154?

Share this post


Link to post
Share on other sites

Alles in allem hab ich also heraus gefunden, dass er zwar speichert, aber Müll speichert.

"instanceID" und irgend ne Zahl is falsch. Dachte erst ist ne gepackte Info, aber nein, hier erkennt er so wie ich es verstanden habe die Typen nicht. 😕

EDIT:

Hehe, Unity du bist ne Zicke! :D Jetzt hats mich doch gewundert, warum das JSON Serilisieren und Deserilisieren denn beim JSON Test funktioniert hat und hier nicht. Ja, einziger blöder Unterschie war, die Speicherklassen "LD_SP_Data_Player", "LD_SP_Data_Child" und "LD_SP_Data_Enemy" haben von MonoBehavior geerbt. Es darf keine Vererbung zu dieser Basis-Klasse stattfinden, schon gehts.

So sieht das schon ne ganze Ecke besser aus:

{
	"characterList": [{
		"playerAnimalTyp": 0,
		"playerID": 0,
		"playerName": "",
		"playTime": "",
		"playerVitaility": 0,
		"playerMana": 0,
		"playerCoat": 0,
		"playerPattern": 0,
		"playerParts": 0,
		"playerAddition": 0,
		"playerPos": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0
		},
		"playerRotation": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0,
			"w": 0.0
		},
		"playerInventory": [],
		"sceneID": 0
	}],
	"childList": [],
	"enemyList": [{
		"enemyID": 0,
		"enemyName": "",
		"enemyVitaility": 0,
		"enemyMana": 0,
		"enemyPos": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0
		},
		"enymyRotation": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0,
			"w": 0.0
		},
		"enemyAnimalTyp": 0
	}, {
		"enemyID": 0,
		"enemyName": "",
		"enemyVitaility": 0,
		"enemyMana": 0,
		"enemyPos": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0
		},
		"enymyRotation": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0,
			"w": 0.0
		},
		"enemyAnimalTyp": 0
	}, {
		"enemyID": 0,
		"enemyName": "",
		"enemyVitaility": 0,
		"enemyMana": 0,
		"enemyPos": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0
		},
		"enymyRotation": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0,
			"w": 0.0
		},
		"enemyAnimalTyp": 0
	}, {
		"enemyID": 0,
		"enemyName": "",
		"enemyVitaility": 0,
		"enemyMana": 0,
		"enemyPos": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0
		},
		"enymyRotation": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0,
			"w": 0.0
		},
		"enemyAnimalTyp": 0
	}, {
		"enemyID": 0,
		"enemyName": "",
		"enemyVitaility": 0,
		"enemyMana": 0,
		"enemyPos": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0
		},
		"enymyRotation": {
			"x": 0.0,
			"y": 0.0,
			"z": 0.0,
			"w": 0.0
		},
		"enemyAnimalTyp": 0
	}]
}

Jetzt stellt sich nur noch die Frage wie ich das beim Laden machen, wie erstelle ich die gespeicherten GameObjects und wann.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

×
×
  • Create New...