Articles

GetComponent in C#

In diesem Tutorial

  • Zugriff auf ein anderes Skript aus dem Objekt heraus
  • Wie funktioniert GetComponent?
  • Abrufen vieler Komponenten
  • Interaktion zwischen Objekten.
  • Interaktion zwischen Objekten innerhalb der Hierarchie
  • SendMessage und BroadcastMessage
  • Interaktive Funktionen

Einführung

Ein wiederkehrendes Problem beim Start von Unity ist der Zugriff auf die Elemente in einem Skript von einem anderen Skript aus. Viele würden denken, dass die Dereferenzierung vom Skriptnamen ausreichen würde, sie erkennen schnell, dass dies nicht der Fall ist.

Bei der Entwicklung eines Programms werden Variablen an verschiedenen Stellen im Speicher gespeichert. Wenn ein Objekt andere Objektelemente sehen könnte, bestünde das Risiko, sie zu ändern, obwohl dies nicht beabsichtigt war. Wenn zum Beispiel zwei Objekte dasselbe Skript mit denselben Variablennamen enthalten, kann der Compiler nicht herausfinden, auf welches wir uns beziehen.

Um dieses Problem zu vermeiden, kann jedes Objekt im Speicher keine anderen Objekte sehen. Es ist dann notwendig, einem Objekt mitzuteilen, wo sich die benötigte Variable im Speicher befindet. Das ganze Zeigerprinzip wurde vor langer Zeit fallen gelassen und jetzt verwenden wir stattdessen Referenz (die tatsächlich Zeiger intern verwendet).

Es gibt verschiedene Möglichkeiten, auf Variablen zuzugreifen, einige sind besser als andere und einige sind einfach in bestimmten Situationen zu verwenden.

  1. GetComponent, die häufigste, auch diejenige, die zuerst am meisten verwirrt
  2. SendMessage, es mag einfacher aussehen zu verstehen, aber es ist auch weniger effizient.
  3. Statische Variablen, die am einfachsten, aber auch am kompliziertesten zu verstehen sind.

Einrichten einer Szene

Erstellen Sie eine neue Szene, fügen Sie leere Spielobjekte hinzu und nennen Sie sie „StormTrooper“. Erstellen Sie zwei Skripte mit den Namen „Health“ und „Force“. Fügen Sie beide Skripte zu StormTrooper hinzu.

Öffnen Sie beide Skripte im Editor und fügen Sie diese Zeilen hinzu:

Health.cs

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private int health = 5; private bool hasForce = false; private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { this.hasForce = Force.ReportForce(); if(hasForce == true) { this.health = 10; } } }}

Erzwingen.cs

using UnityEngine;using System.Collections;public class Force : MonoBehaviour{ private bool force = false; private void Start() { this.force = true; } private void Update() { if (Input.GetKeyDown(KeyCode.P)) { string str = (this.force == true) ? " the": " no"); Debug.Log("I have" + str + " force"); } } public bool ReportForce() { return force; }}

Wenn Sie diesen Code ausprobieren, wird einfach der Fehler „Für den Zugriff auf nicht statische Elemente ist eine Objektreferenz erforderlich“ zurückgegeben.

Wir müssen der Gesundheitskomponente des StormTrooper mitteilen, wo sich die Kraftkomponente befindet, damit sie auf ihre Mitglieder zugreifen kann. Da sich unsere Skripte beide auf demselben Objekt befinden, müssen wir lediglich eine Variable vom Typ des Skripts deklarieren, auf das wir zugreifen möchten, und GetComponent , um das Skript zu finden.

Zugriff auf ein anderes Skript innerhalb des Objekts

Ändern Sie die Gesundheit mit:

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private Force forceScript= null; public int health = 5; private bool hasForce = false; private void Start() { this.forceScript= GetComponent<Force>(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { this.hasForce = this.forceScript.ReportForce(); if(this.hasForce == true) { this.health = 10; } } }}

Führen Sie mit den Änderungen das Spiel aus, drücken Sie die Leertaste und dann P, Ihr StormTrooper hat jetzt 10 Gesundheit (Beachten Sie, dass ein StormTrooper nicht die Kraft hat, dies ist nur für das Beispiel). Sie greifen auf Variablen genauso zu, wie Sie auf Methoden zugegriffen haben. Stellen Sie einfach sicher, dass die Mitglieder, die Sie erreichen möchten, als öffentlich deklariert sind, da sonst eine Fehlermeldung angezeigt wird.

Wenn die Mitglieder privat wären, wäre es nicht möglich, auf sie zuzugreifen. Alles, was nicht von außerhalb der Klasse / des Skripts erreicht werden sollte, sollte privat sein, um mit dem Kapselungsprinzip fertig zu werden.

Wie funktioniert GetComponent?

Zuerst deklarieren wir ein Objekt vom Typ des Skripts, das wir erreichen wollen.

private Force forceScript = null;

Force ist unser Typ und forceScript ist nur ein Name, den wir für die Verwendung der Referenz im Skript angeben. Wir könnten ihm jeden Namen geben.

Die Funktion GetComponent wird in das Objekt (das StormTrooper-Spielobjekt) schauen, um eine Komponente zu finden, die dem Typ entspricht, den wir übergeben haben. Wenn keine gefunden wird, wird eine Null-Referenz zurückgegeben und unsere forceScript-Variable wird nichts tun. Wenn eine Komponente vom Typ Force gefunden wird, wird die Adresse dieser Komponente an die Variable forceScript übergeben. Um auf die öffentlichen Mitglieder von Force zuzugreifen, müssen wir nur die Skriptvariable mit dem Punktoperator dereferenzieren. Es wird gesagt, dass die forceScript Variable ein Handle für das Objekt ist. Es ermöglicht den Zugriff auf alle öffentlichen (und internen) Mitglieder.

script.PublicMembers;

Private und geschützte Mitglieder bleiben unzugänglich.

Wenn Sie das SerializeField Attribut verwenden, können Sie sehen, dass die Variablen im Inspektor nicht privat sind. Ihre forceScript-Variable beginnt mit dem Wert None(Force) und wird zu einer Kraftreferenz. Wenn Sie darauf klicken, wird das Zielobjekt hervorgehoben.

reference_component

Also kann ich Komponente mit GetComponent zwischenspeichern?

Ja, und es wird empfohlen, dies für einige regelmäßig verwendete Komponenten zu tun.

Beim Zugriff auf integrierte Komponenten wie Rigidbody, Transform, Renderer usw. erlaubte Unity den „direkten“ Zugriff wie folgt:

private void Update(){ this.transform.position += this.transform.forward; this.rigidbody.AddForce(force);}

Sie haben sie alle außer der Transformation entfernt, da es immer eine Transformation für ein bestimmtes Spielobjekt gibt.

GetComponent sollte in Update vermieden werden, daher wird Caching mit ein paar einfachen Codezeilen wie empfohlen:

private Transform myTransform = null;private Rigidbody rigidbody = null;private void Start(){ this.myTransform = this.gameObject.GetComponent<Transform>(); this.rigidbody = this.gameObject.GetComponent<Rigidbody>();}private void Update(){ this.myTransform.position += this.myTransform.forward; this.rigidbody.AddForce(force);}

Dies spart einige Berechnungen.

Viele Komponenten bekommen

Was ist, wenn unser StormTrooper viele Komponenten in sich hat (wahrscheinlich nicht). Duplizieren Sie die Kraft zweimal so StormTrooper hat drei von ihnen.

Gesundheit wird:

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private Force scripts = null; private int health = 5; private void Start() { this.scripts = this.gameObject.GetComponents<Force>(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { for (int i = 0; i < this.scripts.Length; i++) { if(this.scripts.ReportForce()) { this.health += 5; } } } }}

Starte das Spiel, drücke die Leertaste, unser StormTrooper wird ein Jedi. GetComponents sucht nach allen Komponenten des Typs und gibt ein Array davon zurück. Dies ist in Ordnung, wenn Sie nicht auf eine bestimmte Komponente des Typs abzielen, sondern eher auf alle.

Siehe Folgendes würde das Licht ein- und ausschalten:

public class LightFlicker:MonoBehaviour{ private Light lights = null; private float timer = 0.0f private float clockTimer = 0.0f; private void Start(){ this.lights = this.gameObject.GetComponents<Light>(); this.clockTimer = Random.Range(0.5f, 2.0f); } private void Update(){ this.timer += Time.deltaTime; if(this.timer > this.clockTimer){ this.timer = 0.0f; this.clocktimer = Random.Range(0.5f, 2.0f); for(int i = 0; i < this.lights.Length; i++){ lights.enabled = !lights.enabled; } } }}

Interaktion zwischen Objekten.

Wir können auch zwei verschiedene Objekte auf ähnliche Weise interagieren lassen. Erstellen Sie ein neues Objekt namens „Sith“. Entfernen Sie die duplizierten Komponenten aus dem StormTrooper, sodass nur noch eine übrig ist.

using UnityEngine;using System.Collections;public class DarkSide : MonoBehaviour { private const bool force = true; private Force script = null; private void Start () { this.script = GameObject.Find ("StormTrooper").GetComponent<Force>(); } private void Update () { if(Input.GetKeyDown (KeyCode.Space)) { UseForceToControl (); } } private void UseForceToControl() { if(this.script.ReportForce() == false) { Debug.Log ("Do as I say."); } }}

Ändern Sie das Force-Skript des Stormtroopers so, dass er nicht über die Force verfügt (die Sith würden dies verdächtig finden …).

Unten ist die Zeile, die den StormTrooper findet und seine Kraftkomponente erhält.

private void Start () { this.script = GameObject.Find ("StormTrooper").GetComponent<Force>();}

Spielobjekt.Find() findet zuerst das als Parameter übergebene Objekt und sucht dann nach der erforderlichen Komponente darin. Es wird empfohlen, diese Aktionen zu Beginn oder zu einem Zeitpunkt des Spiels auszuführen, zu dem Verzögerungen das Gameplay nicht beeinflussen. GameObject.Find und GetComponent sind teure Funktionen, da sie die gesamte Hierarchie durchlaufen müssen. Wenn Ihre Szene nur 10 Objekte enthält, ist dies in Ordnung, aber wenn sie Tausende davon enthält (eine Armee von Klonen), werden Sie sicherlich einen schlechten Effekt sehen. Erwägen Sie, diese in der Startfunktion aufzurufen. Wenn Ihre Szene viele Sturmtruppen enthält, kann Uity nicht wissen, welche Sie verwenden möchten, und gibt die erste gefundene Szene zurück. Höchstwahrscheinlich haben Sie einen Verweis auf das Objekt, mit dem Sie interagieren möchten, z. B. einen Kollisionsrückruf.

Interaktion zwischen Objekten innerhalb der Hierarchie

Betrachten Sie ein Fahrzeug,, Beim Fahren wird das Zeichen als untergeordnetes Objekt daran angehängt. Das Fahrzeug hätte eine Besonderheit, wenn der Fahrer die Kraft hat (besseres Handling, schneller und so weiter). Da der Fahrer angehängt ist, befindet er sich unten in der Hierarchie.
Das Fahrzeug kann die Kraftkomponente des Fahrers mit dem gleichen Prinzip finden wir zuvor verwendet (GameObject.Finden), aber wir können es schneller machen, indem wir dem Fahrzeug anzeigen, dass sich der Fahrer in seiner eigenen Hierarchie befindet.

private Force script = null;private void Start () { this.script = this.transform.Find ("Sith").GetComponent<DarkSideForce>();}

GameObject wird gegen transform getauscht. In der Tat gehört ein untergeordnetes Objekt zur Transformation des übergeordneten Objekts, daher müssen wir in die Transformation schauen, um unser Objekt zu finden. Im Beispiel befindet sich das Objekt direkt unter dem aufrufenden Objekt, wenn sich Ihr Sith im Cockpit-Objekt unter dem Auto-Objekt befindet, müssen Sie den Pfad angeben:

private void Start () { this.script = this.transform.Find ("Cockpit/Sith").GetComponent<DarkSideForce>();}

Das aufrufende Objekt ist das Auto und wird im Aufruf als this bezeichnet. Dann wird Cockpit durchsucht und Sith durchsucht. Wenn der Pfad falsch ist, gibt Find null zurück und Sie erhalten eine Nullreferenzausnahme. Sie können das folgendermaßen vermeiden:

private void Start () { Transform tr = this.transform.Find ("Cockpit/Sith"); if(tr != null){ this.script = tr.GetComponent<DarkSideForce>(); }else{Debug.LogWarning("No component found");}}

transformieren.Find gibt eine Transformation zurück, während GameObject.Find gibt ein GameObject zurück.

Noch besser, wir können verwenden:

private void Start () { this.script = GetComponentInChildren<DarkSideForce>();}

Jetzt kann unser Fahrzeug wissen, dass unser Sith die Kraft hat und spezielle Funktion anwenden.
Ein Problem, das Sie kennen sollten, wenn das übergeordnete Objekt ein Skript des gesuchten Typs enthält, wird es zurückgegeben. Wenn Sie also denken, dass Sie es mit der untergeordneten Komponente zu tun haben, haben Sie es tatsächlich mit der übergeordneten Komponente zu tun.

In diesem Beispiel müssen wir für jede Art von Treiber überprüfen, ob es sich um einen StormTrooper oder einen Sith handelt. Ein besserer Ansatz wäre die Verwendung der Vererbung gewesen, so dass die oberste Basisklasse die Kraftvariable enthält und eine einzige Funktion für alle Treibertypen verwendet wird. Aber Vererbung und Effizienz waren hier nicht unser Hauptproblem.

SendMessage und BroadcastMessage

SendMessage und BroadcastMeassage sind zwei weitere Möglichkeiten, die einfacher, aber auch teurer sein können. Das Prinzip bleibt dasselbe, wir haben einen Verweis auf ein Objekt und möchten ein darin enthaltenes Skript ändern.

Mit SendMessage können Sie eine Funktion für ein Objekt aufrufen, ohne einen Verweis auf das Skript finden zu müssen. Jetzt sagst du mir: „Was ist all diese Verwirrung, wenn du es einfach machen kannst?“

SendMessage verwendet Reflection und ist im Vergleich zu GetComponent extrem langsam und sollte nur in Fällen in Betracht gezogen werden, in denen es das Spiel nicht beeinflusst oder wenn Sie keine andere Lösung haben.

Und das sollte logisch klingen, mit GetComponent geben wir genau an, wo sich das Skript im Speicher befindet. Mit SendMessage kennt der Compiler das Objekt, durchläuft jedoch alle Skripte und Funktionen, bis er den glücklichen Gewinner findet. Dies kann sich auf Ihr Spiel auswirken, wenn Sie es häufig für mehrere Objekte verwenden und das Objekt viele Komponenten und Skripte enthält (die übrigens Komponenten sind).

SendMessage sucht nur das Objekt, an das der Aufruf erfolgt, BroadcastMessage startet eine Suche für das Zielobjekt, aber auch für alle seine untergeordneten Objekte. Die Recherche dauert noch länger, wenn das Objekt eine große Hierarchie enthält. Ein Newcomer erschien mit Unity4 SendMessageUpwards, das Sie erraten haben sollten, macht dasselbe wie BroadcastMessage aber nach oben, also alle Eltern des Objekts bis root.

Ok, betrachten wir, dass unser StormTrooper eine Funktion zum Empfangen von Bestellungen hat

//Order.csusing UnityEngine;using System.Collections;public class Order : MonoBehaviour{ private void ReceiveOrder(string order) { switch(order) { case "Fetch": //Action Debug.Log("Can you repeat Sir?"); break; // other cases } }}

Betrachten wir nun, dass unser StormTrooper einen Kommandanten aus dem Imperium trifft:

// Commander.csprivate void Update(){ if(Input.GetKeyDown(KeyCode.Space)) { GameObject.Find("StormTrooper").SendMessage("ReceiveOrder","Fetch"); }}

Wie Sie sehen können, bleibt das Prinzip dasselbe, ich brauche einen Verweis auf das Zielobjekt und dann sende ich eine Nachricht an es. Genau wie GetComponent ist es besser, wenn Sie die Objektreferenz zwischengespeichert haben.

Es ist auch möglich, einen Parameter hinzuzufügen, um sicherzustellen, dass die Nachricht einen Empfänger findet oder nicht.

Beachten Sie, dass es nicht möglich ist, eine Funktion zu verwenden, die einen Wert zurückgibt. Sie müssten dann zurückgehen und GetComponent verwenden.

Interaktive Funktionen

Unity verwendet viele Funktionen, die zwei oder mehr Objekte interagieren lassen. Der Vorteil dieser Funktionen ist, dass sie viele Informationen über das andere Objekt speichern.

  1. Kollisionsfunktionen OnTriggerXXXX/OnCollisionXXXX
  2. Physikfunktionen XXXXcast

Los geht’s mit der Kollisionsdeklaration:

private void OnCollisionEnter(Collision colReference){}private void OnTriggerEnter(Collider colReference){}

Der Aufruf der Funktion erzeugt eine Variable vom Typ Collision oder Collider. Die Funktion füllt diese Instanz mit Informationen zur Kollision und enthält einen Verweis auf das andere Objekt, mit dem wir interagieren.

prviate void OnCollisionEnter(Collision colReference){ GameObject goRef = colReference.gameObject; if(goRef.CompareTag("StormTrooper")) { Health script = goRef.GetComponent<Health>(); script.health -= 5; }}

Beachten Sie, dass ich nicht nach dem anderen Objekt suchen muss, da colReference vom Typ Collision ist und einen Verweis auf dieses Objekt als Member enthält. Jetzt kann ich meine GetComponent wie gewohnt ausführen und auf meine Public Health-Variable zugreifen.

Funktionen aus dem Physikunterricht haben ein ähnliches Prinzip. Die Daten werden nicht in einer Collision / Collider-Instanz, sondern in einer RaycastHit-Instanz gespeichert:

using UnityEngine;using System.Collections;public class CheckFlat : MonoBehaviour { private void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { float result = Vector3.Dot(hit.normal,Vector3.up); if(Mathf.Abs(result) > 0.9) { Debug.Log("Ground is flat enough"); } else { Debug.Log("Not Flat Enough"); } } }}

Ich deklariere eine Variable vom Typ RaycastHit und übergebe sie an die Funktion. Ich benötige das Schlüsselwort out, um einen Verweis auf diese Struktur zu erstellen, damit die Funktion die darin enthaltene Struktur ändern kann. Wenn Sie sich über diesen Prozess wundern, lesen Sie den Artikel Speicherverwaltung und den Abschnitt Wert / Referenztyp.
Was ich hier mache, ist zu überprüfen, wie flach die Oberfläche ist, auf die ich den Cursor lege. Dies ist nützlich für strategische Spiele, bei denen Sie ein Gebäude platzieren möchten, aber nicht auf einer Klippe.
Ich benutze das Punktprodukt des Geländes normal mit dem Vector3.Wenn das Ergebnis nahe genug an 1 liegt, was bedeutet, dass sie ausgerichtet sind, ist der Boden flach genug für den Bau.
Offensichtlich sind einige weitere Berechnungen erforderlich, da dies nur für einen Punkt gilt und Sie den Normalwert überprüfen müssten, aber Sie haben die Idee. Ich habe mit einem Raycast eine Interaktion zwischen meiner Mausposition und dem Gelände erstellt.

Ein Raycast enthält einen Startpunkt und eine Richtung. In unserem Fall ist der Ausgangspunkt die Umwandlung der Mausposition in die Weltposition und dann eine Richtung basierend auf der Kameradrehung und der Projektionsmatrix. Diese Linie verläuft unendlich in die definierte Richtung (Sie können auch eine maximale Entfernung festlegen) und wenn sie auf einen Collider trifft, werden die Informationen dieses Colliders und der Kollision in der RaycastHit-Struktur gespeichert. Die Struktur enthält dann einen Verweis auf das Trefferobjekt.