Jump to content
Unity Insider Forum
  • Einträge
    9
  • Kommentare
    52
  • Aufrufe
    34.835

Skriptkommunikation in Unity


Sascha

1.629 Aufrufe

Immer, wenn Unity Tech ein neues Tutorial raus bringt, habe ich das Bedürfnis, es anzugucken... um festzustellen, ob Käse erzählt wird. In der Vergangenheit hat man z.B. in der Scripting Reference immer wieder Beispiele gesehen, die einem einen Schauer über den Rücken gejagt haben.
Du brauchst eine Referenz auf ein Objekt? GameObject.Find. Oder FindWithTag. Autsch.
Warum, habe ich schon öfter erklären müssen, und die Hemmschwelle, das zu ignorieren, was von offizieller Seite kommt, ist oft hoch.

Heute bin ich auf ein neues Videotutorial der offiziellen Learn-Serie gestoßen: [url="http://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/communicating-between-components-gameobjects"]http://unity3d.com/l...nts-gameobjects[/url]

Da ist eine Menge wachgerüttelt worden, schon bevor ich darauf klickte. Kommunikation zwischen Komponenten ist ein sehr zentrales Thema in Unity, und so oft man es braucht, so oft wird es auch missverstanden oder suboptimal bis falsch vermittelt.

Also Augen [s]zu[/s] auf und durch.
Und was sehe ich? FindGameObjectsWithTag. Juuhuuu. In meinem Kopf setzen sich Formulierungen für einen neuen Blogeintrag zusammen.
[img]http://board.bytezero.de/let-me-tell-you-why-thats.jpg[/img]

Aber vielleicht, so dachte ich, lehne ich mich heute einfach mal nicht so weit aus dem Fenster, bevor ich ein paar Messungen gemacht habe. Dinge über Strings zu finden klingt auf den zweiten Blick und mit Hashtables im Hinterkopf gar nicht mal soo abwegig.

Über GameObject.Find und FindWithTag schreibe ich heute einmal nicht. Diese Funktionen fallen für mich völlig aus der Wertung, weil sie schon in der theoretischen Anwendung bescheuert sind.

Heute geht es um den Hauptteil des besagten Tutorials, der sich mit folgendem beschäftigt:
[size=5]Wie finde ich alle Komponenten vom Typ T in meiner Szene?[/size]

Dazu habe ich mir eine Szene mit 4000 gleichartigen GameObjects erstellt. Diese haben den gleichen Tag und außerdem alle ein Script vom Typ "Something".

Ich möchte in Start() oder Update() Referenzen auf alle diese GameObjects bzw. Komponenten vom Typ Something haben. Herangehensweisen dafür:[list=1]
[*]FindGameObjectsWithTag
[*]FindGameObjectsWithTag mit GetComponent<T> (um die Komponente direkt zu referenzieren)
[*]In Something.Awake eine List<T> befüllen
[*]FindObjectsOfType<T>
[*]FindObjectsOfType(typeof(T)) as T[]
[/list]
Da wir hier Variablen initialisieren, gehört der jeweilige Code im Zweifelsfall klassischerweise in Awake(). Allerdings gibt es erstaunliche Ergebnisse, wenn man ihn in Start() ausführt, aber lest selbst...

Die Werte, die gleich folgen, sind "Ticks". Die Messergebnisse waren auch bei 4000 Objekten (mehr wurde im Editor anstrengend) zu klein für Millisekunden-Angaben.
[b]In Klammern dahinter sind die Ergebnisse für das Gleiche mit nur 3 GameObjects.[/b]


[size=5]1. FindGameObjectsWithTag[/size]
Ich bin kein großer Fan von Tags, [s]da sie dazu verleiten, jegliche Identifikation darüber laufen zu lassen und sich dann zu wundern, wie man 33 verschiedene Arten von Objekten unterscheiden soll.[/s] Inzwischen gibt es offenbar beliebig viele Tags. Trotzdem sind Tags aus verschiedenen Gründen nicht ganz so toll. Wenn es dich interessiert, frag mich gerne :)

Für wirklich häufig vorkommende Objektsorten wie eben Player, Respawn oder Finish sind Tags aber zumindest von der Idee her vertretbar. Wie sieht es mit der Performance aus?

Der Code:
[CODE]
var sw = new Stopwatch();
sw.Start();
var gos = GameObject.FindGameObjectsWithTag("Respawn");
sw.Stop();
print(sw.Elapsed);
[/CODE]
Das Ergebnis:
[b]In Awake: 10533 (1360)[/b]
[b]In Start: 4065 (1703)[/b]

Zwischenfazit: Offenbar legt Unity zwischen Awake und Start Strukturen an, die FindGameObjectsWithTag bei vielen Objekten beschleunigen. Verrückt!


[size=5]2. FindGameObjectsWithTag mit GetComponent<T>[/size]
Wir wollen in den meisten Fällen direkt die Referenzen auf die Komponenten haben, da wir ja mit denen arbeiten und nicht mit dem GameObject. Diese sollten wir jetzt besorgen und speichern, sonst heißt es im Zweifelsfall in Update GetComponent<T>, und das muss ja nicht sein ;)

Der Code:
[CODE]
var sw = new Stopwatch();
sw.Start();
var gos = GameObject.FindGameObjectsWithTag("Respawn");
var things = new Something[gos.Length];
for(int i = 0; i < gos.Length; ++i)
{
things[i] = gos[i].GetComponent<Something>();
}
sw.Stop();
print(sw.Elapsed);
[/CODE]
Das Ergebnis:
[b]In Awake: 73343 (3540)[/b]
[b]In Start: 69558 (3050)[/b]

Zwischenfazit: Mit GetComponent<T> legt diese Variante ordentlich zu. Je mehr Objekte man hat, desto öfter wird GetComponent<T> aufgerufen und das dauert.
Getestet habe ich auch mit LINQ. Das dauert länger, insbesondere bei wenigen Objekten (10849 in Awake für 3 GOs!), da ein Umwandeln durch ToList() und, will man am Ende wieder ein Array haben, danach ein Zurückwandeln mit ToArray() nötig ist. Und das kostet konstant eine Menge Zeit.


[size=5]3. Eine List<T> in T.Awake befüllen[/size]
So habe ich das bisher immer gemacht. Ich halte es für elegant, dass die Menge aller Komponenten T auch in der Klasse T angelegt und verwaltet wird. Aber wie sieht's mit der Performance aus?

Der Code:
[CODE]
private static Stopwatch sw = new Stopwatch();
public static List<Something> all = new List<Something>();

void Awake()
{
if(all.Count == 0) sw.Start();
all.Add(this);
if(all.Count == 4000)
{
Finder.sw.Stop();
print(sw.Elapsed);
}
}
[/CODE]
Das Ergebnis:
[b]29928 (975)[/b]

Zwischenfazit: Diese Variante ist offenbar durchaus schneller. Zumindest, wenn man Anspruch darauf hat, die Referenzen auf die Komponente zu haben, und nicht auf die GameObjects.
Bei wenigen GameObjects allerdings ist diese Methode soweit die beste.

[size=5]4. FindObjectsOfType<T>[/size]
Auf diese Idee bin ich erst durch das Tutorial gekommen. Einfach mal ausprobieren!

Der Code:
[CODE]
var sw = new Stopwatch();
sw.Start();
things = FindObjectsOfType<Something>();
sw.Stop();
print(sw.Elapsed);
[/CODE]
Das Ergebnis:
[b]In Awake: 49376 (5184)[/b]
[b]In Start: 62903 (4657)[/b]

Zwischenfazit: Gar nicht mal schlecht! Diese Variante ist etwas langsamer als Variante 3, besonders bei wenigen GameObjects. Sie hat allerdings noch einen dicken Haken, aber auf den gehe ich im nächsten Zwischenfazit ein.

[size=5]5. FindObjectsOfType(typeof(T)) as T[][/size]
Das Tutorial benutzt diese Variante der Methode... ich hätte zwar gedacht, dass die generische Variante schneller ist, aber trotzdem habe ich es einfach mal ausprobiert.

Der Code:
[CODE]
var sw = new Stopwatch();
sw.Start();
things = FindObjectsOfType(typeof(Something)) as Something[];
sw.Stop();
print(sw.Elapsed);
[/CODE]
Das Ergebnis:
[b]In Awake: 31080 (2536)[/b]
[b]In Start: 28986 (2226)[/b]

Zwischenfazit: Verrückt, diese Variante ist tatsächlich schneller als die generische.
Aber zu dem erwähnten Problem: FindObjectsOfType kommt für mich als Lösung nicht wirklich in Frage, da man es dort benutzen muss, wo man die Objekte braucht, nicht dort, wo die Objekte sind.
Braucht man die Liste aller Komponenten also an mehreren Stellen, muss man FindObjectsOfType auch mehrere Male aufrufen. Einzige Alternative ist ein "T-Manager", der alle Komponenten vom Typ T verwaltet und für andere zur Verfürgung stelle. Und das ist alles andere als lose Kopplung. Bäh.

[size=5]Endfazit[/size]
Die ganzen Messergebnisse sind hier so klein, dass sie kaum wichtig erscheinen. Und ja, auf einem guten Computer ist FindGameObjectsWithTag vielleicht gar nicht mal soo problematisch.
Jedoch soll vielleicht auch das Spiel auf Android exportiert werden, und die Zielgruppe dort ist nicht bekannt dafür, besonders geduldig zu sein ;)
Außerdem gilt nach wie vor meine Kritik an Tags und, wer aufmerksam gelesen hat und sich ein wenig auskennt, mag bemerkt haben, dass meine Kritik an FindObjectsOfType auch für FindGameObjectsWithTag gelten muss: Haben mehrere Scripts Interesse an z.B. allen Spawns, wird's haarig.
Der einzige saubere Weg ist imo der, die Menge aller Komponenten T in der Klasse T selbst aufzubewahren. Und das geht mit Variante 3.

Ein schöner Umstand, dass diese Vorgehensweise auch gleichzeitig die performanteste zu sein scheint.

4 Kommentare


Recommended Comments

Was mich überrascht: in FindGameObjectsWithTag ist der Unterschied von drei Objekten zu 4000 sehr gering.

Vielen Dank für die interessanten Einsichten. Das mit der Liste in der Klasse leuchtet ein, kostet einmal am Anfang Zeit, dann nicht mehr.
Link zu diesem Kommentar
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...