Unity3D Design Patterns – State – Building a Unity3D State Machine

  • Főoldal /
  • Unity3D
  • / Unity3D Design Patterns – State – Building a Unity3D State Machine
By Jason Weimann /Május 26, 2017

A tervezési minták újrafelhasználható megoldások a szoftverprojektekben előforduló gyakori problémákra. A jó tervezési minták használata a Unityben segíthet tisztán, karbantarthatóan és könnyen bővíthetően tartani a projektet. Emellett megkönnyíti az építésüket is.. Ma az állapotmintával és az állapotgép építésének alapjaival foglalkozom a Unity3D-ben.

Az állapotminta egy viselkedésalapú szoftvertervezési minta, amely objektumorientált módon valósít meg egy állapotgépet. … Ezt a mintát a számítógépes programozásban arra használják, hogy ugyanannak az objektumnak a belső állapota alapján eltérő viselkedést kapszulázzanak.

Egy megvalósítás, amit nem szeretek…

Az állapotminta Unityben többféleképpen is megvalósítható. Az egyik leggyakoribb módszer, amit látok, a switch utasítás használatával történik. Ez általában valahogy így néz ki.

Problémák

Míg ez technikailag működik, van néhány komoly probléma, ami idővel felmerül.

Először is, az összes logikánk most ebben az egyetlen osztályban van. A támadás, a védekezés, a hazatérés és minden más állapot, amit létre akarunk hozni, mind ide kell kerüljön. Ez egy nagyra duzzadt mesteri osztályt fog eredményezni a mesterséges intelligenciánk számára. A kódunk is nagyobb valószínűséggel fog elromlani, mivel minden logikai típushoz ugyanazt az osztályt szerkesztjük. Hamarosan ez az osztály könnyen egy több mint 1000 soros katasztrófává válhat.

Az állapotok belépésekor és kilépésekor bekövetkező események kezelésére sincs jó megoldás. Elég gyakran azt akarjuk, hogy egy karakter vagy objektum csináljon valamit, amikor belép egy állapotba, megismételje a műveletet, amíg abban az állapotban van, és valami teljesen mást csináljon, amikor elhagyja az állapotot.

Ezzel a beállítással több kapcsolót kellene hozzáadnunk az állapotba való belépéshez és kilépéshez, ami tovább duzzasztaná az osztályt, és mindent nehezebbé tenne megérteni.

Egy jobb megvalósítás

Egy jobb megközelítés az, hogy minden állapot saját osztály, általában egy alap “állapot” osztállyal, amitől örökölnek.

Ezzel a karakter vagy objektum csak egy referenciát kell, hogy kapjon az aktuális állapotára és egy hívást, hogy az állapot frissüljön. Amikor új állapotot akarunk hozzáadni, egyszerűen létrehozunk egy új osztályt.

Ezt a módszert használva a karakterosztályunk egy kicsit másképp néz ki:

Elképzelhető, hogy az osztályok mérete nagyjából azonos… de ez csak azért van, mert az első verzió valójában nem csinál semmit (főleg, hogy emészthető maradjon). Ha az első osztály mindent megcsinálna, amit a rendszer, amit most mutatok, hatalmas lenne.

Hogyan működik?

Ha megnézed az Update metódust, láthatod, hogy egyszerűen meghívja a Tick() metódust az aktuális állapotra. Ezzel a hívással az állapot vezérli, hogy a karakter mit csináljon minden egyes képkockán.

Egy SetState metódusunk is van, amely lehetővé teszi számunkra, hogy átadjuk azt az állapotot, amelybe a karaktert szeretnénk helyezni. A SetState meghívásával és különböző állapotok átadásával teljesen megváltoztathatjuk a karakter viselkedését anélkül, hogy magában a karakterosztályban bármit is megváltoztatnánk.

A beállításoktól függően célszerű lehet állapotokat tartani és váltogatni közöttük a szemétgyűjtés elkerülése érdekében. A könnyebb érthetőség érdekében ezt kihagytam, de érdemes észben tartani, ha az állapotváltások gyorsak, és olyan platformon dolgozol, ahol ez számít.

Mi az állapotosztály?

Az állapotosztály egy absztrakt alaposztály, amelyet minden általunk létrehozott állapothoz használni fogunk. Meghatároz egy nagyon alapvető szerződést arra vonatkozóan, hogy egy állapotnak mit kell implementálnia, és egyéb dolgokat, amelyeket opcionálisan implementálhat.

A karakterreferenciát is ehhez az állapot alaposztályhoz adjuk hozzá, mert ebben a példában minden állapotunk valamilyen módon kapcsolódni fog egy karakterhez. Az állapotosztályunk konstruktorában átvesszük a karaktert, és egy védett változóban tartjuk, hogy az állapotosztályaink számára elérhető legyen.

Megvan az absztrakt Tick() metódus is. Az absztraktvá tétele azt jelenti, hogy az állapotosztályaink kénytelenek implementálni.

Ezzel szemben van két virtuális metódusunk az OnStateEnter() & OnStateExit() számára, amelyek opcionálisak. Mivel ezek virtuálisak, van egy alapértelmezett implementációjuk, ami nem csinál semmit, és nem kötelező implementálni őket.

Ez a teljes állapotosztály… elég egyszerű, nem?

Egy állapot létrehozása – Hazatérés

Most, hogy megvannak az alapok, hozzunk létre egy állapot implementációt, amit a karakterünkhöz kapcsolhatunk.

A hazatérés állapota egyszerűen arra készteti a karaktert, hogy visszatérjen az otthoni helyére. Mivel valójában nem határozzuk meg a home célpontját, ez az alapértelmezett Vector3.zero.

Tick

A Tick metódus azt mondja a karakternek, hogy MoveToward a célpont felé (0, 0, 0).

A MoveToward metódus a karakter egy verzióján van a bejegyzés végén. Egy valós projektben ezt a mozgást valószínűleg egy másik komponens, például egy ‘CharacterMovement’ osztály vagy valami más vezérelné.

Ezután ellenőrzi, hogy a karakter hazaért-e, és ha igen, akkor megmondja a karakternek, hogy váltson ‘vándorló’ állapotba. Vegyük észre, hogy az új állapot létrehozásakor átadjuk a karaktert. Ez azért van így, mert az állapotunk úgy van kialakítva, hogy megköveteli a karaktert.

EnterState

A hazatérési állapotba való belépéskor az OnStateEnter-en keresztül megváltoztatjuk a karakter színét is. Tehát mivel a karakter hazafelé tart, kék színű lesz.

Másik állapot létrehozása – Vándorlás

Az állapotgép értelméhez egynél több állapotra van szükségünk. Valójában a hazatérés állapotunk valójában át akarja váltani a karaktert egy vándorló állapotba, amint végzett, ezért létre kell hoznunk ezt a vándorló állapotot.

A vándorló állapot kiválaszt egy véletlenszerű helyet, és a karaktert az adott pozíció felé mozgatja. Amikor eléri azt a pozíciót, kiválaszt egy másik helyet, ahová vándorolhat.

Ezt addig fogja folytatni, amíg 5 másodpercig nem vándorolt (a wanderTime változónk alapján). Ekkor megmondja a karakternek, hogy térjen vissza a ReturnHome állapotba (43. sor).

A vándorló állapot az OnStateEnter metóduson keresztül a karakterünk színét is zöldre változtatja.

Lássuk, hogy néz ki

Itt létrehoztam egy jelenetet, amelyben egy tucat “karakter” kockaként van ábrázolva.

Láthatjuk, hogy addig bolyonganak, amíg zöld színűek, majd amikor kék színűek, ismét hazamennek, és megismétlik a folyamatot.

A teljes karakter

Fentebb említettem, hogy a teljes karakterosztály itt van a MoveToward metódussal. Ismétlem, egy valódi projektben azt javasolnám, hogy az alapvető mozgást tartsd elkülönítve egy másik komponensben, de az egyszerűség kedvéért itt van a Character osztályban.

Következtetések

Az állapotminta elképesztően erős. Könnyedén építhetsz AI rendszereket, menüket és még sok mást, ha egyszer megbarátkozol ezzel a mintával.

Ez az állapotgép is nagyon egyszerű, és bővíthető olyan dolgokkal, mint az állapotok gyorsítótárazása (az új memóriafoglalások elkerülése érdekében), vagy még inkább a saját igényeidre szabható.

Egy másik nagyszerű módja az állapotgépek építésének a szkriptelhető objektumokon keresztül. A scriptableobjects segítségével létrehozott állapotgépet a tervezők könnyen konfigurálhatják a szerkesztőben/inspektorban anélkül, hogy valaha is hozzányúlnának a kódhoz, de ez egy kicsit bonyolultabb téma, amibe később talán belemerülök.

Amikor legközelebb egy kis játéklogikát kell építened, próbálj meg létrehozni egy saját állapotgépet (vagy másold le ezt kiindulási pontként), biztos vagyok benne, hogy segíteni fog, és élvezni fogod az új mintát.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.