Unity Serialisierung

TIM COOPER / UNITY TECHNOLOGIESContributor
Oct 25, 2012|14 Min.
Unity Serialisierung
Diese Website wurde aus praktischen Gründen für Sie maschinell übersetzt. Die Richtigkeit und Zuverlässigkeit des übersetzten Inhalts kann von uns nicht gewährleistet werden. Sollten Sie Zweifel an der Richtigkeit des übersetzten Inhalts haben, schauen Sie sich bitte die offizielle englische Version der Website an.

Sie schreiben also eine wirklich coole Editor-Erweiterung in Unity und die Dinge scheinen wirklich gut zu laufen. Sie haben Ihre Datenstrukturen in den Griff bekommen und sind wirklich zufrieden damit, wie das von Ihnen geschriebene Tool funktioniert.

Dann betreten und verlassen Sie den Spielmodus.

Plötzlich sind alle von Ihnen eingegebenen Daten verschwunden und Ihr Tool wird auf den Standardzustand zurückgesetzt, der gerade erst initialisiert wurde. Das ist sehr frustrierend! "Warum passiert das?", fragen Sie sich. Der Grund dafür liegt in der Funktionsweise der verwalteten (Mono) Schicht von Unity. Wenn Sie es erst einmal verstanden haben, wird alles viel einfacher :)

Was passiert, wenn eine Baugruppe neu geladen wird?
Wenn Sie den Spielmodus aufrufen/verlassen oder ein Skript ändern, muss Unity die Mono-Assemblies, d.h. die mit Unity verknüpften DLLs, neu laden.

Auf der Benutzerseite ist dies ein 3-stufiger Prozess:

  • Ziehen Sie alle serialisierbaren Daten aus dem verwalteten Bereich heraus und erstellen Sie eine interne Darstellung der Daten auf der C++-Seite von Unity.
  • Löschen Sie den gesamten Speicher / alle Informationen, die mit der verwalteten Seite von Unity verbunden sind, und laden Sie die Assemblies neu.
  • Reserialisieren Sie die in C++ gespeicherten Daten zurück in Managed Land.

Das bedeutet, dass Sie sicherstellen müssen, dass Ihre Datenstrukturen/Informationen ordnungsgemäß in den und aus dem C++-Speicher serialisiert werden können, damit sie ein Neuladen der Assembly überleben. Dies bedeutet auch, dass Sie (mit einigen kleinen Änderungen) diese Datenstruktur in einer Asset-Datei speichern und zu einem späteren Zeitpunkt wieder laden können.

Wie arbeite ich mit der Serialisierung von Unity?
Am einfachsten lernen Sie die Serialisierung in Unity kennen, indem Sie ein Beispiel durcharbeiten. Wir beginnen mit einem einfachen Editor-Fenster, das einen Verweis auf eine Klasse enthält, die ein Neuladen der Assembly überleben soll.

using UnityEngine;
using UnityEditor;

public class MyWindow : EditorWindow
{
	private SerializeMe m_SerialziedThing;

	[MenuItem ("Window/Serialization")]
	static void Init () {
		GetWindow ();
	}

	void OnEnable ()
	{
		hideFlags = HideFlags.HideAndDontSave;
		if (m_SerialziedThing == null)
			m_SerialziedThing = new SerializeMe ();
	}

	void OnGUI () {
		GUILayout.Label ("Serialized Things", EditorStyles.boldLabel);
		m_SerialziedThing.OnGUI ();
	}
}

using UnityEditor;

public struct NestedStruct
{
	private float m_StructFloat;
	public void OnGUI ()
	{
		m_StructFloat = EditorGUILayout.FloatField("Struct Float", m_StructFloat);
	}
}

public class SerializeMe
{
	private string m_Name;
	private int m_Value;

	private NestedStruct m_Struct;

	public SerializeMe ()
	{
		m_Struct = new NestedStruct();
		m_Name = "";
	}

	public void OnGUI ()
	{
		m_Name = EditorGUILayout.TextField( "Name", m_Name);
		m_Value = EditorGUILayout.IntSlider ("Value", m_Value, 0, 10);

		m_Struct.OnGUI ();
	}
}

Wenn Sie dies ausführen und ein Neuladen der Assembly erzwingen, werden Sie feststellen, dass alle Werte im Fenster, die Sie geändert haben, nicht erhalten bleiben. Das liegt daran, dass beim erneuten Laden der Assembly der Verweis auf 'm_SerialziedThing' verschwunden ist. Sie ist nicht für die Serialisierung vorgesehen.

Es gibt ein paar Dinge, die getan werden müssen, damit diese Serialisierung richtig funktioniert:
In MyWindow.cs:

  • Das Feld 'm_SerializedThing' muss mit dem Attribut [SerializeField] versehen werden. Damit wird Unity mitgeteilt, dass es versuchen soll, dieses Feld bei einem Neuladen der Assembly oder ähnlichen Ereignissen zu serialisieren.

In SerializeMe.cs:

  • Die Klasse 'SerializeMe' muss mit dem Attribut [Serializable] versehen werden. Dies teilt Unity mit, dass die Klasse serialisierbar ist.
  • Die Struktur 'NestedStruct' muss mit dem Attribut [Serializable] versehen werden.
  • Jedes (nicht öffentliche) Feld, das serialisiert werden soll, muss mit dem Attribut [SerializeField] versehen werden.

Nachdem Sie diese Flaggen hinzugefügt haben, öffnen Sie das Fenster und ändern Sie die Felder. Sie werden feststellen, dass die Felder nach einem Neuladen der Assembly ihre Werte beibehalten, d.h. mit Ausnahme des Feldes, das aus der Struktur stammt. Das bringt uns zum ersten wichtigen Punkt: Structs werden bei der Serialisierung nicht sehr gut unterstützt. Die Änderung von 'NestedStruct' von einer Struktur zu einer Klasse behebt dieses Problem.

Der Code sieht nun wie folgt aus:

using UnityEngine;
using UnityEditor;

public class MyWindow : EditorWindow
{
	private SerializeMe m_SerialziedThing;

	[MenuItem ("Window/Serialization")]
	static void Init () {
		GetWindow ();
	}

	void OnEnable ()
	{
		hideFlags = HideFlags.HideAndDontSave;
		if (m_SerialziedThing == null)
			m_SerialziedThing = new SerializeMe ();
	}

	void OnGUI () {
		GUILayout.Label ("Serialized Things", EditorStyles.boldLabel);
		m_SerialziedThing.OnGUI ();
	}
}

using System;
using UnityEditor;
using UnityEngine;

[Serializable]
public class NestedStruct
{
	[SerializeField]
	private float m_StructFloat;
	public void OnGUI ()
	{
		m_StructFloat = EditorGUILayout.FloatField("Struct Float", m_StructFloat);
	}
}

[Serializable]
public class SerializeMe
{
	[SerializeField]
	private string m_Name;
	[SerializeField]
	private int m_Value;
	[SerializeField]
	private NestedStruct m_Struct;

	public SerializeMe ()
	{
		m_Struct = new NestedStruct();
		m_Name = "";
	}

	public void OnGUI ()
	{
		m_Name = EditorGUILayout.TextField( "Name", m_Name);
		m_Value = EditorGUILayout.IntSlider ("Value", m_Value, 0, 10);

		m_Struct.OnGUI ();
	}
}

Einige Regeln zur Serialisierung

  • Vermeiden Sie Strukturen
  • Klassen, die serialisierbar sein sollen, müssen mit [Serializable] markiert werden.
  • Öffentliche Felder werden serialisiert (sofern sie eine [Serializable]-Klasse referenzieren)
  • Private Felder werden unter bestimmten Umständen serialisiert (Editor).
  • Markieren Sie private Felder als [SerializeField], wenn Sie möchten, dass sie serialisiert werden.
  • [NonSerialized] existiert für Felder, die Sie nicht serialisieren möchten.

Skriptfähige Objekte
Bisher haben wir uns mit der Verwendung normaler Klassen für die Serialisierung beschäftigt. Leider gibt es bei der Verwendung einfacher Klassen einige Probleme bei der Serialisierung in Unity. Schauen wir uns ein Beispiel an.

using System;
using UnityEditor;
using UnityEngine;

[Serializable]
public class NestedClass
{
	[SerializeField]
	private float m_StructFloat;
	public void OnGUI()
	{
		m_StructFloat = EditorGUILayout.FloatField("Float", m_StructFloat);
	}
}

[Serializable]
public class SerializeMe
{
	[SerializeField]
	private NestedClass m_Class1;

	[SerializeField]
	private NestedClass m_Class2;

	public void OnGUI ()
	{
		if (m_Class1 == null)
			m_Class1 = new NestedClass ();
		if (m_Class2 == null)
			m_Class2 = m_Class1;

		m_Class1.OnGUI();
		m_Class2.OnGUI();
	}
}

Dies ist ein erfundenes Beispiel, um einen sehr speziellen Eckfall des Unity-Serialisierungssystems zu zeigen, der Sie erwischen kann, wenn Sie nicht vorsichtig sind. Sie werden feststellen, dass wir zwei Felder vom Typ NestedClass haben. Wenn das Fenster zum ersten Mal gezeichnet wird, werden beide Felder angezeigt. Da m_Class1 und m_Class2 auf dieselbe Referenz verweisen, wird beim Ändern eines Feldes auch das andere geändert.

Versuchen Sie nun, die Baugruppe neu zu laden, indem Sie den Spielmodus aufrufen und wieder verlassen... Die Referenzen wurden entkoppelt. Das liegt daran, wie die Serialisierung funktioniert, wenn Sie eine Klasse einfach als [Serializable] markieren.

Wenn Sie Standardklassen serialisieren, geht Unity durch die Felder der Klasse und serialisiert jedes einzelne, auch wenn die Referenz von mehreren Feldern gemeinsam genutzt wird. Das bedeutet, dass Sie dasselbe Objekt mehrfach serialisieren lassen können und das System bei der Deserialisierung nicht weiß, dass es sich um dasselbe Objekt handelt. Wenn Sie ein komplexes System entwerfen, ist dies eine frustrierende Einschränkung, denn es bedeutet, dass komplexe Interaktionen zwischen Klassen nicht richtig erfasst werden können.

Geben Sie ScriptableObjects ein! ScriptableObjects sind eine Art von Klasse, die korrekt als Referenzen serialisiert werden, so dass sie nur einmal serialisiert werden. Dadurch können komplexe Klasseninteraktionen so gespeichert werden, wie Sie es erwarten würden. Intern sind ScriptableObjects und MonoBehaviours in Unity gleich. Im Userland-Code können Sie ein ScriptableObject haben, das nicht an ein GameObjects angehängt ist; dies unterscheidet sich von der Funktionsweise von MonoBehaviour. Sie eignen sich hervorragend für die allgemeine Serialisierung von Datenstrukturen.

Ändern wir das Beispiel, damit es die Serialisierung richtig handhaben kann:

using System;
using UnityEditor;
using UnityEngine;

[Serializable]
public class NestedClass : ScriptableObject
{
	[SerializeField]
	private float m_StructFloat;

	public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }

	public void OnGUI()
	{
		m_StructFloat = EditorGUILayout.FloatField("Float", m_StructFloat);
	}
}

[Serializable]
public class SerializeMe
{
	[SerializeField]
	private NestedClass m_Class1;

	[SerializeField]
	private NestedClass m_Class2;

	public SerializeMe ()
	{
		m_Class1 = ScriptableObject.CreateInstance ();
		m_Class2 = m_Class1;
	}

	public void OnGUI ()
	{
		m_Class1.OnGUI();
		m_Class2.OnGUI();
	}
}

Die drei erwähnenswerten Änderungen sind folgende:

  • NestedClass ist jetzt ein ScriptableObject.
  • Wir erstellen eine Instanz mit der Funktion CreateInstance<>, anstatt den Konstruktor aufzurufen.
  • Wir setzen auch die Ausblenden-Flags... das wird später erklärt

Diese einfachen Änderungen bedeuten, dass die Instanz der NestedClass nur einmal serialisiert wird, wobei alle Verweise auf die Klasse auf dieselbe verweisen.

ScriptableObject Initialisierung
Jetzt wissen wir also, dass es für komplexe Datenstrukturen, bei denen eine externe Referenzierung erforderlich ist, eine gute Idee ist, ScriptableObjects zu verwenden. Aber was ist der richtige Weg, um mit ScriptableObjects vom Benutzercode aus zu arbeiten? Als Erstes sollten Sie untersuchen, WIE skriptfähige Objekte initialisiert werden, insbesondere durch das Serialisierungssystem von Unity.

Der Konstruktor wird für das ScriptableObject aufgerufen.

Die Daten werden von der C++-Seite von Unity in das Objekt serialisiert (sofern solche Daten vorhanden sind).

OnEnable() wird für das ScriptableObject aufgerufen.

Wenn wir mit diesem Wissen arbeiten, können wir einige Dinge sagen:

  • Die Initialisierung im Konstruktor ist keine sehr gute Idee, da die Daten möglicherweise vom Serialisierungssystem überschrieben werden.
  • Die Serialisierung findet NACH der Konstruktion statt, also sollten wir unsere Konfiguration nach der Serialisierung vornehmen.
  • OnEnable() scheint der beste Kandidat für die Initialisierung zu sein.

Lassen Sie uns einige Änderungen an der Klasse 'SerializeMe' vornehmen, damit sie ein ScriptableObject ist. So können wir das korrekte Initialisierungsmuster für ScriptableObjects erkennen.

// also updated the Window to call CreateInstance instead of the constructor
using System;
using UnityEngine;

[Serializable]
public class SerializeMe : ScriptableObject
{
	[SerializeField]
	private NestedClass m_Class1;

	[SerializeField]
	private NestedClass m_Class2;

	public void OnEnable ()
	{
		hideFlags = HideFlags.HideAndDontSave;
		if (m_Class1 == null)
		{
			m_Class1 = CreateInstance ();
			m_Class2 = m_Class1;
		}
	}

	public void OnGUI ()
	{
		m_Class1.OnGUI();
		m_Class2.OnGUI();
	}
}

Oberflächlich betrachtet scheint es, als hätten wir an dieser Klasse nicht viel geändert. Sie erbt jetzt von ScriptableObject und verfügt statt eines Konstruktors über eine OnEnable(). Der wichtige Teil, auf den Sie achten sollten, ist etwas subtiler... OnEnable() wird NACH der Serialisierung aufgerufen; dadurch können wir sehen, ob die [SerializedFields] null sind oder nicht. Wenn sie null sind, bedeutet dies, dass dies die erste Initialisierung ist und wir die Instanzen konstruieren müssen. Wenn sie nicht null sind, wurden sie bereits in den Speicher geladen und müssen NICHT konstruiert werden. Es ist üblich, in OnEnable() auch eine benutzerdefinierte Initialisierungsfunktion aufzurufen, um alle privaten / nicht serialisierten Felder des Objekts zu konfigurieren, ähnlich wie Sie es in einem Konstruktor tun würden.

HideFlags
In den Beispielen mit ScriptableObjects werden Sie feststellen, dass wir die 'hideFlags' des Objekts auf HideFlags.HideAndDontSave setzen. Dies ist eine spezielle Einrichtung, die erforderlich ist, wenn Sie benutzerdefinierte Datenstrukturen schreiben, die keinen Ursprung in der Szene haben. Damit umgehen Sie die Funktionsweise des Szeneladens in Unity.

Wenn eine Szene intern geladen wird, ruft Unity Resources.UnloadUnusedAssets auf. Wenn nichts auf ein Asset verweist, wird der Garbage Collector es finden. Die GC verwendet die Szene als 'Wurzel' und durchläuft die Hierarchie, um zu sehen, was auf die GC übertragen werden kann. Wenn Sie das Flag HideAndDontSave für ein ScriptableObject setzen, wird Unity angewiesen, dieses Objekt als Stammobjekt zu betrachten. Aus diesem Grund wird sie nicht einfach verschwinden, wenn Sie die Baugruppe neu laden. Das Objekt kann immer noch durch den Aufruf von Destroy() zerstört werden.

Einige Regeln für ScriptableObjects

  • ScriptableObjects werden nur einmal serialisiert, so dass Sie Referenzen korrekt verwenden können.
  • Verwenden Sie OnEnable, um ScriptableObjects zu initialisieren.
  • Rufen Sie niemals den Konstruktor eines ScriptableObjects auf, verwenden Sie stattdessen CreatInstance
  • Für verschachtelte Datenstrukturen, die nur einmal referenziert werden, sollten Sie kein ScriptableObject verwenden, da diese mehr Overhead verursachen.
  • Wenn Ihr skriptfähiges Objekt nicht in der Szene verankert ist, setzen Sie die hideFlags auf HideAndDontSave.

Konkrete Array-Serialisierung
Schauen wir uns ein einfaches Beispiel an, das eine Reihe von konkreten Klassen serialisiert.

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

[Serializable]
public class BaseClass
{
	[SerializeField]
	private int m_IntField;
	public void OnGUI() {m_IntField = EditorGUILayout.IntSlider ("IntField", m_IntField, 0, 10);}
}

[Serializable]
public class SerializeMe : ScriptableObject
{
	[SerializeField]
	private List m_Instances;

	public void OnEnable ()
	{
		hideFlags = HideFlags.HideAndDontSave;
		if (m_Instances == null)
			m_Instances = new List ();
	}

	public void OnGUI ()
	{
		foreach (var instance in m_Instances)
			instance.OnGUI ();

		if (GUILayout.Button ("Add Simple"))
			m_Instances.Add (new BaseClass ());
	}
}

Dieses einfache Beispiel enthält eine Liste von BaseClasses. Wenn Sie auf die Schaltfläche 'Einfach hinzufügen' klicken, wird eine Instanz erstellt und der Liste hinzugefügt. Da die Klasse SerializeMe richtig für die Serialisierung konfiguriert ist (wie zuvor beschrieben), funktioniert sie einfach. Unity erkennt, dass die Liste zur Serialisierung markiert ist und serialisiert jedes der Listenelemente.

Allgemeine Array-Serialisierung
Lassen Sie uns das Beispiel ändern, um eine Liste zu serialisieren, die Mitglieder einer Basisklasse und einer untergeordneten Klasse enthält:

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

[Serializable]
public class BaseClass
{
	[SerializeField]
	private int m_IntField;
	public virtual void OnGUI() { m_IntField = EditorGUILayout.IntSlider("IntField", m_IntField, 0, 10); }
}

[Serializable]
public class ChildClass : BaseClass
{
	[SerializeField]
	private float m_FloatField;
	public override void OnGUI()
	{
		base.OnGUI ();
		m_FloatField = EditorGUILayout.Slider("FloatField", m_FloatField, 0f, 10f);
	}
}

[Serializable]
public class SerializeMe : ScriptableObject
{
	[SerializeField]
	private List m_Instances;

	public void OnEnable ()
	{
		if (m_Instances == null)
			m_Instances = new List ();

		hideFlags = HideFlags.HideAndDontSave;
	}

	public void OnGUI ()
	{
		foreach (var instance in m_Instances)
			instance.OnGUI ();

		if (GUILayout.Button ("Add Base"))
			m_Instances.Add (new BaseClass ());
		if (GUILayout.Button ("Add Child"))
			m_Instances.Add (new ChildClass ());
	}
}

Das Beispiel wurde erweitert, so dass es jetzt eine ChildClass gibt, aber wir serialisieren mit der BaseClass. Wenn Sie mehrere Instanzen der ChildClass und der BaseClass erstellen, werden diese korrekt dargestellt. Probleme treten auf, wenn sie in einer Baugruppe neu geladen werden. Nach Abschluss des Neuladens ist jede Instanz eine BaseClass, bei der alle ChildClass-Informationen entfernt wurden. Die Instanzen werden durch das Serialisierungssystem zerlegt.

Um diese Einschränkung des Serialisierungssystems zu umgehen, können Sie wieder ScriptableObjects verwenden:

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

[Serializable]
public class MyBaseClass : ScriptableObject
{
	[SerializeField]
	protected int m_IntField;

	public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }

	public virtual void OnGUI ()
	{
		m_IntField = EditorGUILayout.IntSlider("IntField", m_IntField, 0, 10);
	}
}

[Serializable]
public class ChildClass : MyBaseClass
{
	[SerializeField]
	private float m_FloatField;

	public override void OnGUI()
	{
		base.OnGUI ();
		m_FloatField = EditorGUILayout.Slider("FloatField", m_FloatField, 0f, 10f);
	}
}

[Serializable]
public class SerializeMe : ScriptableObject
{
	[SerializeField]
	private List m_Instances;

	public void OnEnable ()
	{
		if (m_Instances == null)
			m_Instances = new List();

		hideFlags = HideFlags.HideAndDontSave;
	}

	public void OnGUI ()
	{
		foreach (var instance in m_Instances)
			instance.OnGUI ();

		if (GUILayout.Button ("Add Base"))
			m_Instances.Add(CreateInstance());
		if (GUILayout.Button ("Add Child"))
			m_Instances.Add(CreateInstance());
	}
}

Nachdem Sie dies ausgeführt, einige Werte geändert und Assemblies neu geladen haben, werden Sie feststellen, dass ScriptableObjects sicher in Arrays verwendet werden können, auch wenn Sie abgeleitete Typen serialisieren. Der Grund dafür ist, dass bei der Serialisierung einer [serialisierbaren] Standardklasse diese 'an Ort und Stelle' serialisiert wird, während ein ScriptableObject extern serialisiert und die Referenz in die Sammlung eingefügt wird. Die Scherung tritt auf, weil der Typ nicht richtig serialisiert werden kann, da das Serialisierungssystem denkt, dass er vom Basistyp ist.

Abstrakte Klassen serialisieren
Wir haben also gesehen, dass es möglich ist, eine allgemeine Liste zu serialisieren (solange die Mitglieder vom Typ ScriptableObject sind). Lassen Sie uns sehen, wie sich abstrakte Klassen verhalten:

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

[Serializable]
public abstract class MyBaseClass : ScriptableObject
{
	[SerializeField]
	protected int m_IntField;

	public void OnEnable() { hideFlags = HideFlags.HideAndDontSave; }

	public abstract void OnGUI ();
}

[Serializable]
public class ChildClass : MyBaseClass
{
	[SerializeField]
	private float m_FloatField;

	public override void OnGUI()
	{
		m_IntField = EditorGUILayout.IntSlider("IntField", m_IntField, 0, 10);
		m_FloatField = EditorGUILayout.Slider("FloatField", m_FloatField, 0f, 10f);
	}
}

[Serializable]
public class SerializeMe : ScriptableObject
{
	[SerializeField]
	private List m_Instances;

	public void OnEnable ()
	{
		if (m_Instances == null)
			m_Instances = new List();

		hideFlags = HideFlags.HideAndDontSave;
	}

	public void OnGUI ()
	{
		foreach (var instance in m_Instances)
			instance.OnGUI ();

		if (GUILayout.Button ("Add Child"))
			m_Instances.Add(CreateInstance());
	}
}

Dieser Code funktioniert ähnlich wie das vorherige Beispiel. Aber es IST gefährlich. Lassen Sie uns sehen, warum.

Die Funktion CreateInstance<>() erwartet einen Typ, der von ScriptableObject erbt. Die Klasse 'MyBaseClass' erbt in der Tat von ScriptableObject. Das bedeutet, dass es möglich ist, dem Array m_Instances eine Instanz der abstrakten Klasse MyBaseClass hinzuzufügen. Wenn Sie dies tun und dann versuchen, auf eine abstrakte Methode zuzugreifen, passieren schlimme Dinge, weil es keine Implementierung dieser Funktion gibt. In diesem speziellen Fall wäre das die Methode OnGUI.

Die Verwendung von abstrakten Klassen als serialisierter Typ für Listen und Felder funktioniert, solange sie von ScriptableObject erben, ist aber nicht empfehlenswert. Ich persönlich halte es für besser, konkrete Klassen mit leeren virtuellen Methoden zu verwenden. So können Sie sicher sein, dass es Ihnen nicht schlecht geht.

Wann werden skriptfähige Objekte in Szenen-/Voreinstellungsdateien gespeichert?
GameObjects und ihre Komponenten werden standardmäßig in einer Szene gespeichert. Asset-Typen (Materialien / Meshes / AnimationClip / SerializedObjects), die durch Code erstellt werden, werden in der Szene gespeichert, solange ein Spielobjekt oder seine Komponenten in der Szene darauf verweisen.

Asset-Typen können auch mit AssetDatabase.CreateAsset explizit als Assets markiert werden. In diesem Fall werden sie nicht in der Szene gespeichert, sondern nur referenziert. Wenn ein Asset-Typ oder ein Spielobjekttyp als HideAndDontSave markiert ist, wird er auch nicht in der Szene gespeichert.

Haben Sie Fragen?