Engine & platform

Verstehen der Serialisierungssprache von Unity, YAML

NICOLAS ALEJANDRO BORROMEO Nicolas Alejandro Borromeo
Jul 28, 2022|13 Min.
Verstehen der Serialisierungssprache von Unity, YAML
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.

Wussten Sie, dass Sie im Unity-Editor jede Art von Asset bearbeiten können, ohne sich mit Serialisierungssprachen wie XML oder JSON auseinandersetzen zu müssen? In den meisten Fällen funktioniert dies, aber es gibt auch Fälle, in denen Sie Ihre Dateien direkt ändern müssen. Denken Sie zum Beispiel an Merge-Konflikte oder beschädigte Dateien.

Deshalb werden wir in diesem Blog-Beitrag das Serialisierungssystem von Unity weiter auspacken und Anwendungsfälle vorstellen, die durch die direkte Änderung von Asset-Dateien erreicht werden können.

Wie immer sollten Sie Ihre Dateien sichern und idealerweise eine Versionskontrolle verwenden, um Datenverluste zu vermeiden. Das manuelle Ändern von Asset-Dateien ist ein riskanter Vorgang und wird von Unity nicht unterstützt. Asset-Dateien sind nicht dafür ausgelegt, manuell geändert zu werden, und geben keine hilfreichen Fehlermeldungen aus, die erklären, was passiert ist, wenn Fehler auftreten. Indem Sie die Funktionsweise von Unity besser verstehen und sich auf die Lösung von Merge-Konflikten vorbereiten, können Sie Situationen kompensieren, in denen die Asset-Datenbank-API nicht ausreicht.

YAML-Struktur

YAML, auch bekannt als "YAML Ain't Markup Language", gehört zur Familie der für Menschen lesbaren Datenserialisierungssprachen wie XML und JSON. Aber weil sie im Vergleich zu anderen gängigen Sprachen leicht und relativ einfach ist, gilt sie als leichter zu lesen.

Unity verwendet eine leistungsstarke Serialisierungsbibliothek, die eine Teilmenge der YAML-Spezifikation implementiert. So werden beispielsweise Leerzeilen, Kommentare und einige andere in YAML unterstützte Syntaxen in Unity-Dateien nicht unterstützt. In bestimmten Randfällen weicht das Unity-Format von der YAML-Spezifikation ab.

Schauen wir uns dazu einen Ausschnitt aus dem YAML-Code in einem Cube Prefab an. Erstellen Sie zunächst einen Standardwürfel in Unity, konvertieren Sie ihn in eine Prefab und öffnen Sie die Prefab-Datei in einem beliebigen Texteditor. Wie Sie in Abbildung 1 sehen können, sind die ersten beiden Zeilen Kopfzeilen, die später nicht wiederholt werden. Die erste definiert, welche YAML-Version Sie verwenden, während die zweite ein Makro namens "!u!" für den URI-Präfix "tag:unity3d.com,2011:" erstellt (siehe unten).

Code der Kopfzeilen im YAML-Format
Code der Kopfzeilen im YAML-Format

Nach den Kopfzeilen finden Sie eine Reihe von Objektdefinitionen, wie z. B. GameObjects in einer Prefab oder Szene, die Komponenten jedes GameObjects und möglicherweise andere Objekte wie Lightmap-Einstellungen für Szenen.

YAML für ein Spielobjekt namens Cube
YAML für ein Spielobjekt namens Cube

Jede Objektdefinition beginnt mit einer zweizeiligen Kopfzeile, wie die in unserem Beispiel für Abbildung 2: "--- !u!1 &7618609094792682308" folgt dem Format "--- !u!{CLASS ID} &{FILE ID}", das in zwei Teilen analysiert werden kann:

  • !u!{CLASS ID}:Hier wird Unity mitgeteilt, zu welcher Klasse das Objekt gehört. Der Teil "!u!" wird durch das zuvor definierte Makro ersetzt, so dass "tag:unity3d.com,2011:1" übrig bleibt - die Zahl 1 bezieht sich in diesem Fall auf die GameObject-ID. Jede Klassen-ID ist im Quellcode von Unity definiert, eine vollständige Liste findet sich hier.
  • &{DATEI-ID}:Dieser Teil definiert die ID für das Objekt selbst, die verwendet wird, um Objekte untereinander zu referenzieren. Sie wird Datei-ID genannt, weil sie die ID des Objekts in einer bestimmten Datei darstellt. Weitere Informationen zu dateiübergreifenden Verweisen finden Sie weiter unten in diesem Beitrag.

Die zweite Kopfzeile des Objekts ist der Name des Objekttyps (hier GameObject), der es Ihnen ermöglicht, das Objekt beim Lesen der Datei zu identifizieren.

Format der Kopfzeile
Format der Kopfzeile

Nach dem Objektkopf finden Sie alle serialisierten Eigenschaften. In unserem obigen GameObject-Beispiel zeigt Abbildung 2 Details wie seinen Namen (m_Name: Würfel) und Ebene (m_Layer: 0). Bei der Serialisierung von MonoBehaviour werden die öffentlichen und die privaten Felder mit dem Attribut SerializeField gekennzeichnet. Dieses Format wird in ähnlicher Weise für skriptfähige Objekte, Animationen, Materialien usw. verwendet. Bitte beachten Sie, dass ScriptableObjects MonoBehaviour als Objekttyp verwenden, anstatt einen eigenen zu definieren. Das liegt daran, dass die gleiche interne Klasse MonoBehaviour auch diese beherbergt.

Schnelles Refactoring mit YAML

Mit dem bisher Erlernten können Sie die Möglichkeiten der Modifikation von YAML z. B. für die Umgestaltung von Animationsstrecken nutzen.

Unitys Animationsdateien funktionieren, indem sie eine Reihe von Spuren oder Animationskurven beschreiben; eine für jede Eigenschaft, die Sie animieren möchten. Wie in Abbildung 4 dargestellt, identifiziert eine Animationskurve das zu animierende Objekt über die Pfadeigenschaft, die die Namen der untergeordneten GameObjects bis hin zu diesem Objekt enthält. In diesem Beispiel animieren wir ein GameObject namens "JumpingCharacter" - ein Kind des GameObjects "Shoulder", das ein Kind des GameObjects ist, das die Animator-Komponente hat, die diese Animation abspielt. Um dieselbe Animation auf verschiedene Objekte anzuwenden, verwendet das Animationssystem stringbasierte Pfade anstelle von GameObject-IDs.

Code der Pfadeigenschaft einer Animationskurve
Code der Pfadeigenschaft einer Animationskurve

Die Umbenennung eines animierten Objekts in der Hierarchie kann zu einem sehr häufigen Problem führen: Die Kurve könnte aus dem Blick geraten. Zwar lässt sich dieses Problem in der Regel durch Umbenennen der einzelnen Animationsspuren im Animationsfenster beheben, aber es gibt Fälle, in denen mehrere Animationen mit mehreren Kurven auf ein und dasselbe Objekt angewendet werden, was einen langsamen und fehleranfälligen Prozess darstellt. Die YAML-Bearbeitung ermöglicht es Ihnen stattdessen, mehrere Animationskurvenpfade auf einen Schlag zu korrigieren, indem Sie die Animationsdateien ganz klassisch mit dem Ihnen vertrauten Texteditor suchen und ersetzen".

Original YAML und Hierarchie links, umbenannte GameObject-Version rechts
Original YAML und Hierarchie links, umbenannte GameObject-Version rechts
Lokale Referenzen

Wie bereits erwähnt, hat jedes Objekt in einer YAML-Datei eine ID, die sogenannte "File ID". Diese ID ist für jedes Objekt innerhalb der Datei eindeutig und dient dazu, Verweise zwischen ihnen aufzulösen. Denken Sie an ein Spielobjekt und seine Komponenten, die Komponenten und ihr Spielobjekt oder sogar Skriptreferenzen, wie eine "Weapon"-Komponentenreferenz auf ein "SpawnPoint"-Spielobjekt in derselben Prefab.

Das YAML-Format hierfür ist "{fileID: FILE ID}" als Wert der Eigenschaft. In Abbildung 6 können Sie sehen, dass diese Transformation zu einem Spielobjekt mit der ID 4112328598445621100 gehört, da die Eigenschaft "m_GameObject" über die Datei-ID darauf verweist. Sie können auch Beispiele für Null-Referenzen wie "m_PrefabInstance" beobachten (da die Datei-ID Null ist). Lesen Sie weiter, um mehr über Fertighausinstanzen zu erfahren.

Code von Transform, der mit einem bestimmten Spielobjekt verbunden ist
Code von Transform, der mit einem bestimmten Spielobjekt verbunden ist

Betrachten wir den Fall des Reparierens von Objekten innerhalb einer Prefab. Sie können die Datei-ID der "m_Father"-Eigenschaft eines Transforms mit der Datei-ID des neuen Ziel-Transforms ändern und sogar die YAML des alten übergeordneten Transforms korrigieren, um dieses Objekt aus seinem "m_Children"-Array zu entfernen und es der neuen übergeordneten "m_Children"-Eigenschaft hinzuzufügen.

Verwandlung mit einem Elternteil und einem einzelnen Kind
Verwandlung mit einem Elternteil und einem einzelnen Kind

Um eine bestimmte Transformation anhand ihres Namens zu finden, müssen Sie zunächst ihre GameObject File ID ermitteln, indem Sie diejenige mit dem gesuchten m_Namen suchen. Nur dann können Sie den Transform finden, dessen Eigenschaft m_GameObject auf diese Datei-ID verweist.

Metadateien und dateiübergreifende Referenzen

Wenn Sie Objekte außerhalb dieser Datei referenzieren, wie z. B. ein "Weapon"-Skript, das eine "Bullet"-Prefab referenziert, werden die Dinge ein wenig komplexer. Beachten Sie, dass die Datei-ID dateispezifisch ist, d. h. sie kann in verschiedenen Dateien wiederholt werden. Um ein Objekt in einer anderen Datei eindeutig zu identifizieren, benötigen wir eine zusätzliche ID oder "GUID", die die gesamte Datei und nicht nur einzelne Objekte innerhalb der Datei identifiziert. Für jedes Asset ist diese GUID-Eigenschaft in seiner Metadatei definiert, die sich im selben Ordner wie die Originaldatei befindet und den exakt gleichen Namen mit der Erweiterung ".meta" trägt.

Bild einer Liste von Unity Assets und ihrer Metadateien
Bild einer Liste von Unity Assets und ihrer Metadateien

Für nicht Unity-eigene Dateiformate, wie PNG-Bilder oder FBX-Dateien, serialisiert Unity zusätzliche Importeinstellungen in den Metadateien, wie z. B. die maximale Auflösung und das Kompressionsformat einer Textur oder den Skalierungsfaktor eines 3D-Modells. Dies geschieht, um erweiterte Dateieigenschaften separat zu speichern und sie in nahezu jeder Versionskontrollsoftware bequem zu versionieren. Neben diesen Einstellungen speichert Unity aber auch allgemeine Asset-Einstellungen in der Metadatei, wie z.B. die GUID (Eigenschaft "GUID") oder das Asset-Bundle (Eigenschaft "assetBundleName"), sogar für Ordner oder Unity-eigene Formatdateien wie Materialien.

Code für Metadatei für eine Textur
Code für Metadatei für eine Textur

In diesem Sinne können Sie ein Objekt eindeutig identifizieren, indem Sie die GUID in der Metadatei und die Datei-ID des Objekts innerhalb der YAML kombinieren, wie in Abbildung 10 dargestellt. Konkret sehen Sie, dass YAML die Variable "bulletPrefab" eines Waffenskripts generiert hat, die auf das Root-GameObject mit der File-ID 4551470971191240028 der Prefab mit der GUID afa5a3def08334b95acd2d70ee44a7c2 verweist.

Code des Verweises auf ein anderes Dateiobjekt
Code des Verweises auf ein anderes Dateiobjekt

Sie können auch ein drittes Attribut namens "Typ" sehen. Mit Typ wird festgelegt, ob die Datei aus dem Ordner Assets oder aus dem Ordner Bibliothek geladen werden soll. Beachten Sie, dass nur die folgenden Werte, beginnend mit 2, unterstützt werden (da 0 und 1 veraltet sind):

  • Typ 2: Assets, die vom Editor direkt aus dem Assets-Ordner geladen werden können, wie Materialien und .asset-Dateien
  • Typ 3: Assets, die im Bibliotheksordner bearbeitet und geschrieben wurden und von dort vom Editor geladen werden, wie Prefabs, Texturen und 3D-Modelle

Ein weiterer wichtiger Faktor bei der Skript-Serialisierung ist, dass der YAML-Typ für alle Skripte gleich ist, nämlich MonoBehaviour. Das eigentliche Skript wird in der Eigenschaft "m_Script" referenziert, wobei die GUID der Metadatei des Skripts verwendet wird. Auf diese Weise können Sie beobachten, wie jedes Skript wie ein Vermögenswert behandelt wird.

MonoBehaviour YAML mit Verweis auf ein Script-Asset
MonoBehaviour YAML mit Verweis auf ein Script-Asset

Zu den Anwendungsfällen für dieses Szenario gehören unter anderem:

  • Auffinden aller Verwendungen eines Assets durch Suche nach der GUID des Assets in allen anderen Assets
  • Ersetzen aller Verwendungen dieses Assets durch eine andere Asset-GUID im gesamten Projekt
  • Ersetzen eines Assets durch ein anderes mit einer anderen Erweiterung (z. B. Ersetzen einer MP3-Datei durch eine WAV-Datei) durch Löschen des ursprünglichen Assets, Benennen des neuen Assets mit der neuen Erweiterung und Umbenennen der Metadatei des ursprünglichen Assets mit der neuen Erweiterung
  • Behebung verlorener Referenzen beim Löschen und erneuten Hinzufügen desselben Assets, indem die GUID der neuen Version durch die GUID der alten Version ersetzt wird
Vorgefertigte Instanzen, verschachtelte vorgefertigte Instanzen und Varianten

Bei der Verwendung von Prefab-Instanzen in einer Szene oder verschachtelten Prefabs innerhalb einer anderen Prefab werden die Prefab-GameObjects und -Komponenten nicht in der Prefab, die sie verwendet, serialisiert, sondern es wird ein PrefabInstance-Objekt hinzugefügt. Wie Sie in Abbildung 12 sehen können, hat die PrefabInstance zwei Schlüsseleigenschaften: "m_SourcePrefab" und "m_Modifications".

YAML für einen verschachtelten Prefab
YAML für einen verschachtelten Prefab

Wie Sie vielleicht schon bemerkt haben, ist "m_SourcePrefab" ein Verweis auf das verschachtelte Voreinstellungs-Asset. Wenn Sie nun die Datei-ID im verschachtelten Prefab-Asset suchen, werden Sie es nicht finden. In diesem Fall ist "100100000" die Datei-ID eines Objekts, das beim Import der Prefab erstellt wurde, das sogenannte Prefab Asset Handle, das in der YAML nicht vorhanden sein wird.

Darüber hinaus umfasst "m_Modifications" eine Reihe von Änderungen oder "Overrides", die an der ursprünglichen Prefab vorgenommen wurden. In Abbildung 12 überschreiben wir die X-, Y- und Z-Achsen der ursprünglichen lokalen Position einer Transformation innerhalb der verschachtelten Voreinstellung, die über ihre Datei-ID in der Zieleigenschaft identifiziert werden kann. Beachten Sie, dass die obige Abbildung 12 zur besseren Lesbarkeit gekürzt wurde. Eine echte PrefabInstance wird in der Regel mehr Einträge im Abschnitt m_Modifications haben.

Sie fragen sich jetzt vielleicht, wie wir auf Objekte in den verschachtelten Prefabs verweisen können, wenn wir die verschachtelten Prefab-Objekte nicht in unserem äußeren Prefab haben? Für solche Szenarien erstellt Unity ein "Platzhalter"-Objekt im Prefab, das auf das entsprechende Objekt im Nested Prefab verweist. Diese Platzhalterobjekte sind mit dem Tag "stripped" gekennzeichnet, was bedeutet, dass sie nur mit den Eigenschaften vereinfacht werden, die für die Funktion als Platzhalterobjekte erforderlich sind.

Platzhalter Verschachtelte vorgefertigte Transformation, die von ihren Kindern referenziert wird
Platzhalter Verschachtelte vorgefertigte Transformation, die von ihren Kindern referenziert wird

Abbildung 13 zeigt in ähnlicher Weise einen mit dem Tag "stripped" gekennzeichneten Transform, der nicht über die üblichen Eigenschaften eines Transforms (wie "m_LocalPosition") verfügt. Stattdessen sind die Eigenschaften "m_CorrespondingSourcePrefab" und "m_PrefabInstance" so gefüllt, dass sie auf das verschachtelte Voreinstellungs-Asset und das PrefabInstance-Objekt in der zugehörigen Datei verweisen. Darüber ist ein Teil einer anderen Transformation zu sehen, deren "m_Father" auf diesen Platzhalter Transform verweist, so dass dieses GameObject ein Kind des Nested Prefab-Objekts ist. Je mehr Objekte Sie in den Nested Prefabs referenzieren, desto mehr dieser Platzhalter-Objekte werden in die YAML eingefügt.

Praktischerweise gibt es keinen Unterschied, wenn es um vorgefertigte Varianten geht. Die Basis-Prefab einer Variante ist einfach eine PrefabInstance mit einem Transform, die kein Elternteil hat, d. h. sie ist das Stammobjekt der Variante. In Abbildung 14 ist zu sehen, dass die Eigenschaft "m_TransformParent" der PrefabInstance auf "fileID: 0.” Das bedeutet, dass es keinen Vater hat und somit das Stammobjekt ist.

Code der Prefab-Instanz ohne übergeordnete Instanz, die damit zur Basis-Prefab für die Datei wird
Code der Prefab-Instanz ohne übergeordnete Instanz, die damit zur Basis-Prefab für die Datei wird

Sie können dieses Wissen zwar nutzen, um eine verschachtelte Voreinstellung oder die Basisvoreinstellung einer Variante durch eine andere zu ersetzen, aber diese Art der Änderung kann riskant sein. Gehen Sie mit Vorsicht vor und halten Sie für den Fall der Fälle ein Backup bereit.

Beginnen Sie mit dem Ersetzen aller Verweise auf die GUID der aktuellen Basis-Prefab durch die GUID der neuen, sowohl im PrefabInstance-Objekt als auch in den Platzhalterobjekten. Achten Sie darauf, dass Sie die Datei-IDs der Platzhalterobjekte beachten. Ihre "m_CorrespondingSourceObject"-Eigenschaften verweisen nicht nur auf das Asset, sondern auch auf die darin enthaltenen Objekte über ihre Datei-IDs. Es ist sehr wahrscheinlich, dass sich die Datei-IDs der Objekte in der aktuellen Voreinstellung von denen in der neuen Voreinstellung unterscheiden - und wenn Sie diese nicht korrigieren, verlieren Sie Überschreibungen, Verweise, Objekte und andere Daten.

Wie Sie sehen, ist das Ändern einer Basis oder eines verschachtelten Prefab nicht so einfach, wie man vielleicht denkt. Das ist einer der Hauptgründe dafür, dass es im Editor nicht nativ unterstützt wird.

Veraltete Referenzen

Es gibt verschiedene Szenarien, in denen veraltete Objekte und Referenzen in YAML belassen werden können; ein klassischer Fall wäre das Entfernen von Variablen in Skripten. Wenn Sie ein Waffenskript zur Player Prefab hinzufügen, müssen Sie die Bullet Prefab-Referenz auf eine bestehende Prefab setzen und dann die Bullet Prefab-Variable aus dem Waffenskript entfernen. Solange Sie die Player-Voreinstellung nicht ändern und erneut speichern und dabei neu serialisieren, bleibt der Aufzählungspunkt in YAML erhalten. Ein weiteres Beispiel sind Platzhalterobjekte in verschachtelten Prefabs, die nicht entfernt werden, wenn das Objekt aus der ursprünglichen Prefab gelöscht wird, was wiederum durch Ändern und Speichern der Prefab behoben werden könnte. Schließlich kann die erneute Serialisierung von Assets durch Skripting mit der AssetDatabase.ForceReserializeAssets-API erzwungen werden.

Aber warum entfernt Unity in den oben genannten Fällen nicht automatisch veraltete Verweise? Dies ist in erster Linie auf die Leistung zurückzuführen; es soll verhindert werden, dass bei jeder Änderung eines Skripts oder einer Basisvorlage alle Assets neu serialisiert werden. Ein weiterer Grund ist die Vermeidung von Datenverlusten. Nehmen wir an, Sie haben versehentlich eine Skripteigenschaft (z. B. Bullet Prefab) entfernt und möchten sie wiederherstellen. Sie müssen nur die Änderung in Ihrem Skript rückgängig machen. Solange Sie eine Variable mit dem gleichen Namen wie die entfernte Variable haben, gehen Ihre Änderungen nicht verloren. Das Gleiche würde passieren, wenn Sie den referenzierten Bullet Prefab löschen. Wenn Sie die Voreinstellung genau so wiederherstellen, wie sie war, einschließlich der Metadatei, bleibt die Referenz erhalten.

Dies ist normalerweise kein Problem während der Laufzeit, da diese veralteten Objekte und Referenzen gelöscht werden, wenn Unity den Player oder die Addressables erstellt. Aber selbst dann gibt es einige Fälle, in denen veraltete Referenzen Probleme verursachen können - nämlich bei der Verwendung reiner Asset-Bündel. Bei der Berechnung der Asset-Bundle-Abhängigkeit werden veraltete Referenzen berücksichtigt, die unnötige Abhängigkeiten zwischen Bundles schaffen und mehr als erforderlich zur Laufzeit laden können. Dies sollte bei der Verwendung von Asset-Bündeln bedacht werden. Erstellen Sie ein Tool oder verwenden Sie ein vorhandenes Tool, um unnötige Verweise zu entfernen.

Fazit

Obwohl man YAML die meiste Zeit über ignorieren kann, ist es für das Verständnis des Serialisierungssystems von Unity nützlich, es zu verstehen. Auch wenn große Refactors und das direkte Lesen oder Ändern von YAML mit Asset-Verarbeitungstools schnell und effektiv sein können, ist die Suche nach Lösungen, die auf der Unity Asset Database API basieren, sehr empfehlenswert, wann immer dies möglich ist. Sie ist auch besonders hilfreich bei der Lösung von Zusammenführungsproblemen in der Versionskontrolle. Wir empfehlen Ihnen, sich mit dem Smart Merge-Tool vertraut zu machen, mit dem Sie widersprüchliche Prefabs automatisch zusammenführen können. Weitere Informationen zu YAML finden Sie in unserer offiziellen Dokumentation.