Articles

GetComponent in C#

w tym tutorialu

  • dostęp do innego skryptu z wnętrza obiektu
  • Jak działa GetComponent?
  • uzyskanie wielu komponentów
  • interakcji między obiektami.
  • interakcja między obiektami w hierarchii
  • SendMessage i BroadcastMessage
  • funkcje interaktywne

wprowadzenie

jednym z powtarzających się problemów podczas rozpoczynania od Unity jest dostęp do elementów w jednym skrypcie z innego skryptu. Wielu uważa, że dereferencja z nazwy skryptu wystarczy, szybko zdają sobie sprawę, że tak nie jest.

podczas tworzenia programu zmienne są przechowywane w pamięci w różnych miejscach. Jeśli obiekt może zobaczyć inne elementy obiektu, istnieje ryzyko ich modyfikacji, nawet jeśli nie było to zamierzone. Jeśli dla instancji dwa obiekty posiadają ten sam skrypt o tych samych nazwach zmiennych, kompilator nie byłby w stanie określić, do którego z nich się odnosimy.

aby zapobiec temu problemowi, każdy obiekt w pamięci nie może zobaczyć innych obiektów. Następnie należy wskazać jednemu obiektowi, gdzie jest potrzebna zmienna w pamięci. Cała zasada wskaźnika została porzucona dawno temu, a teraz używamy referencji (która faktycznie używa wskaźnika wewnętrznie).

istnieje wiele sposobów dostępu do zmiennych, niektóre są lepsze od innych, a niektóre są po prostu używane w określonych sytuacjach.

  1. GetComponent, najczęściej spotykany, również ten, który na początku najbardziej myli
  2. SendMessage, może wydawać się łatwiejszy do uchwycenia, ale jest również mniej wydajny.
  3. zmienne statyczne, najłatwiejsze, ale i najbardziej skomplikowane do pełnego zrozumienia na początku.

Tworzenie sceny

Utwórz nową scenę, dodaj puste obiekty gry i nazwij ją „StormTrooper”. Utwórz dwa skrypty o nazwach „Zdrowie” i „siła”. Dodaj oba skrypty do StormTrooper.

Otwórz oba skrypty w edytorze i dodaj następujące linie:

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; } } }}

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; }}

próba tego kodu zwróci po prostu błąd „odniesienie do obiektu jest wymagane, aby uzyskać dostęp do niestatycznych elementów”.

musimy powiedzieć komponentowi zdrowia na Szturmowcu, gdzie jest komponent siły, aby mógł uzyskać dostęp do swoich członków. Ponieważ nasze Skrypty znajdują się na tym samym obiekcie, musimy po prostu zadeklarować zmienną typu skryptu, do którego chcemy uzyskać dostęp i użyć GetComponent do znalezienia skryptu.

dostęp do innego skryptu wewnątrz obiektu

Modyfikuj Zdrowie za pomocą:

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; } } }}

z modyfikacjami, uruchom grę, naciśnij spację, a następnie naciśnij P, Twój Szturmowiec ma teraz 10 zdrowia (zauważ, że Szturmowiec nie ma siły, jest to tylko dla przykładu). Dostęp do zmiennych odbywa się w ten sam sposób, w jaki uzyskiwano dostęp do metod. Po prostu upewnij się, że Członkowie, do których próbujesz dotrzeć, są uznani za publicznych lub pojawi się błąd.

gdyby członkowie byli prywatni, dostęp do nich nie byłby możliwy. Wszystko, co nie powinno być osiągnięte spoza klasy / skryptu, powinno być prywatne, aby poradzić sobie z zasadą enkapsulacji.

Jak działa GetComponent?

najpierw deklarujemy obiekt typu skryptu, do którego chcemy dotrzeć.

private Force forceScript = null;

Force jest naszym typem, a forceScript jest tylko nazwą, którą podajemy przy użyciu referencji w skrypcie. Możemy nadać mu dowolne imię.

funkcja GetComponent zajrzy do wnętrza obiektu (obiektu Gry StormTrooper), aby znaleźć komponent odpowiadający typowi, który przekazaliśmy. Jeśli nie zostanie znalezione, zwracana jest Referencja null, a nasza zmienna forceScript nic nie zrobi. Jeśli zostanie znaleziony komponent typu Force, To Adres tego komponentu jest przekazywany do zmiennej forceScript, zmienna wskazuje teraz na komponent Force przechowywany gdzieś w pamięci. Aby uzyskać dostęp do public members of Force, musimy tylko zderferować zmienną skryptową za pomocą operatora kropki. Mówi się, że zmienna forceScript jest uchwytem do obiektu. Daje dostęp do wszystkich członków publicznych (i wewnętrznych).

script.PublicMembers;

członkowie prywatni i chronieni pozostaną niedostępni.

użycie atrybutu SerializeField pozwala zobaczyć, że zmienne w Inspektorze despute są prywatne. Zmienna forceScript zaczyna się od wartości None (Force) i zamienia się w odniesienie Force. Jeśli klikniesz na niego, podświetli obiekt docelowy.

reference_component

więc mogę buforować komponent za pomocą GetComponent?

tak, i zaleca się to w przypadku niektórych regularnie używanych komponentów.

podczas uzyskiwania dostępu do wbudowanych komponentów, takich jak Rigidbody, Transform, Renderer,…itp., Unity umożliwiał „bezpośredni” dostęp w następujący sposób:

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

usunęli je wszystkie z wyjątkiem transform, ponieważ zawsze istnieje Transform na dowolnym obiekcie gry.

GetComponent należy unikać wewnątrz aktualizacji, więc zalecane jest buforowanie z kilkoma prostymi liniami kodu, takimi jak:

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);}

To pozwoli zaoszczędzić trochę obliczeń.

zdobycie wielu elementów

Co jeśli nasz Szturmowiec ma w sobie wiele sił (choć raczej nie). Podwoić siły dwa razy, więc Szturmowiec ma ich trzech.

zdrowie staje się:

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; } } } }}

Uruchom grę, naciśnij spację, nasz Szturmowiec staje się Jedi. GetComponents wyszukuje wszystkie komponenty tego typu i zwraca ich tablicę. Jest to w porządku, jeśli nie kierujesz konkretnego komponentu typu, ale bardziej prawdopodobne, że wszystkie z nich.

patrz poniżej, aby włączać i wyłączać światła:

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; } } }}

interakcja między obiektami.

możemy również uzyskać dwa różne obiekty do interakcji w podobny sposób. Utwórz nowy obiekt o nazwie „Sith”. Usuń zduplikowane elementy z szturmowca, aby pozostał tylko jeden.

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."); } }}

zmodyfikuj skrypt siły szturmowca, aby nie miał siły (Sithowie uznaliby to za podejrzane…), daję ci wskazówkę, że dzieje się to w skrypcie startowym.

Poniżej znajduje się linia, która znajduje szturmowca i pobiera jego składnik siły.

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

GameObject.Find() najpierw znajduje obiekt przekazany jako parametr, a następnie szuka wymaganego komponentu wewnątrz niego. Zaleca się wykonywanie tych działań na początku lub w czasie gry, w którym LGD nie wpłynie na rozgrywkę. GameObject.Find I GetComponent są kosztownymi funkcjami, ponieważ muszą iterować przez całą hierarchię. Jeśli twoja scena zawiera tylko 10 obiektów, będzie dobrze, ale jeśli zawiera ich tysiące (armię klonów), na pewno zobaczysz zły efekt. Rozważ wywołanie ich w funkcji Start. Zauważ, że jeśli twoja scena zawiera wiele szturmowców, Uity nie może wiedzieć, którego zamierzałeś użyć i zwróci pierwszego, którego znajdzie. Najprawdopodobniej przechowujesz odniesienie do obiektu, z którym chcesz wejść w interakcję, na przykład wywołanie zwrotne kolizji.

interakcja między obiektami w hierarchii

rozważa pojazd,, podczas jazdy nim, znak jest dołączony do niego jako obiekt potomny. Pojazd miałby specjalną cechę, jeśli kierowca ma siłę (lepsze prowadzenie, szybsze i tak dalej). Ponieważ kierowca jest dołączony, jest on poniżej w hierarchii.
pojazd może znaleźć składową siły kierowcy na tej samej zasadzie, której używaliśmy wcześniej (GameObject.Znaleźć), ale możemy sprawić, że stanie się to szybciej, wskazując pojazdowi, że kierowca znajduje się we własnej hierarchii.

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

GameObject zostanie zamieniony na transform. Rzeczywiście, obiekt potomny należy do transformacji rodzica, więc musimy przyjrzeć się transformacji, aby znaleźć nasz obiekt. W przykładzie obiekt znajduje się tuż pod obiektem wywołującym, jeśli twój Sith znajduje się wewnątrz obiektu kokpitu pod obiektem samochodu, musisz podać ścieżkę:

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

obiekt wywołujący jest Car i jest określany jako this w wywołaniu. Następnie przeszukiwany jest kokpit i Sithowie. Jeśli ścieżka jest błędna, Find zwraca null i otrzymuje wyjątek null Reference. Możesz tego uniknąć w następujący sposób:

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

transform.Find Zwraca transformację podczas gdy GameObject.Find zwraca obiekt GameObject.

jeszcze lepiej, możemy użyć:

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

teraz nasz pojazd może wiedzieć, że nasz Sith ma siłę i zastosować specjalną funkcję.
jedna sprawa, o której warto wiedzieć, jeśli obiekt nadrzędny zawiera skrypt typu, którego szukasz, zwróci go. Więc myśląc, że mamy do czynienia z komponentem potomnym, mamy do czynienia z komponentem rodzica.

w tym przykładzie musimy sprawdzić dla każdego rodzaju sterownika, czy jest to Szturmowiec czy Sith. Lepszym podejściem byłoby użycie dziedziczenia tak, aby najwyższa klasa bazowa posiadała zmienną force i jedna pojedyncza funkcja byłaby używana dla wszystkich typów sterowników. Ale dziedziczenie i efektywność nie były naszym głównym problemem.

SendMessage i BroadcastMessage

SendMessage i BroadcastMeassage to dwie inne możliwości, które mogą być prostsze, ale także droższe. Zasada pozostaje taka sama, mamy odniesienie do obiektu i chcemy zmodyfikować skrypt, który zawiera.

za pomocą SendMessage można wywołać funkcję na obiekcie bez konieczności znajdowania odnośnika do skryptu. Teraz mówisz mi: „co to za zamieszanie, jeśli możesz to uprościć?”

SendMessage używa reflection i jest bardzo powolny w porównaniu do GetComponent i powinien być brany pod uwagę tylko w przypadkach, gdy nie wpłynie to na grę lub jeśli nie masz innego rozwiązania.

i to powinno brzmieć logicznie, za pomocą GetComponent wskazujemy dokładnie, gdzie skrypt znajduje się w pamięci. Dzięki SendMessage kompilator zna obiekt, ale przejrzy wszystkie skrypty i funkcje, dopóki nie znajdzie szczęśliwego zwycięzcy. Może to mieć wpływ na grę, jeśli używasz jej często na wielu obiektach i jeśli obiekt ma wiele komponentów i skryptów (które są komponentami przy okazji).

SendMessage szuka tylko obiektu, do którego jest wywołane, BroadcastMessage uruchamia wyszukiwanie na obiekcie docelowym, ale także na wszystkich jego potomkach. Badania będą jeszcze dłuższe, jeśli obiekt zawiera dużą hierarchię. Przybysz pojawił się z Unity4 SendMessageUpwards, które należy się domyślić robi to samo co BroadcastMessage, ale w górę, więc wszyscy rodzice obiektu do korzenia.

ok rozważmy, że nasz Szturmowiec ma funkcję przyjmowania rozkazów

//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 } }}

teraz rozważmy, że nasz Szturmowiec spotyka dowódcę z Imperium:

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

jak widać, zasada pozostaje taka sama, potrzebuję odniesienia do obiektu docelowego, a następnie wysyłam do niego wiadomość. Podobnie jak w przypadku GetComponent, lepiej jest buforować odniesienie do obiektu.

możliwe jest również dodanie parametru, aby upewnić się, że wiadomość znajdzie odbiornik lub nie.

zauważ, że nie jest możliwe użycie funkcji zwracającej wartość. Następnie musiałbyś wrócić do góry i użyć GetComponent.

funkcje interaktywne

Unity wykorzystuje wiele funkcji, które powodują interakcję dwóch lub więcej obiektów. Zaletą tych funkcji jest to, że przechowują one wiele informacji o drugim obiekcie.

  1. funkcje kolizji OnTriggerXXXX/OnCollisionXXXX
  2. funkcje fizyki XXXXcast

oto deklaracja kolizji:

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

wywołanie funkcji tworzy zmienną typu Collider lub Collider. Funkcja wypełni tę instancję informacjami o kolizji i będzie zawierała odniesienie do innego obiektu, z którym wchodzimy w interakcję.

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

zauważ, że nie muszę szukać innego obiektu, ponieważ colReference jest typu Collision i zawiera odniesienie do tego obiektu jako element. Teraz mogę wykonać mój komponent get jak zwykle i uzyskać dostęp do mojej zmiennej zdrowia publicznego.

funkcje z klasy fizyki mają podobną zasadę. Dane nie są przechowywane w instancji Collider/Collider, ale w instancji RaycastHit:

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"); } } }}

deklaruję zmienną typu RaycastHit, przekazuję ją do funkcji. Raycasthjest to struktura, a następnie typ wartości, potrzebuję słowa kluczowego out, aby utworzyć odniesienie do tej struktury, aby funkcja mogła modyfikować strukturę wewnątrz. Jeśli zastanawiasz się nad tym procesem, zapoznaj się z artykułem o zarządzaniu pamięcią i sekcją na temat typu wartości/odniesienia.
to co tu robię to sprawdzam jak płaska jest powierzchnia na której umieszczam kursor. Jest to przydatne w grach strategicznych, w których chcesz umieścić budynek, ale nie na klifie.
ja używam iloczynu punktowego terenu normalnego z wektorem 3.W górę i jeśli wynik jest wystarczająco blisko 1, co oznacza, że są wyrównane, ziemia jest wystarczająco płaska do budowy.
oczywiście potrzebne są jeszcze jakieś obliczenia, bo to jest tylko jeden punkt i trzeba by sprawdzić normalność wokół, ale masz pomysł. Stworzyłem interakcję między moją pozycją myszy a terenem za pomocą raycasta.

raycast zawiera punkt wyjścia i kierunek. W naszym przypadku punktem wyjścia jest konwersja pozycji myszy na pozycję światową, a następnie kierunek oparty na obrotach kamery i matrycy projekcyjnej. Linia ta biegnie w nieskończoność w określonym kierunku (można również ustawić maksymalną odległość) i jeśli uderzy w Zderzacz, informacje o Zderzaczu i zderzeniu są przechowywane w strukturze RaycastHit. Struktura zawiera wtedy odniesienie do obiektu hit.