Skip to content

Blog Emi

O grach z kobiecej perspektywy

  • GameDev
  • Programowanie
  • Gry
  • Offtop
  • O mnie
Menu

Zenject in depth cz.2 – Contexts & Installers

Posted on 17 października, 201917 października, 2019 by Emi

W poprzednim wpisie wyjaśniłam Wam, do czego może nam się przydać Zenject podczas tworzenia projektów w Unity i dlaczego warto go stosować. W kolejnych artykułach pokażę, z jakich elementów się składa i jakie są najlepsze praktyki korzystania z niego.

Zenjecta pobieramy stąd i instalujemy jako UnityPackage (wystarczy dwa razy kliknąć mają otwarty projekt). Na stronie projektu Github znajdziemy szczegółową dokumentację całego pluginu. Powiem szczerze, że może ona wydać się przytłaczająca na pierwszy (i drugi…) rzut oka. Ja skupię się na tych konkretnych funkcjonalnościach, z których najczęściej korzystam w pracy.

Na początek zapamiętajcie jedno:

Container <- Contexts <- Installer <- Bind(ing)

Czyli np. powiązanie interfejsu z jego implementacją to jest binding, bindingi definiowane są w instalatorach, instalatory podpinane są do kontekstów, a aktywne dla wszystkich kontekstów bindingi trzymane są w kontenerze. Kontener również służy do „resolvowania” (rozwiązywania?) zależności – czyli dana klasa mówi „kochany systemie ja potrzebuję do działa obiektu z interfejsem IXyz i configa XyzConfig” a kontener na to „oho, widzę, że interfejs IXyz jest zbindowany do klasy CoolXyz jako singleton, to przekażę ten obiekt i jeszcze do tego widzę, że jako instancja został zbindowany obiekt z konfiguracją XyzConfig, to go też przekażę”.

Konteksty

Konteksty pozwalają nam na definiowanie miejsc, w których możemy dodawać elementy do kontenera. Będziemy używać dwóch rodzajów kontekstów:

  • SceneContext – wystarczy dodać go na scenie, aby nasze MonoBehavioury miały wstrzykiwane zależności! Na scenie tworzymy nowy pusty obiekt, nazywamy go na przykład „Context” i dodajemy do niego skrypt „SceneContexts”:

Służy on do definiowania instalatorów za pomocą ScriptableObject bądź podpiętych MonoBehaviourów. Dzięki temu możemy też tworzyć instalatory, które wymagają jakichś obiektów Unity – np. prefaba, obiektu ze sceny czy ScriptableObjecta. Różnym typom instalatorów przyjrzymy się w dalszej części artykułu.

  • ProjectContext – w nim definiujemy instalatory globalne, dla wszystkich scen. ProjectContext załaduje się tylko raz, przy starcie aplikacji.

Ma to swoje plusy i minusy. Plusem jest to, że obliczenia związanie ze spinaniem kontenera wykonujemy tylko raz i są one niezmienne przez cały czas działania aplikacji. Nie musimy też martwić się, że w którymś skrypcie użyjemy klasy instalowanej w instalatorze znajdującym się na innej scenie. Minusem jest to, że instalowanie wielu instalatorów potrafi być naprawdę obliczeniożerne – Zenject musi stworzyć drzewo zależności i wstrzykiwać je w obiekty w odpowiedniej kolejności. Dlatego zbyt duża ilość instalatorów (a raczej bindingów) spowolni nam znacznie czas wczytywania się aplikacji, a zbyt długo widoczny loading screen może zaowocować szybką stratą użytkowników.

Aby dodać ProjectContext do naszego projektu, musimy w folderze Resources stworzyć odpowiedni prefab. W tym celu klikamy prawym przyciskiem myszy na odpowiednim katalogu i wybieramy Create -> Zenject -> ProjectContext. Zauważmy, że prefab ma przypięty skrypt bardzo podobny do SceneContextu. I tak, to wszystko – Zenject automagicznie odpali installery dodane do tego prefaba. 🙂

Podsumowując – to co przypniemy pod SceneContext będzie działało tylko wtedy, gdy będziemy mieli aktualnie załadowaną tę scenę, natomiast ProjectContext instaluje zależności raz, globalnie, dla całej aplikacji. W przypadku, gdy używamy addytywnego ładowania scen i posiadamy jakąś scenę np. Core, która jest ciągle aktywna, jej kontekst może nam zastąpić ProjectContext.

Instalatory

Instalatory są miejscem, w którym definiujemy nasze bindingi. Ja najczęściej korzystam z następujących:

  • Installer – zwykła klasa instalatora, możemy jej używać do grupowania bindingów tak, by powiązane ze sobą funkcjonalności definiowane były w oddzielnym miejscu.
  • MonoInstaller – jest to instalator, który możemy przypiąć do GameObjectu. Zazwyczaj mamy co najmniej jeden MonoInstaller na scenie – np. MainSceneInstaller, w którym instalujemy „zwykłe” Installery funkcjonalności najwyższego poziomu. MonoInstallery przydają się też do bindowania skryptów znajdujących się na scenie – np. jakiś PopUpManager, który z jednej strony musi być na scenie, żeby włączać Canvas z okienkiem, a z drugiej strony fajnie jakby był dostępny poprzez wstrzykiwanie do klas działających wewnątrz naszych systemów – na przykład wyświetlenie popupu „Please wait” w trakcie wysyłania zapytania HTTP bądź ładowania reklamy.
  • ScriptableObjectInstaller – instalator, który przechowujemy jak ScriptableObject. Możemy w nim sobie przypinać na przykład prefaby bądź inne ScriptableObjecty. Najczęstszym zastosowaniem takiego instalatora jest właśnie przekazanie do naszych systemów logiki konfiguracji przechowywanej w postaci ScriptableObject. Dzięki temu zyskujemy jedno, konkretne miejsce w którym możemy w razie czego łatwo podmienić konfigurację (na przykład do testów).

Dobre praktyki

Jak z każdą rzeczą na tym świecie, Dependency Injection jest świetnym narzędziem, o ile jest użyte poprawnie. W złych rękach potrafi zamienić utrzymanie każdego projektu w koszmar.

Jeśli chodzi o używanie instalatorów, kluczowa jest ich dobra organizacja. Przepis na porażkę jest taki:

  1. Definiuj bindingi w losowych instalatorach, bez dzielenia na podinstalatory
  2. Posiadaj koszmarnie długie klasy instalatorów, w których panuje bałagan
  3. Podpinaj większość instalatorów pod instalator danej sceny, a jak się okaże, że jednak jest to potrzebne w kilku miejscach, to przenieś do ProjectContextu
  4. Profit – gdy dochodzi nowa klasa do podpięcia, nie wiadomo w którym instalatorze ją zbindować, a jak już znajdziemy niezłe miejsce to okazuje się, że jakaś inna klasa jest nieobecna w danych kontekście, więc przenosimy bindowanie również tamtej klasy, więc mamy wyjątek „Zenject exception – zenject nie mógł stworzyć instancji danej klasy, gdyż nie udało się znaleźć odpowiedniego bindingu” w 5 innych klasach…
  5. Jeśli deadline jest na wczoraj, zapewne skończy się to wrzuceniem prawie wszystkiego w ProjectContext i wydłużeniem czasu ładowania się apki (i kosmicznym bałaganem…)

Ufff, koszmarek, naprawdę.

Na dobry początek starajmy się tworzyć instalator dla każdej funkcjonalności, a potem grupujmy instalatory powiązanych ze sobą funkcjonalności w kolejnych instalatorach. Dobra, przemyślana organizacja (stosowana od samego początku!) pomoże nam uchronić się od opisanego wyżej scenariusza.

Nowy programista, widząc instalator najwyższego poziomu, w którym są instalowanie tylko i wyłącznie inne instalatory (brakuje definicji bindingów) zastanowi się pięć razy, zanim „na pałę” wrzuci gdzieś luźny binding. Dzięki logicznej hierarchii powinno być też dużo łatwiej znaleźć miejsce na zbindowanie naszych nowych klas.

W kolejnym artykule przybliżę Wam różne rodzaje bindingów oraz sposoby wstrzykiwania ich do klas.

Ciężko się pisze o tym po polsku 🙂 Jeśli coś jest dla Was niezrozumiałe, dawajcie znać w komentarzach, postaram się doprecyzować!

Posted in gamedev, programmingTagged czystykod, Unity, Zenject

Zobacz wpisy

Programistka po biol-chemie cz. 2 – O tym, jak było na początku studiów
DaftShot jesień 2019 – czyli poznaj naszą firmę od środka

Related Post

  • O Boiling Frogs 2020, o tym czym jest rzemiosło, o silnym postanowieniu i o tym, po co jest after party
  • Zenject – najlepsze praktyki i tipsy
  • Zenject in depth cz.3 – Bindings & Injections

1 thought on “Zenject in depth cz.2 – Contexts & Installers”

  1. Pingback: Zenject – najlepsze praktyki i tipsy – Blog Emi

Dodaj komentarz Anuluj pisanie odpowiedzi

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Copyright © AllTopGuide 2021 • Theme by OpenSumo