Jump to content
Unity Insider Forum

Marrrk's Blog

  • Einträge
    8
  • Kommentare
    5
  • Aufrufe
    31.039

SaveIt Entwicklungsstand #1 - Neuanfang


Mark

557 Aufrufe

Hier mal der aktuelle Stand meines Lade und Speicherassets "SaveIt".

Ich bin nun mittlerweile soweit dass ich problemlos die meisten Klassen, Strukturen und Primitiven abspeichern kann. Was ist damit gemeint? Sowas hier:

Primitiven: int, float, bool, byte, char, string, ...
Klassen: GameObject, Mesh, Material, AudioClip, eben alles was eine Klasse ist
Strukturen: Vector3, vector4, Quaternion, eben alles was eine Struktur ist

Klassen und Strukturen werden dabei gleich behandelt, für jeden Typ wird ein spezialisierter Serialisierer aufgerufen der dafür zu sorgen hat dass die Member entsprechend abgelegt werden. Gibt es keinen spezialisierten Serialisierer wird einfach ein generischer Klassen/Structurenserialisierer aufgerufen der stumpf alle Member (Fields und Properties) durchgeht und versucht diese abzuspeichern.

Da nicht alle Member abgespeichert werden dürfen (da sie zB nur andere Werte unerwünscht verändern würden, was zB bei Properties schnell der Fall ist) gibt es immer eine Liste der verbotenen Member, die beliebig erweitert werden kann.

Das ganze geht schon recht gut von der Hand, hier mal ein Beispielzum laden einer Testklasse:

TestKlasse.cs
[CODE]
public class TestKlasse
{
public TestKlasse andereTestKlasse = null;
public string text = "Hallo du da!";
}
[/CODE]

Speichern:
[CODE]
var testKlasse = new TestKlasse();
testKlasse.andereTestKlasse = new TestKlasse();
testKlasse.andereTestKlasse.andereTestKlasse = testKlasse; // man beachte besonders diese Zeile
testKlasse.text = "ABCDEFG1234";

var saveIt = new SaveIt();
saveIt.Save(testKlasse, "OptionalerName"); // der letzte Parameter ist optional
saveIt.Flush(); // speichert die daten in eine Datei
[/CODE]
Hierbei wird in die Datei "SaveItData.saveIt" gespeichert, man könnte den Dateinamen und die Art der Datei im Konstruktor der SaveIt Klasse angeben.

Laden:
[CODE]
var saveIt = new SaveIt();
var geladeneTestKlasse = saveIt.Load<TestKlasse>("OptionalerName");
[/CODE]
die geladene Klasse sieht nun genauso aus wie die testKlasse die wir abgespeichert haben:

geladeneTestKlasse.text: "ABCDEFG1234"
geladeneTestKlasse.andereTestKlasse.text: "Hallo du da!"
geladeneTestKlasse.andereTestKlasse.andereTestKlasse.text: "ABCDEFG1234"
geladeneTestKlasse.andereTestKlasse.andereTestKlasse: geladeneTestKlasse

SaveIt speichert eine Klasse nur ein einziges mal ab und lädt sie auch nur ein einziges mal, wodurch Speicherplatz gespart wird und mehr, so haben 2 verschiedene Klassen die eine dritte Klasse referenzieren auch wieder nur diese eine dritte Klasse wenn sie wieder geladen werden und nicht 2 verschiedene dritte Klassen die gleich aussehen.

Man kann auch eigene Serialisierer schreiben, dazu gibt es 2 Möglichkeiten, die kompliziertere welche es auch ermöglich die finale Instanz die deserialisiert werden soll zu erstellen benötigt vom Entwickler, dass er von einem BasisSerialisierer ableitet und ein paar Methoden implementiert, hier ist zb ein Beispiel für den ParticleSystemSerializer, welches ein PartikelSystem serialisieren und deserialisieren kann:

[CODE]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace SaveItSpace.TypeSerializer.Unity
{
public class ParticleSystemSerializer : ComponentSerializer
{
public override bool IsUsable(Type type)
{
return type == typeof(ParticleSystem);
}

public override void Serialize(object what, SaveContext context)
{
var particleSystem = (ParticleSystem)what;

var particles = new ParticleSystem.Particle[particleSystem.particleCount];
particleSystem.GetParticles(particles);

context.Save(particles, "particles");

base.Serialize(what, context);
}

public override void Deserialize(Type type, LoadContext context)
{
base.Deserialize(type, context);

var particleSystem = (ParticleSystem)context.CurrentInstance;

var particles = context.Load<ParticleSystem.Particle[]>("particles");
particleSystem.SetParticles(particles, particles.Length);
}
}
}
[/CODE]

Die andere Variante die etwas leichter ist, wird implementiert indem man einfach in den zu serialisierenden/deserialisierenden Typen 2 Methoden implementiert:

[CODE]
void Save(SaveContext context, ref bool allowAutoSerialization)
void Load(LoadContext context, ref bool allowAutoDeserialization)
[/CODE]

SaveContext und LoadContext bieten hierbei immer eine Reihe von Methoden zum speichern oder laden von Werten an, dazu kommt aber später noch etwas mehr.

Erstmal ein Beispiel wie diese Methoden verwendet werden könnten:

[CODE]
public class TestKlasse
{
public string text = "Huhu";
public int wert = 123;

void Save(SaveContext context, ref bool allowAutoSerialization)
{
// verhindert Automatismen welche einfach nur jeden Member serialisieren würde
allowAutoSerialization = false;
context.Save(wert); // speichert den Wert ohne besonderen Namen ab
}

void Load(LoadContext context, ref bool allowAutoDeserialization)
{
// verhindert Automatismen welche einfach nur jeden Member deserialisieren würde
allowAutoDeserialization = false;
wert = context.Load<int>(); // Lädt den Wert ohne besonderen Namen
}
}
[/CODE]
Dieses Beispiel würde nur TestKlasse.wert speichern und laden, TestKlasse.text dabei aber ignorieren.

Hier nunmal ein kleiner überblick über die bisherigen Methoden der Save und LoadContext Klassen:

SaveContext:
[CODE]
// speichert den Member mit dem angegebenen memberNamen ab
void SaveMember(string memberName)

// speichert den Member mit dem angegebenen memberNamen unter dem angegebenen namen ab
void SaveMember(string memberName, string name)

// speichert what ab
void Save<T>(T what)

// speichert what unter dem angegebenen namen ab
void Save<T>(T what, string name)
[/CODE]
Lässt man name weg dann wird intern ein Zähler erhöht so dass man durch das entgegengesetzte Load die Werte wieder bekommen kann.

LoadContext:
[CODE]
// gibt true zurück wenn der angegeben Typ nicht direkt erzeugt werden kann, wie es zB bei allen Komponenten der Fall ist
bool IsDeferredType(Type type)

// lädt den angebenen memberName in das aktuell zu befüllende Objekt
void LoadMember(string memberName)

// lädt den angebenen memberName unter dem angebenene Namen name in das aktuell zu befüllende Objekt
void LoadMember(string memberName, string name)

// lädt die Daten ohne Namen in die angegebene Instanz (für zB Komponenten)
T LoadToInstance<T>(T instance)
object LoadToInstance(object instance)

// lädt die Daten unter dem Namen name in die angegebene Instanz (für zB Komponenten)
T LoadToInstance<T>(string name, T instance)
object LoadToInstance(string name, object instance)

// Lädt den Wert im aktuellen Slot und ruft die angegebene Aktion bei Erfolg auf (gut für Komponenten)
void Load(Action<object> onLoaded)
void Load<T>(Action<T> onLoaded

// Lädt den Wert mit dem angegebenen Namen name und ruft die angegebene Aktion bei Erfolg auf (gut für Komponenten)
void Load(string name, Action<object> onLoaded)
void Load<T>(string name, Action<T> onLoaded)

// Lädt den Wert im aktuellen Slot
object Load()
T Load<T>()

// Testet ob es einen Wert unter dem angebenenen Namen name gibt
bool Exists(string name)

// Lädt den Wert unter dem angegebenen Namen name
object Load(string name)
T Load<T>(string name)

// Lädt den aktuellen Slot in das angegebene target
void Load<T>(out T target)

// Wartet darauf dass der aktuelle Slot geladen werden kann und ruft die angebenene Action auf
void WaitForLoad<T>(Action<T> onLoaded)
void WaitForLoad(Action<object> onLoaded)

// Wartet darauf dass der Wert unter dem angebenenen Namen name geladen werden kann und ruft die angebenene Action auf
void WaitForLoad<T>(string name, Action<T> onLoaded)
void WaitForLoad(string name, Action<object> onLoaded)
[/CODE]

Schon eine ganze Menge an Methoden und Möglichkeiten sein Objekt zu individuell zu speichern.

Das sehr schöne an der Sache: Ich arbeite aktuell an einer Klasse welche das gesammte Level laden und speichern kann. Dies klappt bisher auch ganz gut, auch wenn ich noch nicht alle einzelnen Komponenten auf Herz und Nieren testen konnte.

Bisher gibt es nur noch ein Problem mit animierten Figuren, wie zB die Bauarbeiter Figur, irgendetwas wird da noch nicht gut genug geladen, mit dem Resultat dass einige Vertexe der Bauarbeiter Figur beim Ursprungspunkt der Welt (0,0,0) verbleiben. Woran genau das liegtist noch fraglich, am gespeicherten Mesh liegts zumindest nicht, denn:

SaveIt hat die Möglichkeit bestimmte Objekte direkt von Unity zu laden ohne die eigene Serialisierung/Deserialisierung dafür bemühen zu müssen, dies spart enorm viel Zeit und Speicherplatz.

So das wars erstmal mit dem aktuellen Stand der Entwicklung zu SaveIt, ich hoffe ich kann bald mehr zeigen unter anderem einen WebPlayer der in die Playerprefs bzw in den Arbeitsspeicher speichert um alles demonstrieren zu können.

0 Kommentare


Recommended Comments

Keine Kommentare vorhanden

Gast
Kommentar schreiben...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

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

Lädt...
×
×
  • Neu erstellen...