Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Garzec

Alter Hase

  • »Garzec« ist der Autor dieses Themas

Beiträge: 693

Wohnort: Gießen

  • Private Nachricht senden

1

14.06.2017, 22:01

Sauberkeit mit Unity / Performance Optimierung

Guten Abend,
ich denke, die Nutzer von Unity werden es wahrscheinlich kennen,

C#-Quelltext

1
2
3
4
5
private void OnTriggerEnter(Collider col)
    {
        if (col.tag == "Player")
            // irgendwas
    }


und

C#-Quelltext

1
2
3
4
5
    private void OnCollisionEnter(Collision col)
    {
        if (col.gameObject.tag == "Player")
            // irgendwas
    }


sind bei Kollisionen logischerweise Pflicht. Nun habe ich das Ganze mehrfach geschrieben und möchte die Methode auslagern. Jetzt habe ich eine Frage, was besser ist:

Prüfe ich auf die strings also die tags der Objekte oder macht es mehr Sinn zu schreiben

C#-Quelltext

1
if(col.gameObject == dasZuPruefendeGameObject)


Lagere ich die Methode aber in eine abstrakte Basis aus, kennt diese natürlich nicht das zu prüfende Objekt. Folglich würde ich das Gameobject als Parameter mitgeben.

Nun wieder zur Performanz, strings bzw. Tags abgleichen und glücklich sein oder die gameobjects abgleichen aber als 2. Parameter das Gameobject mitgeben müssen?

Also klar, ich spare dadurch keinen Schreibaufwand ein, weil es ja schon ein Einzeiler ist, ich würde halt nur Codewiederholungen vermeiden.

Die ausgelagerte Methode wäre ja dann in Kurzform:

C#-Quelltext

1
2
3
4
    protected bool CheckCollision(Collider col, GameObject go)
    {
        return col == go;
    }

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »Garzec« (14.06.2017, 22:43)


Renegade

Alter Hase

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

2

15.06.2017, 13:52

Hallo Garzec,
ich bin persönlich der Meinung das die Tags in Unity fürchterlich sind. Eine kleine Umbenennung durch den Designer o.ä. und schon funktionieren Skripte nicht mehr, denn eine Umbennenung im Editor hat nicht die Ersetzung der Strings im Code zur Folge. Überhaupt ist es eine wirklich schlechte Umsetzung, wenn sowohl die Designer im Editor ein Array von Strings verwalten müssen und die Programmierer im Code auch. (Stichwort Wartbarkeit und Redundanz) Viel sinnvoller ist es, stattdessen auf spezifische Komponenten zu prüfen, denn wenn du mittels IDE eine Klasse umbenennst kannst du das sehr einfach im gesamten Projekt durchführen. (In VS15 ist der Shortcut dafür Str+R, R - Umgestaltung durch Umbenennen). Zumal man häufig sowieso auf das zu prüfende Objekt in irgend einer Weise zugreifen möchte und die GetComponent ohnehin aufruft.
Hier ein Beispiel mittels Component:

C#-Quelltext

1
2
3
4
5
6
private void OnTriggerEnter(Collider collider)
{
    Player player = collider.GetComponent<Player>();
    if (player)
    //irgendwas
}

Da ich es desweiteren stets leid bin, sowas neu zu schreiben, habe ich mir vor einer ganzen Weile ein kleines Skript geschrieben, welches für mich jede Art von Triggering komplett löst. Die Implementation kannst du dir aus der Anwendung gerne selbst überlegen - kleiner Tipp: C# Generics und C# Delegate sind die Schlagworte. So sparst du dir auch jede Art von abstrakter Basisklasse, denn die Faustregel gilt auch hier: Komposition > Vererbung.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public sealed class Foo {

    readonly Trigger _trigger;
    readonly TriggerEvent<Player> _hitPlayer;

    public Foo(Trigger trigger) {
        _trigger = trigger;
        _hitPlayer = _trigger.Add<Player>(Triggered, TriggerEventType.Enter);
    }

    void Triggered(Player player) {
    //Player is always available, 'cause Trigger is already checking for null. So this will only be called if a real player is triggered
    //do some stuff
    //for example: unregister, 'cause this is a one timer and we don't want to get called again
    _trigger.Remove(_hitPlayer);
    }
}
Liebe Grüße,
René

Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von »Renegade« (15.06.2017, 15:27)


KeksX

Community-Fossil

Beiträge: 2 107

Beruf: Game Designer

  • Private Nachricht senden

3

15.06.2017, 14:39

Was ich auch mal benutzt habe, weil der Designer darauf bestand, die Tags zu verwenden:

Wir haben uns ein kleines Vokabular an kurzen Tags gebaut und dann einfach nur mit "Contains()" geprüft. Das ging bei uns glatt, weil es nie mehr als 100 Objekte gleichzeitig waren, die so geprüft wurden. Könnte ggf. also ein Kompromis sein.
WIP Website: kevinheese.de

Renegade

Alter Hase

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

4

16.06.2017, 00:00

Was ich auch mal benutzt habe, weil der Designer darauf bestand, die Tags zu verwenden:

Wir haben uns ein kleines Vokabular an kurzen Tags gebaut und dann einfach nur mit "Contains()" geprüft. Das ging bei uns glatt, weil es nie mehr als 100 Objekte gleichzeitig waren, die so geprüft wurden. Könnte ggf. also ein Kompromis sein.

Das ist mit Sicherheit eine Lösung und auch in den Tutorials von Unity werden Tags häufig genutzt - per se also nicht verkehrt. Meine Erfahrung hat aber gezeigt, dass die Verständigung mit dem Designer nicht notwendig ist, da es für ihn schlicht irrelevant ist und eine mögliche Fehlerquelle birgt - insbesondere durch die Redundanz. Die Identifizierung von Objekten zur Laufzeit sollte stets nur beim Programmierer liegen und absolut nichts mit dem Designer zu tun haben. (Überhaupt, warum hat der Designer das letzte Wort bei technischen Fragen?) Es ist ja auch völlig egal für ihn wie das Objekt heißt. Was passiert zum Beispiel im Fall (in Anbetracht zur überlegten Lösung der abstrakten Basisklasse), wenn man auf alle Einheiten prüfen möchte, die eine Lebenskomponente haben und diesen Schaden zuzufügen möchte, aber nur bei jenen die gegen Feuerschaden nicht resistent sind und keine Spieler sind? (Ich denke du verstehst auf was ich hinaus will) Damit kannst du dir kein Konstrukt über Tags bauen. Eine Bitmaske für solche Überprüfungen bietet Unity nicht und die Collisionmatrix (Layersystem) ist meiner Meinung nach nur zur nachträglichen Optimierung. (Sollte man nämlich etwas nachträglich erweitern, zum Beispiel um einen neuen Layer, müsste man diesen ggf. bei allen GameObjects in der Szene "anklicken" die ihn benötigen, oder ein extra Editorskript für diese Aufgabe bauen - unnötige, nervige Arbeit) Sobald man einmal mit Tags anfängt hat man zwar einen tollen Status Quo der sehr vernünftig wirkt, aber am Ende versperrt man sich entweder die Erweiterung oder muss umarbeiten. Und der Performanceunterschied in der Praxis ist zu vernachlässigen, da es lediglich kritisch wird, wenn man GameObjects mit sehr vielen Komponenten erstellt und dann macht man, meiner Meinung nach, sowieso etwas falsch.
Liebe Grüße,
René

Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von »Renegade« (16.06.2017, 00:08)


KeksX

Community-Fossil

Beiträge: 2 107

Beruf: Game Designer

  • Private Nachricht senden

5

16.06.2017, 00:09

Ja, letztendlich musste ich so oder so mein eigenes System bauen für den ganzen Rest. Aber zumindest zur Unterscheidung einfacher Eigenschaften, die sich zur Laufzeit nicht ändern, war es in Ordnung.

Wieso der Designer da mitzureden hatte: Er musste die Objekte am Ende dann entsprechend im Level platzieren, und bestand aus irgendeinem Grund darauf, das Objekt über Tags beschreiben zu können. War nicht der angenehmste Kerl. :D
WIP Website: kevinheese.de

Renegade

Alter Hase

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

6

16.06.2017, 00:18

Ja, letztendlich musste ich so oder so mein eigenes System bauen für den ganzen Rest. Aber zumindest zur Unterscheidung einfacher Eigenschaften, die sich zur Laufzeit nicht ändern, war es in Ordnung.

Wieso der Designer da mitzureden hatte: Er musste die Objekte am Ende dann entsprechend im Level platzieren, und bestand aus irgendeinem Grund darauf, das Objekt über Tags beschreiben zu können. War nicht der angenehmste Kerl. :D


Genau da steckt der Teufel im Detail. "einfache Eigenschaften" werden über Tags ausgewertet, aber Andere durch ein separates System. Was macht Daten denn "einfach", oder "nicht einfach"? Mein Standpunkt ist die Vereinheitlichung, denn Unterscheidungen, bereits auf diesem Level, führen früher oder später zu undursichtigen Code. Bedingte Anweisungen erhöhen stets die Komplexität einer Software und dort gilt es allemal gewissenhaft und wenn möglich einheitlich vorzugehen. Vielleicht bin ich etwas pedantisch, aber ich habe solche Probleme schon häufig gesehen und die Unity-Community benötigt unbedingt mehr Professionalität und weniger Larifari Lösungen wie das Tag-System.
Liebe Grüße,
René

KeksX

Community-Fossil

Beiträge: 2 107

Beruf: Game Designer

  • Private Nachricht senden

7

16.06.2017, 03:18

Naja, Unity gibts halt vor. Und die wenigsten werden die Zeit reinstecken, es selbstständig besser zu machen. Gibt dafür recht wenig Lernressourcen, die man nutzen könnte.
WIP Website: kevinheese.de

Garzec

Alter Hase

  • »Garzec« ist der Autor dieses Themas

Beiträge: 693

Wohnort: Gießen

  • Private Nachricht senden

8

16.06.2017, 06:59

@Renegade die Tags finde ich auch nicht so toll. Aus diesem Grund prüfe ich auch nicht mehr auf Tags sondern auf das GameObject. Aktuell prüfe ich wenn überhaupt nur auf den Spieler und den habe ich in einer Basis abgelegt.

Deshalb habe ich in der Basis dann

C#-Quelltext

1
2
3
4
5
6
7
8
9
protected bool CheckCollision(Collision col, GameObject go)
    {
        return col.gameObject == go;
    }

    protected bool CheckCollision(Collider col, GameObject go)
    {
        return col.gameObject == go;
    }


und bei Nutzung sieht es wie folgt aus:

C#-Quelltext

1
2
3
4
5
 private void OnTriggerEnter(Collider col)
    {
        if (CheckCollision(col, playerObject))
            // ...
    }


das ist für mich eigentlich ausreichend.

Werbeanzeige