Automatisiertes befüllen des Sprite Atlas

Einleitung

In diesem Blogartikel will ich weiter auf das Unterthema Sprite Atlas meines vorherigen Artikels eingehen. In einem so großen Repository wie dem MRTK kommt es häufig zu Änderungen, die dazu führen können, dass die Referenzen im Sprite Atlas nicht mehr gültig sind. Das Ziel ist es also herauszufinden, ob es möglich ist, alle Sprites eines Prefabs in einem Sprite Atlas zu hinterlegen und dies per Knopfdruck oder automatisch auszuführen.

Dieses Skript besteht also aus drei Schritten:

  1. Sprite Atlas und Prefab Referenzen finden
  2. Alle Sprites der Prefabs sammeln
  3. Sprites im Sprite Atlas serialisieren

Als erstes bauen wir eine Verknüpfung zwischen den Prefabs und dem Atlas auf. Scriptable Objects sind hierfür gut geeignet. Wir legen ein Template in Form einer Klasse an und erstellen damit eine persistente Instanz im Unity-Editor. Dies erlaubt uns auch beliebig viele Atlas/Prefab Kombinationen zu erstellen. Weiterhin fügen wir einen Eintrag im Create-Kontextmenü hinzu um eine Instanz erstellen zu können.

Wir bemühen das Kontextmenü im Projekt-Fenster um eine Instanz unseres ScriptableObjects zu erzeugen und befüllen es mit unseren Daten.

Einstiegspunkte

Unity bietet eine Vielzahl von Events und Callbacks an, um automatisierte Skripte schreiben zu können. Diese Events sind generell über eine von vier Möglichkeiten verteilt, mit denen sie implementiert werden können. Die erste Methode ist das Implementieren von Interfaces (Build.IPreprocessBuild). Als zweites kann man Events von Basisklassen erben (AssetModificationProcessor). Als drittes kann man Klassen und Methoden mit Attributen versehen (InitializeOnLoadMethod). Zuletzt gibt es noch Delegates (PrefabInstanceUpdated).

Für manche Events macht es auch Sinn, eine Verzögerung einzubauen. Es gibt beispielsweise nur OnWillSaveAssets, jedoch kein OnSaveAssets. Wenn man also das tatsächlich gespeicherte Asset braucht, kann man mit EditorApplication.delayCall den Aufruf verzögern.

Für meine Zwecke benutze ich diese Events um Änderungen der Sprite Atlas, Prefabs und Sprites mitzubekommen:

Mit einem der Events als Einstiegspunkt holen wir uns jetzt alle Instanzen unserer zuvor erstellten Scriptable Objects. Hierfür benutze ich die Editor-Methode AssetDatabase.FindAssets. Mit Scriptable Objects kann man auch die üblichen Lifecycle-Methoden wie Awake, OnEnable, OnDisable und OnDestroy benutzen, jedoch werden diese nur aufgerufen, wenn das Objekt auch in den Arbeitsspeicher geladen wurde, durch etwa verwenden in der Szene oder Selektierung mit dem Inspektor.

Nachdem wir in den verschiedenen Events überprüft haben, ob es Updates für uns gibt, brauchen wir alle Sprites der Prefabs, um diese in den Atlas zu schreiben. Die Sprites sind jeweils in Image UI Komponenten enthalten und somit können wir uns diese einfach mit GetComponentsInChildren<Image> holen. Der Atlas ignoriert Duplikate beim Verarbeiten der Sprites, jedoch werden diese trotzdem mehrfach in der Sprite-Liste aufgeführt also entfernen wir sie noch. Zuletzt schreiben wir die Sprites noch in den Atlas mit einer Extension, die ich im nächsten Abschnitt beschreiben werde.

Modifikation von SerializedObject

Zurzeit gibt es keine offene API um Sprites direkt in einen Sprite Atlas zu schreiben, jedoch können wir diese über die serialisierten Eigenschaften des Sprite Atlas modifizieren. Ähnlich wie bei normalen Datei-Operationen müssen wir hier diese Eigenschaften öffnen und schließen. Man „öffnet“ die serialisierte Datei zu einem Unity-Objekt in dem man einfach den Konstruktor von SerializedObject mit dem Unity-Objekt benutzt und nachdem man Änderungen vorgenommen hat, speichert man diese mit ApplyModifiedProperties.

Um Änderungen vorzunehmen brauchen wir den Namen der Eigenschaft in der serialisierten Datei. Diesen finden wir heraus, wenn wir den Sprite Atlas in einem Text-Editor wie beispielsweise Notepad++ öffnen (vorausgesetzt die Serialisierung ist auf Text eingestellt).

Die Sprites werden unter „m_EditorData.packables“ als Objekt im Atlas hinterlegt. Mit diesem Pfad können wir jetzt die Eigenschaft mit FindProperty auslesen und modifizieren. Da es sich hier um ein Array von Objekten handelt leeren wir es zuerst und fügen jeweils ein neues leeres Element ein, in diese wir dann unsere Sprites schreiben.

Abschluss

Mit dem Abschluss dieses Editor-Skripts wird der Sprite Atlas jetzt automatisch bei Änderung mit den Sprites der Prefabs befüllt und so ist sichergestellt, dass dieser immer auf dem neusten Stand ist. Falls noch weitere relevante Events identifiziert werden können diese auch sehr einfach noch hinzugefügt werden.

Die Beschreibung des Sprite Atlas über die serialisierten Eigenschaften ist leider nicht die schönste Lösung, jedoch gibt es zurzeit keine andere Möglichkeit. Ich bin mir sicher diese API wird in Zukunft noch erweitert um einen direkten Weg zu schaffen, Sprites dem Atlas hinzuzufügen.

Vollständiger Code