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

Renegade123

Alter Hase

  • »Renegade123« ist der Autor dieses Themas

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

1

14.12.2013, 02:08

[Unity / C#] GetInterface<T> von GameObject

N'abend,

ich bin vor kurzem auf das Problem gestoßen, dass es in Unity nicht möglich ist, die generische Methode GetComponent mit Schnittstellen als Argument zu verwenden. Kurzerhand hab ich mir selbst zwei Methoden geschrieben, weil ich zu faul bin jedes Mal zu casten.
Jetzt habe ich gelesen, dass die Methode GetComponent mit dem Type Parameter sehr langsam auf mobilen Geräten sei.

Gibt es vielleicht andere / performantere Lösungen?

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class GoodBehaviour : MonoBehaviour {
    protected T GetInterface<T>(GameObject from) where T : class {
        if (typeof(T).IsInterface) {
            foreach (Component c in from.GetComponents(typeof(T))) {
                return c as T;
            }
            throw new System.InvalidOperationException("Can't find any interface on: " + gameObject.name);
        } else {
            throw new System.ArgumentException(typeof(T).Name + " is no valid interface");
        }
    }
    protected T[] GetInterfaces<T>(GameObject from) where T : class {
        if (typeof(T).IsInterface) {
            List<T> temp = new List<T>();
            foreach (Component c in from.GetComponents(typeof(T))) {
                temp.Add(c as T);
            }
            if (temp.Count > 0) {
                return temp.ToArray();
            } else {
                throw new System.InvalidOperationException("Can't find any interface on: " + gameObject.name);
            }
        } else {
            throw new System.ArgumentException(typeof(T).Name + " is no valid interface");
        }
    }
}


Desweiteren stört mich, dass ich den Methoden das GameObject als Argument übergeben muss.
Gibt es eine Möglichkeit meine Methoden direkt in GameObject zu definieren, damit ich sie wie jede andere GetComponent benutzen kann?

So:

C#-Quelltext

1
2
3
foreach (Listener l in gameObject.GetInterfaces<Listener>()) {
    l.Listen();
}

Anstatt:

C#-Quelltext

1
2
3
foreach (Listener l in GetInterfaces<Listener>(gameObject)) {
    l.Listen();
}
Liebe Grüße,
René

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

2

14.12.2013, 12:21

Ich habe das gleiche, was du geschrieben hast, nochmal implementiert (wenn auch noch nicht ausreichend getestet), was fast das gleiche machen dürfte:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class GameObjectExtensions {
    public static T GetTypedComponent<T>(this GameObject gameObject) where T : class {
        return gameObject.GetComponent(typeof(T)) as T;
    }

    public static T[] GetTypedComponents<T>(this GameObject gameObject) where T : class {
        Component[] components = gameObject.gameObject.GetComponents(typeof(T));
        T[] typedComponents = new T[components.Length];
        for(int i = 0; i < components.Length; i++) {
            typedComponents[i] = components[i] as T;
        }
        return typedComponents;
    }
}

Die Änderungen:
Ich verwende Erweiterungsmethoden (erkennbar am ersten Parameter, vor dem ein "this" steht). Dadurch kann man auf jedem GameObject diese neuen Methoden aufrufen. (Interessant finde ich das in Kombination mit Enumerationen, da man so einer Enumeration Methoden anhängen kann, um sich wiederholende switch-Blöcke zu vermeiden.) Und da man die Methoden direkt auf einem GameObject anwenden kann, kann man sie auch auf solchen anwenden, die nicht vom eigenen Typen abgeleitet sind. ;)
Und ich werfe nicht so viele Fehler. Über die Exception bei einer Klasse statt eines Interfaces kann man sich ja noch streiten (ich würde es nicht als Ausnahmefehler ansehen, sondern höchstens eine Warnung ausgeben), aber alle anderen Esceptions sind gänzlich unnötig. Wenn man eine Komponente eines Typs haben möchte und diese nicht da ist, sollte stattdessen null zurückgegeben werden (macht Unity auch so) und wenn man eine Menge von Komponenten eines bestimmten Typs abfragt, wovon aber keine einzige angehangen wurde, dann sollte man einfach eine leere Liste zurückgeben (macht Unity ebenfalls so und ist auch das einzig sinnvolle in diesem Zusammenhang).

Die Namen der Methoden sind durchaus verbesserungswürdig, allerdings dürfen diese ja nicht mit den bereits vorhandenen in Konflikt geraten... =/

Ob es eine bessere Performance abliefert, kann ich nicht sagen, der Unterschied sollte aber eigentlich zu geringfügig sein.
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

Renegade123

Alter Hase

  • »Renegade123« ist der Autor dieses Themas

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

3

14.12.2013, 17:21

Hey Sacaldur,

danke für die coole Idee mit den Erweiterungsmethoden, genau sowas habe ich gesucht!
Mit den Ausnahmen hast du wohl recht...

Schade, dass die Einschränkung des Typparameters nicht Teil der Methoden Signatur ist. Scheinbar ist es dadurch nicht möglich die GetComponent zu überladen, oder sehe ich das falsch?

btw: Bist du beim GGJ14 dabei?
Liebe Grüße,
René

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

4

14.12.2013, 18:40

Die zweite Methode lässt sich sogar noch um einiges vereinfachen:

C#-Quelltext

1
2
3
4
public static T[] GetTypedComponents<T>(this GameObject gameObject) where T : class 
{
    return gameObject.GetComponents(typeof(T)).OfType(typeof(T)).ToArray();
}

Renegade123

Alter Hase

  • »Renegade123« ist der Autor dieses Themas

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

5

14.12.2013, 18:48

Die zweite Methode lässt sich sogar noch um einiges vereinfachen:

C#-Quelltext

1
2
3
4
public static T[] GetTypedComponents<T>(this GameObject gameObject) where T : class 
{
    return gameObject.GetComponents(typeof(T)).OfType(typeof(T)).ToArray();
}


Sehr cool, aber bei mir geht es nur mit den Generischen:

C#-Quelltext

1
2
3
public static T[] GetInterfaces<T>(this GameObject gameObject) where T : class {
        return gameObject.GetComponents(typeof(T)).OfType<T>().ToArray<T>();
    }
Liebe Grüße,
René

Renegade123

Alter Hase

  • »Renegade123« ist der Autor dieses Themas

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

6

14.12.2013, 20:03

Gibt es die Möglichkeit, dass so abzuändern damit auch die Kurzfassung wie bei den anderen GetComponents funktioniert ?

Also:

C#-Quelltext

1
_listener = GetInterface<Listener>();


Anstatt:

C#-Quelltext

1
_listener = gameObject.GetInterface<Listener>();


Mir fällt nur der Schritt über eine abstrakte Klasse ein, aber das möchte ich nur zu gerne vermeiden.
Vielleicht als Default-Parameter?
Liebe Grüße,
René

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Renegade123« (14.12.2013, 20:08)


Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

7

14.12.2013, 20:11

Der Code, den du dir wünschst, befindet sich innerhalb einer Komponente (MonoBehaviour, um genau zu sein). Da die vorhandenen GetComponent-Methoden scheinbar in GameObject und Component unabhängig voneinander implementiert wurden, müsstest du die Erweiterungsmethoden nochmal für Component schreiben (oder wahlweise auch MonoBehaviour, allerdings dürfte es an Component sinnvoller sein, da man dann bspw. auch für eine Camera diesen Code ausführen kann).
Also public static T GetTypedComponent<T>(this Component component) { [...]

@TrommlBomml:
Danke übrigens für die Ergänzung. ;)
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

Renegade123

Alter Hase

  • »Renegade123« ist der Autor dieses Themas

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

8

14.12.2013, 20:20

Das ändert leider nicht all zu viel, denn ich muss dann immer noch this verwenden:

C#-Quelltext

1
2
3
_listener = this.GetInterface<Listener>();
//'GetInterface' does not exist in the current context:
_listener = GetInterface<Listener>();


Und mit this kann ich keinen statischen Default-Parameter initialisieren :(
Liebe Grüße,
René

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

9

14.12.2013, 20:32

Ok, das hätte ich dann durchaus vorher testen sollen...
(Allerdings muss ich auch zugeben, dass für mich das bereits ausreichend wäre, da ich grundsätzlich immer this mit hinschreibe...)
An der Stelle fällt mir keine wirklich elegante Lösung ein, außer evtl. für die eigenen Skripte eine weitere Methode zu implementieren (bspw. getInterface<T>()), die dann mittels this. den richtigen Aufruf durchführt. Aufrufe für das eigene GameObject dürften dann ohne this möglich sein und Aufrufe auf anderen Komponenten (bzw. GameObjects) brauchen ohnehin eine Objektreferenz.
Und damit das nicht für jedes MonoBehaviour erneut implementiert werden muss, könnte eine entsprechende Basisklasse geschrieben werden, wie du bereits am Anfang hattest (angereichert mit den neuen Erkenntnissen ;) ).
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

Renegade123

Alter Hase

  • »Renegade123« ist der Autor dieses Themas

Beiträge: 494

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

10

14.12.2013, 20:53

Mäh. Keine Basisklasse. Hab mich gefreut, dass die raus ist durch die Erweiterungsmethoden. :hmm:
Liebe Grüße,
René

Werbeanzeige