Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Aspect Ratio = Seitenverhältnis

UI Layering

Layering - Basics

Für einfaches Layering gilt folgende Hierarchie:

  1. Sorting Layer
  2. Sorting Wert
  3. Distanz zur Kamera (Transparenzachse; standardmäßig entlang Z)

[Beispiel sprite renderer]

Bedeutet also:

  1. Sind zwei Objekte auf dem gleichen Sorting-Layer?
    1. Ja - Sorting-Wert wird betrachtet.
    2. Nein - Das höhere Layer wird vorne angezeigt.
  2. Haben die Objekte den gleichen Sorting-Wert?
    1. Ja - Die Z-Position wird betrachtet.
    2. Nein - Der höhere Sorting-Wert ist vorne.
  3. Der geringere Z-Wert ist vorne.

Anmerkungen: 

  • Diese Hierarchie ist vereinfacht
    es gibt noch diverse andere Möglichkeiten Renderer zu sortieren. Die genaue Aufschlüsselung findet sich hier.
  • Es gibt standardmäßig 2 Render Queues "Opaque" und "Transparent", welche auch in dieser Reihenfolge sortiert werden.
    Im Folgenden wird hauptsächlich von Objekten in der Transparent Queue gesprochen.
  • Die Transparenzachse kann man umändern
    z.B. auf Y an Stelle von Z. Das ist beispielsweise hilfreich für 2D top-down Spiele-

Performance Tipps

...

Um bessere Performance aus der UI herauszuholen müssen wir uns vor allem zweier Dinge bewusst sein:

  1. Sämtliche UI ist transparent.
  2. Wie funktionieren Canvas und Layouts oder genauer gesagt, wann müssen sie erneuert werden?

Bevor es jetzt weiter geht noch ein Hinweis: Wie bei allem was mit Performance zu tun hat, gibt es kaum einen universell besten Weg Dinge anzugehen, sondern spezifische Lösungen für Probleme mit der eigenen UI. Diese Lösungen können dann wieder an anderen Stellen Dinge kaputt machen, beispielsweise sind mehrere Canvasses eine gute Idee, jedoch erhöhen sie auch die Anzahl an Batches; Spritemesh wird Overdraw verringern, allerdings gibt es jetzt mehr Polygone und man muss die Importeinstellung "Tight" verwenden. Es ist also ein Ausprobieren und Abschätzen, was für einen den meisten Mehrwert bringt. Am Ende kann wahrscheinlich nur der Profiler wirklich bei der Entscheidung helfen.

Overdraw verringern

Gerade auf Mobile ist Overdraw ein Performance-Killer. Er entsteht, wenn sehr viele transparente Objekte über einander liegen. Es werden also oft Pixel gerendered, die später im Rendervorgang überschrieben werden, weil sie tatsächlich verdeckt sind.

Nochmal: Die gesamte UI ist transparent. 

Es ist also allgemein ratsam beim Bau von Elementen darauf zu achten, dass alle statischen Elemente zu einer Textur zusammengeführt werden. Hierzu ein Beispiel:
[Beispiel vorher nachher]

Man kann bereits beim Export von Grafiken darauf achten, dass sie möglichst wenig leere Pixel enthalten und beispielsweise nicht unvorteilhaft rotiert sind.

[Beispiel]

Bei sehr detailierten Sprites kann es sich lohnen, die Funktion "Use Sprite Mesh" der Image-Komponente zu verwenden.

[Beispiel vorher nachher]

Man kann sehen, dass nun ein mehr oder minder genaues Mesh des Sprites verwendet wird, anstelle eines einzelnen Rechtecks. Dafür wird allerdings die Einstellung "Mesh Type - Tight" beim Import benötigt.

Unity bietet Möglichkeiten an Overdraw zu visualisieren - welche es gibt ist abhängig von der verwendeten Renderpipeline. Der Modus für die Built-in-Renderpipeline sieht beispielsweise so aus:

[Beispiel]

Effiziente Nutzung des Canvas

Allgemein wird der Inhalt von Canvasses über ein generiertes dynamisches Mesh gerendered. Dieses Mesh wird jedes mal neu generiert wenn sich etwas innerhalb des Canvas verändert. Wenn sich also ein einzelnes Element verändert, muss das ganze Canvas neu generiert werden. Zusätzlich dazu muss auch noch das Layout neu generiert werden - warum das ebenfalls schlecht ist kommt später im Abschnitt Layouts.

Eine erste Faustregel ist es also mehrere Canvasses zu benutzen und nicht die gesamte UI in ein riesiges Canvas zu Packen.

Im nächsten Schritt kann man dann ein jedes Canvas noch unterteilen, indem man Sub - bzw. Nested Canvasses hinzufügt.

Nested Canvasses

Einen Beispiel Artikel von Unity findet man hier.

Die grundsätzliche Idee ist es UI in statische bzw. dynamische Elemente und damit auch Canvasse zu unterteilen. Dadurch sollen Elemente die sich manchmal bis sehr häufig verändern von denen, die das gar nicht tun, abgeschottet werden, damit sie nicht in Neu-Berechnungen eingeschlossen werden.

Im Artikel von Unity geht es um einen Timer. Dieser besteht aus einem Text und einer Zeitanzeige. Es ist klar, dass sich der Text "Current time" nie ändern wird, während die Anzeige der Zeit sich kontinuierlich updated. Entsprechend bringen wird die Zeitanzeige in ein eigenes Subcanvas, unter dem Canvas auf dem der Text liegt. Wenn sich jetzt die Zeit verändert, wird nur das Subcanvas neu berechnet - der Text bleibt unberührt.

[Beispiel]

...

Performance Tipps

Anchor
Performance
Performance

Um bessere Performance aus der UI herauszuholen müssen wir uns vor allem zweier Dinge bewusst sein:

  1. Sämtliche UI ist transparent.
  2. Wie funktionieren Canvas und Layouts oder genauer gesagt, wann müssen sie erneuert werden?

Bevor es jetzt weiter geht noch ein Hinweis: Wie bei allem was mit Performance zu tun hat, gibt es kaum einen universell besten Weg Dinge anzugehen, sondern spezifische Lösungen für Probleme mit der eigenen UI. Diese Lösungen können dann wieder an anderen Stellen Dinge kaputt machen, beispielsweise sind mehrere Canvasses eine gute Idee, jedoch erhöhen sie auch die Anzahl an Batches; Spritemesh wird Overdraw verringern, allerdings gibt es jetzt mehr Polygone und man muss die Importeinstellung "Tight" verwenden. Es ist also ein Ausprobieren und Abschätzen, was für einen den meisten Mehrwert bringt. Am Ende kann wahrscheinlich nur der Profiler wirklich bei der Entscheidung helfen.

Overdraw verringern

Gerade auf Mobile ist Overdraw ein Performance-Killer. Er entsteht, wenn sehr viele transparente Objekte über einander liegen. Es werden also oft Pixel gerendered, die später im Rendervorgang überschrieben werden, weil sie tatsächlich verdeckt sind.

Nochmal: Die gesamte UI ist transparent. 

Es ist also allgemein ratsam beim Bau von Elementen darauf zu achten, dass alle statischen Elemente zu einer Textur zusammengeführt werden. Hierzu ein Beispiel:
[Beispiel vorher nachher]

Man kann bereits beim Export von Grafiken darauf achten, dass sie möglichst wenig leere Pixel enthalten und beispielsweise nicht unvorteilhaft rotiert sind.

[Beispiel]

Bei sehr detailierten Sprites kann es sich lohnen, die Funktion "Use Sprite Mesh" der Image-Komponente zu verwenden.

[Beispiel vorher nachher]

Man kann sehen, dass nun ein mehr oder minder genaues Mesh des Sprites verwendet wird, anstelle eines einzelnen Rechtecks. Dafür wird allerdings die Einstellung "Mesh Type - Tight" beim Import benötigt.

Unity bietet Möglichkeiten an Overdraw zu visualisieren - welche es gibt ist abhängig von der verwendeten Renderpipeline. Der Modus für die Built-in-Renderpipeline sieht beispielsweise so aus:

[Beispiel]

Effiziente Nutzung des Canvas

Allgemein wird der Inhalt von Canvasses über ein generiertes dynamisches Mesh gerendered. Dieses Mesh wird jedes mal neu generiert wenn sich etwas innerhalb des Canvas verändert. Wenn sich also ein einzelnes Element verändert, muss das ganze Canvas neu generiert werden. Zusätzlich dazu muss auch noch das Layout neu generiert werden - warum das ebenfalls schlecht ist kommt später im Abschnitt Layouts.

Eine erste Faustregel ist es also mehrere Canvasses zu benutzen und nicht die gesamte UI in ein riesiges Canvas zu Packen.

Im nächsten Schritt kann man dann ein jedes Canvas noch unterteilen, indem man Sub - bzw. Nested Canvasses hinzufügt.

Nested Canvasses

Einen Beispiel Artikel von Unity findet man hier.

Die grundsätzliche Idee ist es UI in statische bzw. dynamische Elemente und damit auch Canvasse zu unterteilen. Dadurch sollen Elemente die sich manchmal bis sehr häufig verändern von denen, die das gar nicht tun, abgeschottet werden, damit sie nicht in Neu-Berechnungen eingeschlossen werden.

Im Artikel von Unity geht es um einen Timer. Dieser besteht aus einem Text und einer Zeitanzeige. Es ist klar, dass sich der Text "Current time" nie ändern wird, während die Anzeige der Zeit sich kontinuierlich updated. Entsprechend bringen wird die Zeitanzeige in ein eigenes Subcanvas, unter dem Canvas auf dem der Text liegt. Wenn sich jetzt die Zeit verändert, wird nur das Subcanvas neu berechnet - der Text bleibt unberührt.

[Beispiel]

Layouts
Anchor
PerformanceLayouts
PerformanceLayouts

Auto Layout funktioniert über ein sog. "dirty flag"-System. Wenn sich ein Layoutelement verändert und es damit einen umgebenden Layoutcontroller ungültig macht (z.B. Size oder Scale verändert), wird es als "dirty" markiert, worauf das Layoutsystem dann reagieren kann.

Problem: Layoutelemente sind Komponenten, also kann auf jedem Element oder auch Parent eins oder mehrere sein.

Um die Neu-Berechnung des Layouts korrekt auszuführen, wird nach dem Layoutcontroller gesucht, der am weitesten oben in der Hierarchie steht. Das ganze passiert natürlich über GetComponent() auf jedem Objekt. So wird jedes Element, dass sein Layout auf dirty setzen will, minimal einen Aufruf von GetComponent() nach sich ziehen. Jeder geschaltete Layoutcontroller vervielfältigt dieses Problem.

Wodurch wird ein Layout als dirty markiert?

Kurzform: Durch fast alles ...

Nur ein paar Beispiele:

  • OnEnable() und OnDisable(),
  • Reparenting,
    Doppelt; einmal für den alten und neuen Parent
  • OnDidApplyAnimationProperties(),
    Wenn ein Animator mit dem Element interagiert
  • OnRectTransformDimensionsChanged()
    z.B. Skalieren oder Resizing, Änderung von Position oder Kamera


Lösungen: Was kann man dagegen tun?

  • Vermeidet Layoutelemente wo es geht,
    Oft genug reicht es nur mit festen oder relativen Ankern zu arbeiten.
  • Deaktiviert Layoutelemente nachdem sie ihre Arbeit getan haben,
    Viele Layouts müssen sich nur einmal nach dem Laden des Spiels aufbauen und verändern sich dann nicht mehr. Das gilt insbesondere für statische Anzeigen. Auf all diesen Objekten kann man die Layoutelemente deaktivieren, um sich das dirtying zu sparen.
  • Deaktiviert die Canvas Komponente an Stelle des GameObjects, wenn ihr ein ganzes Canvas ausblenden wollt,
    Dadurch wird nichts mehr angezeigt, aber man spart sich OnDisable() und OnEnable() + dirtying.
  • Suche nach zuständigem Layoutcontroller über leeres Objekt unterbrechen.
    Die Suche nach oben wird beendet, sobald kein Layoutcontroller mehr gefunden wird. Das ist vor allem hilfreich, wenn man Objekte über eine Animation skalieren will, dies aber nicht das Layout beeinflussen soll (z.B. Button drücken mit leichtem bounce).

Der Animator

Kurz gesagt: Benutzt keinen Animator für die UI. Stattdessen solltet ihr Code und/oder Tweens verwenden um eure UI zu animieren. Für Tweens gibt es das sehr gute Package DotweenPro.

[Beispiel]

Warum ist ein Animator Setup, wie auf dem Standard-Button von Unity, schlecht?

Ein Animator wendet jeden Frame seine Werte auf die animierten Objekte an, egal ob sich Werte in der Animation verändert haben oder nicht. Das bedeutet, dass jeden Frame alle animierten Objekte als dirty markiert werden, was wie bereits beschrieben diverse Aktionen im Layout bzw. Canvas Code nach sich zieht.

Entsprechend ist der Animator nicht dafür geeignet States abzubilden - wie z.B. hover, selected im Standard-Button Beispiel - sondern sollte nur zum Abspielen von Animationen verwendet werden, da sich in diesem meist sowieso jeden Frame etwas verändert, was dann dirtying nach sich zieht.

Graphics Raycaster

Den Graphics Raycaster benötigt man auf jedem Canvas (und Sub-Canvas!) das (Touch-)Input erhalten soll. Tatsächlich ist er nicht wirklich ein Raycaster, sondern prüft ob sich ein Punkt innerhalb eines Rechtecks befindet und das für jedes RectTransform unter dem Canvas, dass als interaktiv markiert ist. Als interaktiv markiert sind alle Komponenten mit dem Toggle "Raycast Target" auf an (beispielsweise Image).

[Beispiel]

Faustregel: Achte darauf den Toggle auszuschalten, wo es Sinn macht!

Sorting Group

Mit der Sorting Group gruppiert man ein GameObject und seine Childs als Einheit für das Rendern - ähnlich wie es das Canvas tut. Das bedeutet, dass außerhalb der Gruppe die Sortieroptionen und die Z-Position des Gruppen-Parents für alle Childs gelten.

[Beispiel]

Innerhalb der Gruppe werden Renderer nach den für sie geltenden Regeln sortiert: Beispielsweise gelten für Sprites Sorting-Layer und Wert, während Z zugunsten der Hierarchie ignoriert wird (erneut, wie beim Canvas). Bei 3D-Renderern wird nach Z sortiert und bei gleichem Z-Wert der Hierarchie nach.

[Beispiele]

Insbesondere bei 3D-Renderern ist wichtig, dass Sorting nur auf transparente Geometry angewendet wird. Wird der Render-Mode "Opaque" verwendet sortieren 3D-Renderer mit Sorting-Groups trotzdem über die Z-Position gegen andere Sorting-Groups.

[Beispiel]

Sorting groups sind für zwei Anwendungen wichtig:

  1. Gruppieren von Objekten, die optisch eine Einheit sein sollen - z.B. Charakter aus mehreren Sprites,
  2. Anwendung von Sortieroptionen auf 3D-Renderer.

Layering - Beispiel

Anchor
LayeringBeispiel
LayeringBeispiel

Wie man seine UI am besten layered ist sehr stark vom Spiel abhängig. Um eine Idee davon zu haben kann man sich beispielsweise folgende Fragen stellen:

  • Ist die UI immer vorne?

Die UI könnte ein klassisches HUD sein - in diesem Fall nutzt man ganz einfach den Modus "Screen Space - Overlay" und sortiert verschiedene Canvasse über den Sorting Wert.

  • Benutze ich Z als Achse für Sortierung von Transparenz?

In diesem Fall würde ich keinen "Screen Space - Camera" Modus verwenden. Diese Art von Canvas sortiert sich immer nach Abstand zur Kamera auf Z - ihr X- und Y-Wert ist immer 0. Entsprechend könnten Weltobjekte nun ungewollt vor der UI erscheinen und es wird unmöglich die Sortierung im Scene View nachzuvollziehen.

  • Soll UI dynamisch Spielobjekten folgen?

Canvas mit "World Space" ist dein Freund. Sortiert werden kann das Canvas dann über Abstand zur Kamera oder mittels Sortieroptionen.

  • Was ist wenn UI hinter Spielobjekten auftauchen soll?

Angenommen wir haben ein 2D-Spiel mit Seitenansicht. Es wäre denkbar, dass z.B. Texte zwischen Spielebene und Hintergrund auftauchen sollen.

[Beispiel]

Für diesen Effekt sollten wir "Screen Space - Camera" oder "World Space" verwenden. Mit beiden Optionen können wir entsprechend unserer Szene die UI entweder über Tiefe oder Sortieroptionen einsortieren. Wenn sich diese UI dem Bildschirm oder der Kamera anpassen soll ist "Screen Space - Camera" zu verwenden.

Ein solches Setup erlaubt es auch Partikelsysteme in der Welt vor bzw. hinter der UI zu rendern. Warum das sehr praktisch ist steht im Kapitel über Partikel.

Wenn UI gegen Welt sortiert wird stellt sich dann die Frage nach der Sortierung:

  • Benutze ich Sortieroptionen oder Distanz zur Kamera bzw. Transparenzachse Z?

Meiner Meinung nach schließen sich diese beiden Optionen ein wenig aus und bieten unterschiedliche Vor- bzw. Nachteile. Das liegt vor allem an der zuvor beschriebenen einfachen Hierarchie - Sortieroptionen werden immer vor Z angewendet. Letztendlich muss die Wahl hier vor allem anderen mit der Sortierung der Spielszenen funktionieren.

Ich halte folgende Kombinationen für sinnvoll:

  • Alles sortiert sich nach Tiefe,
  • Alles sortiert sich mit den Sortieroptionen,
  • Global werden Sortieroptionen verwendet, aber UI sortiert sich intern nach Z.

Eine Sortierung nach Tiefe bietet vor allem den "What you see is what you get" - Vorteil. Wenn wir im Scene View auf 3D schalten, können wir unser Layering ganz bequem überblicken.

[Beispiel]

Die verschiedenen Ebenen der Spielszene könnten mit einer Sorting Group versehen werden, damit man diese intern über Sortieroptionen sortieren kann.

[Beispiel]

Nachteil ist natürlich, dass sie nicht funktioniert, wenn Z nicht die Transparenzachse ist - also für Top-Down eher nicht zu gebrauchen.

Wenn die Transparenzachse oder die Szenenhierarchie eine Sortierung per Tiefe nicht zulässt, eignen sich die Sortieroptionen am besten, auch wenn sie die Sortierung unübersichtlicher machen.

Ein Mittelweg kann es sein allein die Sortierung der UI über Z zu regeln und für die Welt Sortieroptionen zu nutzen. Dafür würde man alle Canvasses auf ein Vordergrundlayer verschieben, das über dem Weltlayer liegt. So behält man sich den Überblick der Tiefensortierung bei. 

[Beispiel]

Device Simulator

[Beispiel - wie schaltet man ihn an?]

Mit dem Device Simulator kann man sehen, wie sich unter anderem die UI auf verschiedenen Geräten verhält. Es ist auch möglich das Gerät zu drehen und die Safe Zone anzeigen zu lassen.

[Beispiel]

Man sollte stets verschiedenste gängige Geräte testen - mindestens Android bzw. iOS jeweils als Phone und Tablet. Insbesondere bei der Safe Zone unterscheiden sich die Geräte sehr:

[Beispiel]

Apple macht die Safe Zone gerne symmetrisch und hat außerdem eine vertikale Safe Zone für den Home-Button.

[Beispiel]

Android ist hingegen oft asymmetrisch.

[Beispiel]

Tablets haben oft genug gar keine Safe Zone.


Um all diese Dinge sehen zu können und entsprechend mit Code oder Design zu reagieren, ist der Device Simulator unabdingbar

...

Auto Layout funktioniert über ein sog. "dirty flag"-System. Wenn sich ein Layoutelement verändert und es damit einen umgebenden Layoutcontroller ungültig macht (z.B. Size oder Scale verändert), wird es als "dirty" markiert, worauf das Layoutsystem dann reagieren kann.

Problem: Layoutelemente sind Komponenten, also kann auf jedem Element oder auch Parent eins oder mehrere sein.

Um die Neu-Berechnung des Layouts korrekt auszuführen, wird nach dem Layoutcontroller gesucht, der am weitesten oben in der Hierarchie steht. Das ganze passiert natürlich über GetComponent() auf jedem Objekt. So wird jedes Element, dass sein Layout auf dirty setzen will, minimal einen Aufruf von GetComponent() nach sich ziehen. Jeder geschaltete Layoutcontroller vervielfältigt dieses Problem.

Wodurch wird ein Layout als dirty markiert?

Kurzform: Durch fast alles ...

Nur ein paar Beispiele:

  • OnEnable() und OnDisable(),
  • Reparenting,
    Doppelt; einmal für den alten und neuen Parent
  • OnDidApplyAnimationProperties(),
    Wenn ein Animator mit dem Element interagiert
  • OnRectTransformDimensionsChanged()
    z.B. Skalieren oder Resizing, Änderung von Position oder Kamera

Lösungen: Was kann man dagegen tun?

  • Vermeidet Layoutelemente wo es geht,
    Oft genug reicht es nur mit festen oder relativen Ankern zu arbeiten.
  • Deaktiviert Layoutelemente nachdem sie ihre Arbeit getan haben,
    Viele Layouts müssen sich nur einmal nach dem Laden des Spiels aufbauen und verändern sich dann nicht mehr. Das gilt insbesondere für statische Anzeigen. Auf all diesen Objekten kann man die Layoutelemente deaktivieren, um sich das dirtying zu sparen.
  • Deaktiviert die Canvas Komponente an Stelle des GameObjects, wenn ihr ein ganzes Canvas ausblenden wollt,
    Dadurch wird nichts mehr angezeigt, aber man spart sich OnDisable() und OnEnable() + dirtying.
  • Suche nach zuständigem Layoutcontroller über leeres Objekt unterbrechen.
    Die Suche nach oben wird beendet, sobald kein Layoutcontroller mehr gefunden wird. Das ist vor allem hilfreich, wenn man Objekte über eine Animation skalieren will, dies aber nicht das Layout beeinflussen soll (z.B. Button drücken mit leichtem bounce).

Der Animator

Kurz gesagt: Benutzt keinen Animator für die UI. Stattdessen solltet ihr Code und/oder Tweens verwenden um eure UI zu animieren. Für Tweens gibt es das sehr gute Package DotweenPro.

[Beispiel]

Warum ist ein Animator Setup, wie auf dem Standard-Button von Unity, schlecht?

Ein Animator wendet jeden Frame seine Werte auf die animierten Objekte an, egal ob sich Werte in der Animation verändert haben oder nicht. Das bedeutet, dass jeden Frame alle animierten Objekte als dirty markiert werden, was wie bereits beschrieben diverse Aktionen im Layout bzw. Canvas Code nach sich zieht.

Entsprechend ist der Animator nicht dafür geeignet States abzubilden - wie z.B. hover, selected im Standard-Button Beispiel - sondern sollte nur zum Abspielen von Animationen verwendet werden, da sich in diesem meist sowieso jeden Frame etwas verändert, was dann dirtying nach sich zieht.

Graphics Raycaster

Den Graphics Raycaster benötigt man auf jedem Canvas (und Sub-Canvas!) das (Touch-)Input erhalten soll. Tatsächlich ist er nicht wirklich ein Raycaster, sondern prüft ob sich ein Punkt innerhalb eines Rechtecks befindet und das für jedes RectTransform unter dem Canvas, dass als interaktiv markiert ist. Als interaktiv markiert sind alle Komponenten mit dem Toggle "Raycast Target" auf an (beispielsweise Image).

[Beispiel]

Faustregel: Achte darauf den Toggle auszuschalten, wo es Sinn macht!

Sorting Group

Mit der Sorting Group gruppiert man ein GameObject und seine Childs als Einheit für das Rendern - ähnlich wie es das Canvas tut. Das bedeutet, dass außerhalb der Gruppe die Sortieroptionen und die Z-Position des Gruppen-Parents für alle Childs gelten.

[Beispiel]

Innerhalb der Gruppe werden Renderer nach den für sie geltenden Regeln sortiert: Beispielsweise gelten für Sprites Sorting-Layer und Wert, während Z zugunsten der Hierarchie ignoriert wird (erneut, wie beim Canvas). Bei 3D-Renderern wird nach Z sortiert und bei gleichem Z-Wert der Hierarchie nach.

[Beispiele]

Insbesondere bei 3D-Renderern ist wichtig, dass Sorting nur auf transparente Geometry angewendet wird. Wird der Render-Mode "Opaque" verwendet sortieren 3D-Renderer mit Sorting-Groups trotzdem über die Z-Position gegen andere Sorting-Groups.

[Beispiel]

Sorting groups sind für zwei Anwendungen wichtig:

  1. Gruppieren von Objekten, die optisch eine Einheit sein sollen - z.B. Charakter aus mehreren Sprites,
  2. Anwendung von Sortieroptionen auf 3D-Renderer.

Layering - Beispiel

...

Wie man seine UI am besten layered ist sehr stark vom Spiel abhängig. Um eine Idee davon zu haben kann man sich beispielsweise folgende Fragen stellen:

  • Ist die UI immer vorne?

Die UI könnte ein klassisches HUD sein - in diesem Fall nutzt man ganz einfach den Modus "Screen Space - Overlay" und sortiert verschiedene Canvasse über den Sorting Wert.

  • Benutze ich Z als Achse für Sortierung von Transparenz?

In diesem Fall würde ich keinen "Screen Space - Camera" Modus verwenden. Diese Art von Canvas sortiert sich immer nach Abstand zur Kamera auf Z - ihr X- und Y-Wert ist immer 0. Entsprechend könnten Weltobjekte nun ungewollt vor der UI erscheinen und es wird unmöglich die Sortierung im Scene View nachzuvollziehen.

  • Soll UI dynamisch Spielobjekten folgen?

Canvas mit "World Space" ist dein Freund. Sortiert werden kann das Canvas dann über Abstand zur Kamera oder mittels Sortieroptionen.

  • Was ist wenn UI hinter Spielobjekten auftauchen soll?

Angenommen wir haben ein 2D-Spiel mit Seitenansicht. Es wäre denkbar, dass z.B. Texte zwischen Spielebene und Hintergrund auftauchen sollen.

[Beispiel]

Für diesen Effekt sollten wir "Screen Space - Camera" oder "World Space" verwenden. Mit beiden Optionen können wir entsprechend unserer Szene die UI entweder über Tiefe oder Sortieroptionen einsortieren. Wenn sich diese UI dem Bildschirm oder der Kamera anpassen soll ist "Screen Space - Camera" zu verwenden.

Ein solches Setup erlaubt es auch Partikelsysteme in der Welt vor bzw. hinter der UI zu rendern. Warum das sehr praktisch ist steht im Kapitel über Partikel.

Wenn UI gegen Welt sortiert wird stellt sich dann die Frage nach der Sortierung:

  • Benutze ich Sortieroptionen oder Distanz zur Kamera bzw. Transparenzachse Z?

Meiner Meinung nach schließen sich diese beiden Optionen ein wenig aus und bieten unterschiedliche Vor- bzw. Nachteile. Das liegt vor allem an der zuvor beschriebenen einfachen Hierarchie - Sortieroptionen werden immer vor Z angewendet. Letztendlich muss die Wahl hier vor allem anderen mit der Sortierung der Spielszenen funktionieren.

Ich halte folgende Kombinationen für sinnvoll:

  • Alles sortiert sich nach Tiefe,
  • Alles sortiert sich mit den Sortieroptionen,
  • Global werden Sortieroptionen verwendet, aber UI sortiert sich intern nach Z.

Eine Sortierung nach Tiefe bietet vor allem den "What you see is what you get" - Vorteil. Wenn wir im Scene View auf 3D schalten, können wir unser Layering ganz bequem überblicken.

[Beispiel]

Die verschiedenen Ebenen der Spielszene könnten mit einer Sorting Group versehen werden, damit man diese intern über Sortieroptionen sortieren kann.

[Beispiel]

Nachteil ist natürlich, dass sie nicht funktioniert, wenn Z nicht die Transparenzachse ist - also für Top-Down eher nicht zu gebrauchen.

Wenn die Transparenzachse oder die Szenenhierarchie eine Sortierung per Tiefe nicht zulässt, eignen sich die Sortieroptionen am besten, auch wenn sie die Sortierung unübersichtlicher machen.

Ein Mittelweg kann es sein allein die Sortierung der UI über Z zu regeln und für die Welt Sortieroptionen zu nutzen. Dafür würde man alle Canvasses auf ein Vordergrundlayer verschieben, das über dem Weltlayer liegt. So behält man sich den Überblick der Tiefensortierung bei. 

[Beispiel]

Device Simulator

[Beispiel - wie schaltet man ihn an?]

Mit dem Device Simulator kann man sehen, wie sich unter anderem die UI auf verschiedenen Geräten verhält. Es ist auch möglich das Gerät zu drehen und die Safe Zone anzeigen zu lassen.

[Beispiel]

Man sollte stets verschiedenste gängige Geräte testen - mindestens Android bzw. iOS jeweils als Phone und Tablet. Insbesondere bei der Safe Zone unterscheiden sich die Geräte sehr:

[Beispiel]

Apple macht die Safe Zone gerne symmetrisch und hat außerdem eine vertikale Safe Zone für den Home-Button.

[Beispiel]

Android ist hingegen oft asymmetrisch.

[Beispiel]

Tablets haben oft genug gar keine Safe Zone.

Um all diese Dinge sehen zu können und entsprechend mit Code oder Design zu reagieren, ist der Device Simulator unabdingbar.

Partikel in der UI

...

Partikeleffekte für die UI sind von Unity nicht von Haus aus unterstützt. Man kann zwar ein herkömmliches Partikelsystem an Childs eines Canvas hängen - das führt dann zu dieser komisch wirkenden Mischung von Transform und RectTransform - aber das Sortieren funktioniert nur mäßig:

[Beispiele]

Ein weiterer Grund warum Partikel besser nicht in der UI sein sollten ist Performance. Partikelsysteme sind konstant in Bewegung und würden das Canvas immer wieder neu zeichnen lassen (mehr dazu im Kapitel Performance).

Besser ist es Partikelsysteme in der Welt zu belassen. Dann kann man sie auch einfacher gegen die UI sortieren, in dem man den richtigen Canvas Render Mode wählt (siehe Layering, Canvas).

[Beispiel]

Wenn man seine Partikel dennoch im Canvas haben möchte, um sich beispielsweise das Layouting zu erleichtern, kann man auf ein Rendertextur Setup zurückgreifen.

[Beispiel]

Hier werden die Partikel von einer separaten Kamera gerendert, deren Bild dann auf der Rendertextur angezeigt wird.

Zuletzt noch ein sehr gutes Package, was neben dem Rendertextur Ansatz auch noch andere Lösungen bietet: hier.

UI Design Tools

Tools für UI- oder UX-Design, je nach dem wie genau man mit dem Namen ist, helfen massiv bei der Arbeit an UI. Von Wireframes bis hin zu Click-Prototypen kann man damit alles erstellen und das meistens sogar kollaborativ. Entwickler können später als Betrachter hinzugefügt werden, damit sie alle Layouts, Abstände und Größen genauestens ins Spiel bringen können.

...