W skryptach Unity można spotkać dwie konstrukcję gameObject oraz this. Wiele osób stosuje je zamiennie, ale czy jest to słuszne?

Niedawno na moim fanpage na Facebooku zadałem (nieco podchwytliwe) pytanie czytelnikom. Czy w trakcie pracy z Unity w swoich skryptach korzystają z this, czy może z gameObject. Zdecydowana większość wybierała jedną z możliwości, argumentując wybór czytelnością kodu bądź przyzwyczajeniem. Jedna osoba, natchniona pytaniem przeprowadziła testy szybkości wykonania kodu dla każdego wariantu (tutaj serdecznie pozdrawiam). Dla zainteresowanych – podobno wykorzystanie this jest szybsze dla aktualnej wersji Unity.

Dopiero po jakimś czasie jedna osoba zwróciła uwagę na jedną bardzo istotną rzecz, która była ukryta w moim pytaniu. Mianowicie, this i gameObject to dwie różne rzeczy!

Jak mogą wiedzieć osoby związane z programowaniem, słówko this nie jest niczym nowym. Wykorzystywane jest niemal w każdym języku obiektowym. Jest niczym więcej jak odwołaniem się obiektu do samego siebie. Jego rola w Unity jest w zasadzie identyczna.

Musimy sobie tutaj rozgraniczyć dwie rzeczy. Ogólnie w programowaniu mamy klasy i obiekty. Obiekt jest instancją (reprezentantem) jakiejś konkretnej klasy. Natomiast w Unity, obiektem nazywamy również poszczególne elementy wrzucone w świat gry. Np. skrzynka czy postać gracza to też obiekt, dla rozróżnienia, takie obiekt nazwiemy obiektem gry. Dokładnie w tej różnicy pojęć znajduje się odpowiedź, czym różni się this od gameObject.

Obiekt gry, czyli dowolny element w grze, zbudowany jest z komponentów. Skrypty są takim komponentem. Dlatego słówko this wewnątrz skryptu będzie odwoływało się konkretnie do skryptu, w którym zostało użyte. Natomiast gameObject odwołuje się do obiektu gry jako całości.

Tym jest gameObject
Do tego odwołujemy się stosując this.

Nasuwa się tutaj pytanie, to czemu w obu mamy dostępne te same pola i funkcje? Wynika to z uprzejmości Unity, które przygotowało swoje oprogramowanie tak, aby było to możliwe. Nie jest to jedyna sytuacja, gdzie taki ruch jest dozwolony. Bardzo dobrym przykładem będzie komponent Transform. Przykładowo:

transform.postion.x
gameObject.transform.position.x

Te dwie linijki zrobią dokładnie to samo. Różnica jest taka, że dzięki odpowiedniej konstrukcji Unity, możemy niejawnie wywołać pobieranie komponentu transform i poniekąd skrócić sobie kod. Idąc tą samą drogą, poniższy zapis, również przedstawia dwie identyczne linie kodu:

gameObject.transform.position.x
this.gameObject.transform.position.x

Idea powstania takiej konstrukcji, to wygoda użytkowania. W zasadzie moglibyśmy się do każdego komponentu odwoływać z wykorzystaniem funkcji GetComponent. Jednak do niektórych komponentów odwołujemy się bardzo często, dlatego twórcy Unity skrócili nam drogę proponując takie rozwiązania jak transform. Tak samo działa gameObject, które możemy wykorzystać skrótowo, zamiast za każdym razem pobierać obiekt do którego należy skrypt. Część z was na pewno zauważyła też możliwość wykorzystania takiego kodu:

transform.gameObject

Jest to nic innego jak pobranie obiektu, do którego przypisany jest dany komponent transform. Właściwie przez takie ułatwiacze, możemy sobie napisać nawet coś takiego:

transform.gameObject.transform.gameObject.gameObject.transform

Jest to kompletnie bez sensu, ale będzie działało… Przez dodanie tej masy ułatwiaczy, czasami sami twórcy Unity się gubią i w dokumentacji możemy znaleźć takie zapisy:

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour {
    void Awake() {
        DontDestroyOnLoad(transform.gameObject);
    }
}
// https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html

Gdzie taki zapis jest redundantny (nadmiarowy), bo wystarczyłoby:

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour {
    void Awake() {
        DontDestroyOnLoad(gameObject);
    }
}

Najprostszy test, żeby zaobserwować różnicę między gameObject i this to przetestowanie takich dwóch linijek kodu:

Destroy (gameObject);
Destroy (this);

Pierwszy wariant powinien usunąć cały obiekt, do którego jest przypisany skrypt ze sceny. Natomiast druga funkcja, powinna usunąć sam komponent skryptu z obiektu.

Jak to stosować?

Zostaje nam w zasadzie jedno pytanie, to kiedy używać gameObject, a kiedy this?

W zasadzie nie ma żadnych wyznaczników, na stronie Unity również nic nie znalazłem. Dlatego ja proponuje wam takie zastosowanie. Jeżeli odwołujecie się do konkretnej części skryptu, np. wywołujecie funkcję lub operujecie na atrybutach tego skryptu stosujcie this, natomiast w przypadku odwoływania się do zewnętrznych atrybutów obiektu gry, stosujcie gameObject.

Jeżeli nie chce wam się tego pamiętać, to rozsądniejsze może być stosowanie gameObject.