Unity3d QuickTip – czyli szybkie porady, rozwiązania częstych problemów i sztuczki w Unity3d!
Dzisiejszy odcinek: Komunikacja skryptów
Uwaga! Jest to poradnik typu QuickTip. Zatem skupia się on na osiągnięciu założonego celu. Zatem zakładamy że użytkownik zna na tyle program Unity3d, aby samodzielnie wykonać najprostsze czynności, jak np. dodanie modelu kostki do sceny czy dodanie modelowi jakiegoś komponentu. Jeżeli brakuje Ci tej podstawowej wiedzy, zapraszam do tutoriala:
Unity Tutorial – Podstawy
Teoria
Jest to jeden z najczęściej spotykanych problemów, zwłaszcza u początkujących programistów. Jak zmienić wartość w skrypcie A, będąc w skrypcie B? Dziś omówimy sobie kilka technik, które mogą nam to umożliwić.
Zacznijmy od tego, że mamy kilka przypadków:
- Skrypty A i B, są komponentami tego samego obiektu.
- Skrypt A jest komponentem w obiekcie nadrzędnym (parent), a skrypt B jest komponentem w obiekcie podrzędnym (child).
- Skrypty A i B są komponentami dwóch różnych obiektów.
Skrypty A i B są w tym samym obiekcie
Tutaj sytuacja jest oczywiście najprostsza. Ponieważ do komunikacji wystarczy coś takiego
SecondScript sc = gameObject.GetComponent<SecondScript>(); sc.someValue = false; sc.someFunction();
Dokumentacja: GetComponent<Type typ>()
SecondScript to oczywiście nazwa naszego skryptu, do którego się odwołujemy. Jedyne o czym musimy pamiętać w takim wypadku, jest fakt, że odwołać się możemy tylko do funkcji i zmiennych publicznych!
Skrypty A i B są w relacji Parent – Child
Metoda automatyczna
W tym przypadku, mogłoby być nieco trudniej, ale na szczęście Unity dostarcza fajnych rozwiązań. Otóż skrypt wygląda następująco:
SecondScript sc = gameObject.GetComponentInChildren<SecondScript>(); sc.someValue = false; sc.someFunction();
Dokumentacja: GetComponentInChildren<Type typ>()
W takim przypadku, skrypt będzie poszukiwał pierwszego pasującego komponentu, zaczynając od najmniej zagnieżdżonych obiektów. Jednak, co w sytuacji, gdy mamy np. dwa tak samo nazwane skrypty, w dwóch różnych childach i chcemy się odwołać do tego bardziej zagnieżdżonego? Wtedy z pomocą przychodzi kolejna metoda!
Metoda ręczna
Sytuacja będzie podobna do pierwszego przypadku, jednak obiekt do którego się odwołamy, znajdziemy sobie sami, a posłuży do tego funkcja Find z biblioteki Transform (co jest istotne!)
GameObject ourChild = transform.Find("LeftShoulder/Arm/Hand/Finger");
Dokumentacja: Transform.Find(string nazwa)
Jako parametr podajemy string, który jest hierarchią. Przykładowo załóżmy, że mamy taką hierarchię obiektów:
– Gracz
– Lewa Reka
– Prawa Reka
– Pistolet
Jeżeli chcemy znaleźć obiekt pistolet, będąc w obiekcie Gracz, skrypt wyglądać będzie tak:
GameObject ourChild = transform.Find("Prawa Reka/Pistolet");
Jeżeli chcemy znaleźć obiekt pistolet, będąc w obiekcie Prawa Reka, wystarczy coś takiego:
GameObject ourChild = transform.Find("Pistolet");
Teraz w zmiennej ourChild mamy dostęp do obiektu pistolet. Odwołanie się do zmiennej w komponencie, będzie analogiczne jak w pierwszym przypadku. Więc skrypt kompletny, da nam coś takiego:
GameObject ourChild = transform.Find("Pistolet"); SecondScript sc = outChild.GetComponent<SecondScript>(); sc.someValue = false; sc.someFunction();
A co z relacją Child – Parent?
Wiemy, że da się odwołać z obiektu nadrzędnego, do obiektu podrzędnego. A co w przypadku, gdy chcemy odwoływać się w przeciwną stronę? Otrzymujemy wtedy bliźniaczą do GetComponentInChild funkcję. Nazywa się: GetComponentInParent, co ciekawe, składniowo nieco inna:
SecondScript sc = gameObject.GetComponentInParent<SecondScript>(); sc.someValue = false; sc.someFunction();
Dokumentacja: GetComponentInParent(Type typ)
Zanika trójkątny nawias, a nazwę komponentu podajemy jak zwykły parametr. Funkcja znów przeszukuje drzewo w górę. Z racji, że dany child, ma zawsze tylko jeden obiekt nadrzędny (parent), nie ma tutaj niejasności. Uniemożliwia to, odwoływanie się w bok. Tzn. Jeśli przypomnimy sobie naszą hierarchię z początku postu, stanie się jasne, że z obiektu prawej ręki, nie przeszukamy obiektu lewej ręki. Jeżeli skrypt uruchomimy z obiektu pistolet, skrypt będzie szukany tylko w obiektach prawa ręka i gracz.
Metoda z indeksem
Jest jeszcze jedna sztuczka. Jeśli znamy indeks dziecka (childa) i będzie on niezmienny, możemy dostać się do obiektu, za pomocą funkcji GetChild, otrzymamy wtedy coś takiego:
GameObject ourChild = transform.GetChild(0); SecondScript sc = outChild.GetComponent<SecondScript>(); sc.someValue = false; sc.someFunction();
Dokumentacja: GetChild(int Indeks)
Może to być szczególnie przydatne, gdy lista dzieci jest stała przez całą grę. Np. Gdy wiemy, że gracz zawsze pod obiektem prawa ręka, ma tylko jednego childa, lub od samego początku gry, do samego końca będzie ich zawsze np. 5.
Skrypty A i B, są w różnych obiektach
Kolizja przyjacielem
Tutaj, właściwie są trzy metody. Najczęściej zdarzy się tak, że obiekt na których chcemy wpłynąć wejdzie z nami w kolizję. Np. zbierając przedmioty, gracz znajduje się w obszarze kolizji z nimi. Co nam to daje? Przyjrzyjmy się funkcji OnTriggerEnter:
void OnTriggerEnter(Collider other) { }
Dokumentacja: OnTriggerEnter(Collider kolider)
Collider other! Other, to nic innego jak obiekt, wchodzący w kolizję z nami! Ponownie, możemy wykorzystać sztuczki z poprzednich przykładów. Jednak, ponownie Unity, dostarcza nam fajne narzędzie. Funkcja SendMessage – bo o niej tu mowa, nie wymaga od nas podania komponentu. Sama znajdzie sobie funkcję w danym obiekcie, oczywiście jeśli ona istnieje i jest publiczna. Wadą tego rozwiązania, jest niestety fakt, że pozwala nam przesłać tylko jeden parametr do funkcji – są sztuczki znoszące to ograniczenie, ale nie zawsze skuteczne. Zobaczmy jak to działa, składnia samej funkcji wygląda tak:
gameObject.SendMessage("ourFunction", 5.0F);
Dokumentacja: SendMessage(string nazwaFunkcji, Object parametr)
Pierwszy parametr, to nazwa funkcji, której szukamy, a druga to parametr.
[stextbox id=”info” defcaption=”true”]Sztuczka!
Jeżeli chcesz przesłać np. dwie lub trzy wartości liczbowe, możesz jako parametr przesłać obiekt typu Vector2 lub Vector3. Dla bardziej skomplikowanych wartości, można nawet utworzyć własną klasę.[/stextbox]
Ostatecznie, nasz skrypt, wyglądał by tak:
void OnTriggerEnter(Collider other) { other.SendMessage("ourFunction", 5.0F); }
Stały partner
Czasami, nie dochodzi do kolizji między dwoma obiektami, a potrzebujemy połączyć ich skrypty. Jeżeli obiekty, znajdują się zawsze w grze, możemy to wykorzystać, czyniąc z jednego obiektu ze skryptem, parametr drugiego skryptu. Wystarczy przygotować zmienną publiczną typu GameObject:
public GameObject partner;
Następny krok, to oczywiście przypisanie z poziomu Unity drugiego obiektu na miejsce tej zmiennej (np. przeciągając obiekt).
Teraz sprawdzi, się dowolna z naszych sztuczek, tylko zamiast gameObject, czy other, podawać będziemy partnera. Przykładowo:
SecondScript sc = partner.GetComponent<SecondScript>(); sc.someValue = false; sc.someFunction();
Gdzieś na planie
Ostatni, zdecydowanie najgorszy przypadek, to taki w którym wiemy, że obiekt jest, ale nie wiemy gdzie. Jedyne co możemy zrobić, to go szukać. Znów korzystamy z funkcji Find, jednak tym razem, z biblioteki GameObject. Składniowo, nie różni się ona kompletnie od tej, poznanej na samym początku:
GameObject obj = GameObject.Find("LeftShoulder/Arm/Hand/Finger");
Dokumentacja: GameObject.Find(string nazwa)
Jednak tutaj warto pamiętać o tym, że ta funkcja jest potwornie zasobożerna. Użycie jej w funkcji Update, to praktycznie zamordowanie gry. Dlatego mimo, że tak funkcja jest tak prosta i wygodna, powinniśmy korzystać z niej w ostateczności, a do tego w funkcjach wykonywanych raz, takich jak Start czy Awake.