Unity3D Design Patterns – State – Building a Unity3D State Machine

  • Home /
  • Unity3D
  • / Unity3D Design Patterns – State – Building a Unity3D State Machine
By Jason Weimann /May 26, 2017

Wzorce projektowe to rozwiązania wielokrotnego użytku dla powszechnych problemów spotykanych w projektach oprogramowania. Używanie dobrych wzorców projektowych w Unity może pomóc utrzymać projekt w czystości, utrzymać go w dobrym stanie i łatwo go rozszerzyć. Sprawia to również, że są one łatwiejsze do zbudowania… Dzisiaj zajmę się wzorcem stanu i podstawami budowy maszyny stanowej w Unity3D.

Wzorzec stanu jest behawioralnym wzorcem projektowym oprogramowania, który implementuje maszynę stanów w sposób zorientowany obiektowo. … Ten wzorzec jest używany w programowaniu komputerowym do enkapsulacji różnych zachowań dla tego samego obiektu w oparciu o jego stan wewnętrzny.

Wdrożenie, którego nie lubię…

Istnieje kilka sposobów na implementację wzorca stanu w Unity. Jedną z najczęstszych metod, jakie widzę, jest użycie instrukcji switch. Ogólnie wygląda to tak.

Problemy

Choć technicznie to działa, istnieją pewne poważne problemy, które pojawią się z czasem.

Po pierwsze, cała nasza logika jest teraz wewnątrz tej pojedynczej klasy. Atakowanie, obrona, powrót do domu i każdy inny stan, który chcemy stworzyć, muszą być dodane tutaj. Doprowadzi to do powstania dużej, rozdętej klasy głównej dla naszej AI. Sprawi to również, że nasz kod będzie bardziej podatny na błędy, ponieważ będziemy edytować tę samą klasę dla każdego typu logiki. Niedługo ta klasa może łatwo zamienić się w ponad 1000 liniową katastrofę.

Nie ma też dobrego sposobu na radzenie sobie ze zdarzeniami, które dzieją się podczas wchodzenia i wychodzenia ze stanu. Dość często chcemy, aby postać lub obiekt zrobił coś, gdy wchodzi w stan, powtórzył czynność, gdy jest w tym stanie, i zrobił coś zupełnie innego, gdy opuszcza stan.

W takim układzie musielibyśmy dodać więcej przełączników dla wchodzenia i wychodzenia ze stanu, co jeszcze bardziej rozdęłoby klasę i sprawiło, że wszystko byłoby trudniejsze do zrozumienia.

Najlepsza implementacja

Najlepszym podejściem jest posiadanie każdego stanu jako swojej własnej klasy, generalnie z bazową klasą 'state’, z której dziedziczą.

W ten sposób postać lub obiekt potrzebuje tylko referencji do swojego aktualnego stanu i wywołania, aby ten stan zaktualizować. Kiedy chcemy dodać nowy stan, po prostu tworzymy nową klasę.

Używając tej metody, nasza klasa postaci wygląda nieco inaczej:

Możesz zauważyć, że klasy są mniej więcej tej samej wielkości… ale to tylko dlatego, że pierwsza wersja właściwie nic nie robi (głównie po to, aby zachować strawność). Gdyby ta pierwsza klasa robiła wszystko to, co system, który zamierzam ci pokazać, byłaby ogromna.

Jak to działa?

Jeśli spojrzysz na metodę Update, zobaczysz, że po prostu wywołuje ona metodę Tick() na bieżącym stanie. Dzięki temu wywołaniu, stan kontroluje to, co postać będzie robić w każdej klatce.

Mamy również metodę SetState, która pozwala nam przekazać stan, w który chcemy, aby postać weszła. Wywołując SetState i przekazując różne stany, możemy całkowicie zmienić zachowanie tej postaci, bez zmiany czegokolwiek w samej klasie postaci.

W zależności od konfiguracji, może być wskazane, aby zachować stany i przełączać się między nimi, aby uniknąć zbierania śmieci. Aby było to łatwe do zrozumienia, pominąłem to, ale warto mieć to na uwadze, jeśli zmiany stanów są szybkie i jesteś na platformie, gdzie ma to znaczenie.

Co to jest klasa State?

Klasa State jest abstrakcyjną klasą bazową, której będziemy używać dla wszystkich stanów, które tworzymy. Definiuje ona bardzo podstawowy kontrakt na to, co stan musi zaimplementować i inne rzeczy, które może opcjonalnie zaimplementować.

Referencja znaku jest również dodana do tej klasy bazowej, ponieważ wszystkie nasze stany w tym przykładzie będą w jakiś sposób interfejsować ze znakiem. Pobieramy znak w konstruktorze naszej klasy stanów i przechowujemy go w zmiennej chronionej, aby był dostępny dla naszych klas stanów.

Mamy również abstrakcyjną metodę Tick(). Uczynienie jej abstrakcyjną oznacza, że nasze klasy stanów są zmuszone do jej implementacji.

W przeciwieństwie do tego, mamy dwie wirtualne metody dla OnStateEnter() & OnStateExit(), które są opcjonalne. Ponieważ są one wirtualne, mają domyślną implementację, która nic nie robi i nie muszą być zaimplementowane.

To jest cała klasa stanów… całkiem proste prawda?

Tworzenie stanu – Return Home

Teraz, gdy mamy już podstawy, stwórzmy implementację stanu, który możemy podłączyć do naszej postaci.

Stan Return Home po prostu sprawia, że postać wraca do swojej domowej lokalizacji. Ponieważ tak naprawdę nie definiujemy miejsca docelowego dla home, jest to domyślny Vector3.zero.

Tick

Metoda Tick mówi postaci, aby MoveToward skierowała się do miejsca docelowego (0, 0, 0).

Metoda MoveToward znajduje się w wersji postaci na końcu tego postu. W prawdziwym projekcie, ten ruch byłby prawdopodobnie kontrolowany przez inny komponent, taki jak klasa 'CharacterMovement’ lub coś innego.

Potem sprawdza, czy postać dotarła do domu, a jeśli tak, to mówi postaci, aby przeszła w stan 'wędrówki’. Zauważ, że przekazujemy postać podczas tworzenia nowego stanu. Jest to spowodowane tym, że nasz stan jest zaprojektowany tak, aby wymagał postaci.

EnterState

Zmieniamy również kolor postaci, gdy stan powrotu do domu jest wprowadzany poprzez OnStateEnter. Więc gdy postać wraca do domu, jest niebieska.

Tworzenie innego stanu – Wander

Aby maszyna stanów miała sens, potrzebujemy więcej niż jednego stanu. W rzeczywistości, nasz stan powrotu do domu chce przełączyć postać do stanu wędrówki, więc musimy stworzyć stan wędrówki.

Stan wędrówki wybierze losowe miejsce i przesunie postać w kierunku tego miejsca. Gdy osiągnie tę pozycję, wybierze kolejne miejsce, do którego będzie wędrować.

Będzie to robić do momentu, gdy będzie wędrować przez 5 sekund (w oparciu o naszą zmienną wanderTime). W tym momencie powie postaci, aby wróciła do stanu ReturnHome (linia 43).

Stan wędrówki zmienia również kolor naszej postaci na zielony poprzez metodę OnStateEnter.

Zobaczmy jak to wygląda

Tutaj stworzyłem scenę z tuzinem 'postaci’ reprezentowanych jako sześciany.

Zobaczysz, jak wędrują, gdy są zielone, a następnie wracają do domu, gdy są niebieskie, i powtarzają ten proces.

Pełna postać

Wspomniałem powyżej, że pełna klasa postaci jest tutaj z metodą MoveToward. Ponownie, w prawdziwym projekcie, zalecałbym zachowanie podstawowego ruchu oddzielonego od innego komponentu, ale aby zachować prostotę, jest on tutaj w klasie Character.

Wnioski

Wzorzec stanu jest niesamowicie potężny. Możesz budować systemy AI, menu i wiele innych z łatwością, gdy już będziesz się czuł komfortowo z tym wzorcem.

Ta maszyna stanów jest również bardzo podstawowa i może być rozszerzona o takie rzeczy jak buforowanie stanów (aby uniknąć nowej alokacji pamięci) lub bardziej dostosowana do twoich potrzeb.

Innym świetnym sposobem na budowanie maszyn stanów jest użycie obiektów skryptowych. Maszyna stanów skonfigurowana przy użyciu scriptableobjects może być łatwo skonfigurowana w edytorze/inspektorze przez projektantów bez dotykania kodu, ale to trochę bardziej skomplikowany temat, w który mogę się zagłębić później.

Następnym razem, gdy będziesz potrzebował zbudować trochę logiki w grze, spróbuj skonfigurować własną maszynę stanów (lub skopiuj tę jako punkt startowy), jestem pewien, że to pomoże i będziesz cieszyć się nowym wzorcem.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.