Jakiś czas temu zacząłem pracować z Unity i nadal mam problem z ściśle powiązanymi skryptami. Jak mogę uporządkować kod, aby uniknąć tego problemu?
Na przykład:
Chcę mieć systemy zdrowia i śmierci w osobnych skryptach. Chcę też mieć różne wymienne skrypty chodzenia, które pozwalają mi zmieniać sposób poruszania się postaci gracza (oparte na fizyce, bezwładnościowe elementy sterujące, takie jak w Mario, w porównaniu do ścisłych, drżących elementów sterujących, takich jak w Super Meat Boy). Skrypt zdrowia musi zawierać odniesienie do skryptu śmierci, aby mógł uruchomić metodę Die (), gdy zdrowie graczy osiągnie 0. Skrypt śmierci powinien zawierać odniesienie do użytego skryptu chodzenia, aby wyłączyć chodzenie po śmierci (I mam dość zombie).
Chciałbym normalnie tworzyć interfejsy, jak IWalking
, IHealth
i IDeath
, tak, że można zmienić te elementy na kaprysu bez rozbijania resztę kodu. Powiedzmy, że ustawiłbym je osobnym skryptem na obiekcie odtwarzacza PlayerScriptDependancyInjector
. Może to skrypt musiałby publicznych IWalking
, IHealth
oraz IDeath
atrybuty, tak że zależności może być ustawiona przez projektanta z poziomu inspektora przez przeciąganie i upuszczanie odpowiednich skryptów.
To pozwoliłoby mi po prostu łatwo dodawać zachowania do obiektów gry i nie martwić się o mocno zakodowane zależności.
Problem w jedności
Problemem jest to, że w jedności nie mogę wystawiać interfejsów w inspektora, a jeśli piszę własnych inspektorów, odniesienia przyzwyczajenie się w odcinkach, a to dużo niepotrzebnej pracy. Dlatego pozostało mi pisanie ściśle powiązanego kodu. Mój Death
skrypt ujawnia odniesienie do InertiveWalking
skryptu. Ale jeśli zdecyduję, że chcę, aby postać gracza ściśle kontrolowała, nie mogę po prostu przeciągnąć i upuścić TightWalking
skryptu, muszę go zmienić Death
. To jest do bani. Poradzę sobie z tym, ale moja dusza płacze za każdym razem, gdy robię coś takiego.
Jaka jest preferowana alternatywa dla interfejsów w Unity? Jak rozwiązać ten problem? Znalazłem to , ale mówi mi to, co już wiem, i nie mówi mi, jak to zrobić w Jedności! To również omawia, co należy zrobić, a nie jak, nie rozwiązuje problemu ścisłego łączenia skryptów.
Podsumowując, uważam, że zostały napisane dla osób, które przyszły do Unity z doświadczeniem w projektowaniu gier, które po prostu uczą się kodować, a dla Unity jest bardzo mało zasobów dla zwykłych programistów. Czy istnieje jakiś standardowy sposób na uporządkowanie kodu w Unity, czy też muszę wymyślić własną metodę?
The problem is that in Unity I can't expose interfaces in the inspector
Nie sądzę, że rozumiem, co masz na myśli przez „ujawnienie interfejsu w inspektorze”, ponieważ moją pierwszą myślą było „dlaczego nie?”Odpowiedzi:
Istnieje kilka sposobów pracy, aby uniknąć ścisłego łączenia skryptów. Internal to Unity to funkcja SendMessage, która po skierowaniu na obiekt GameObject Monobehaviour wysyła tę wiadomość do wszystkiego w obiekcie gry. Więc możesz mieć coś takiego w swoim obiekcie zdrowia:
To jest naprawdę uproszczone, ale powinno dokładnie pokazać, o co mi chodzi. Właściwość rzucająca wiadomość jak zdarzenie. Twój skrypt śmierci przechwyciłby następnie tę wiadomość (a jeśli nie ma nic, co mogłoby ją przechwycić, SendMessageOptions.DontRequireReceiver zagwarantuje, że nie otrzymasz wyjątku) i wykonał działania w oparciu o nową wartość zdrowia po jej ustawieniu. Tak jak:
Nie ma w tym jednak żadnej gwarancji porządku. Z mojego doświadczenia wynika, że zawsze przebiega od najwyższego skryptu GameObject do najniższego skryptu, ale nie opierałbym się na niczym, czego sam nie mógłbyś zaobserwować.
Istnieją inne rozwiązania, które można zbadać, które budują niestandardową funkcję GameObject, która pobiera Komponenty i zwraca tylko te komponenty, które mają określony interfejs zamiast Typu, zajęłoby to trochę dłużej, ale mogłoby dobrze służyć twoim celom.
Dodatkowo możesz napisać niestandardowy edytor, który sprawdza obiekt przypisany do pozycji w edytorze dla tego interfejsu przed faktycznym wykonaniem przypisania (w tym przypadku skrypt byłby przypisany normalnie, umożliwiając serializację, ale generowanie błędu jeśli nie używał właściwego interfejsu i odmawiał przypisania). Zrobiłem już oba z nich i mogę wygenerować ten kod, ale najpierw potrzebuję trochę czasu, aby go znaleźć.
źródło
Ja osobiście NIGDY nie korzystam z SendMessage. Nadal istnieje zależność między twoimi komponentami dzięki SendMessage, jest ona bardzo słabo wyświetlana i łatwa do złamania. Korzystanie z interfejsów i / lub delegatów naprawdę eliminuje potrzebę korzystania z SendMessage w dowolnym momencie (co jest wolniejsze, chociaż nie powinno to stanowić problemu, dopóki nie będzie konieczne).
http://forum.unity3d.com/threads/free-vfw-full-set-of-drawers-savesystem-serialize-interfaces-generics-auto-props-delegates.266165/
Użyj rzeczy tego faceta. Zapewnia mnóstwo edytorów, takich jak odsłonięte interfejsy ORAZ delegaci. Istnieje mnóstwo takich rzeczy, a nawet możesz stworzyć własne. Cokolwiek robisz, NIE kompromituj swojego projektu ze względu na brak obsługi skryptów Unity. Te rzeczy powinny być domyślnie w Jedności.
Jeśli nie jest to opcja, przeciągnij i upuść klasy monobehaviour zamiast tego i dynamicznie rzutuj je na wymagany interfejs. To więcej pracy i mniej eleganckie, ale wykonuje pracę.
źródło
Podejście specyficzne dla Jedności, które przychodzi mi do głowy, to najpierw
GetComponents<MonoBehaviour>()
uzyskać listę skryptów, a następnie rzutować te skrypty na zmienne prywatne dla określonych interfejsów. Chciałbyś to zrobić w Start () na skrypcie inżektora zależności. Coś jak:W rzeczywistości dzięki tej metodzie można nawet kazać poszczególnym komponentom powiedzieć skryptowi wstrzykiwania zależności, jakie są ich zależności, za pomocą polecenia typeof () i typu „Type” . Innymi słowy, komponenty przekazują iniektorowi zależności listę typów, a następnie iniektor zależności zwraca listę obiektów tych typów.
Alternatywnie, możesz po prostu użyć klas abstrakcyjnych zamiast interfejsów. Klasa abstrakcyjna może osiągnąć prawie ten sam cel, co interfejs i można do niej odwoływać się w Inspektorze.
źródło
GetComponent<IMyInterface>()
iGetComponents<IMyInterface>()
bezpośrednio whch powraca składnik (ów) Realizuje.Z własnego doświadczenia twórcy gier tradycyjnie wymagają bardziej pragmatycznego podejścia niż twórcy przemysłowi (z mniejszą ilością warstw abstrakcji). Częściowo dlatego, że chcesz przedkładać wydajność nad łatwość konserwacji, a także dlatego, że mniej prawdopodobne jest ponowne użycie kodu w innych kontekstach (zwykle istnieje silne wewnętrzne powiązanie między grafiką a logiką gry)
Całkowicie rozumiem twoje obawy, szczególnie dlatego, że problemy z wydajnością są mniej krytyczne w przypadku najnowszych urządzeń, ale czuję, że będziesz musiał opracować własne rozwiązania, jeśli chcesz zastosować najlepsze praktyki OOP w rozwoju gier Unity (takie jak wstrzykiwanie zależności, itp...).
źródło
Spójrz na IUnified ( http://u3d.as/content/wounded-wolf/iunified/5H1 ), który pozwala ujawniać interfejsy w edytorze, tak jak wspomniałeś. Jest trochę więcej do zrobienia w porównaniu do normalnej deklaracji zmiennych, to wszystko.
Ponadto, jak wspomniano w innych odpowiedziach, użycie SendMessage nie usuwa sprzężenia. Zamiast tego sprawia, że nie jest to błąd podczas kompilacji. Bardzo źle - w miarę zwiększania skali projektu utrzymanie go może stać się koszmarem.
Jeśli chcesz rozdzielić kod bez korzystania z interfejsu w edytorze, możesz użyć silnie typowanego systemu opartego na zdarzeniach (lub nawet luźno typowanego, ale trudniejszego w utrzymaniu). Oparłem mój na tym poście , co jest bardzo wygodne jako punkt wyjścia. Pamiętaj, aby stworzyć mnóstwo wydarzeń, ponieważ mogą one wywołać GC. Zamiast tego obejrzałem problem z pulą obiektów, ale głównie dlatego, że pracuję z urządzeniami mobilnymi.
źródło
Źródło: http://www.udellgames.com/posts/ultra-useful-unity-snippet-developers-use-interfaces/
źródło
Używam kilku różnych strategii, aby ominąć wspomniane ograniczenia.
Jednym z tych, o których nie widzę, jest użycie
MonoBehavior
które udostępnia dostęp do różnych implementacji danego interfejsu. Na przykład mam interfejs, który określa sposób implementacji nazw RaycastIRaycastStrategy
I następnie wdrożyć go w różnych klas z klas, jak
LinecastRaycastStrategy
iSphereRaycastStrategy
i realizuje MonoBehaviorRaycastStrategySelector
że odsłania jedno wystąpienie każdego typu. Coś podobnegoRaycastStrategySelectionBehavior
, co jest realizowane coś takiego:Jeśli implementacje prawdopodobnie odwołują się do funkcji Unity w swoich konstrukcjach (lub po prostu są po bezpiecznej stronie, po prostu zapomniałem o tym podczas pisania tego przykładu) używaj,
Awake
aby uniknąć problemów z wywoływaniem konstruktorów lub odwoływaniem się do funkcji Unity w edytorze lub poza głównym wątekTa strategia ma pewne wady, szczególnie gdy trzeba zarządzać wieloma różnymi implementacjami, które szybko się zmieniają. Problemy z brakiem sprawdzania czasu kompilacji można rozwiązać, korzystając z edytora niestandardowego, ponieważ wartość wyliczenia może być serializowana bez specjalnej pracy (chociaż zwykle rezygnuję z tego, ponieważ moje implementacje nie są tak liczne).
Korzystam z tej strategii ze względu na elastyczność, jaką zapewnia, bazując na MonoBehaviors bez faktycznego zmuszania do implementacji interfejsów przy użyciu MonoBehaviors. To daje „najlepsze z obu światów”, ponieważ dostęp do Selektora można uzyskać za pomocą
GetComponent
, serializacja jest obsługiwana przez Unity, a Selektor może zastosować logikę w czasie wykonywania, aby automatycznie przełączać implementacje na podstawie pewnych czynników.źródło