Unity3D Design Patterns – State – Building a Unity3D State Machine

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

Ontwerppatronen zijn herbruikbare oplossingen voor veelvoorkomende problemen die in softwareprojecten voorkomen. Het gebruik van goede ontwerppatronen in Unity kan helpen om uw project schoon, onderhoudbaar en gemakkelijk uit te breiden te houden. Het maakt het ook makkelijker om te bouwen… Vandaag behandel ik het staatspatroon en de basisprincipes van het bouwen van een toestandsmachine in Unity3D.

Het staatspatroon is een gedragsgericht softwareontwerppatroon waarmee een toestandsmachine op een objectgeoriënteerde manier wordt geïmplementeerd. … Dit patroon wordt gebruikt in computerprogrammering om variërend gedrag voor hetzelfde object in te kapselen op basis van zijn interne toestand.

Een implementatie die ik niet leuk vind..

Er zijn een paar manieren waarop je een toestandspatroon in Unity kunt implementeren. Een van de meest voorkomende methoden die ik zie, is het gebruik van de switch-instructie. Het ziet er over het algemeen ongeveer zo uit.

Problemen

Terwijl dit technisch werkt, zijn er een aantal ernstige problemen die na verloop van tijd zullen ontstaan.

Voreerst, al onze logica is nu binnen deze enkele klasse. Aanvallen, verdedigen, thuiskomen, en elke andere toestand die we willen creëren moeten allemaal hier worden toegevoegd. Dit zal leiden tot een grote opgeblazen hoofdklasse voor onze AI. Het zal ook de kans op fouten in onze code vergroten, omdat we dezelfde klasse bewerken voor elk type logica. Al snel kan deze klasse uitgroeien tot een ramp van meer dan 1000 regels.

Er is ook geen goede manier om om te gaan met gebeurtenissen die plaatsvinden bij het binnenkomen en verlaten van een status. Vaak willen we dat een karakter of object iets doet als het een toestand binnengaat, een actie herhaalt terwijl het in die toestand is, en iets totaal anders doet als het de toestand verlaat.

Met deze opzet zouden we meer schakelaars moeten toevoegen voor het binnengaan en verlaten van de toestand, waardoor de klasse nog groter wordt en alles moeilijker te begrijpen wordt.

Een betere implementatie

Een betere aanpak is om elke toestand zijn eigen klasse te laten zijn, in het algemeen met een basis ’toestand’ klasse waar ze van erven.

Op deze manier hoeft het personage of object alleen maar een verwijzing naar zijn huidige toestand te hebben en een oproep om die toestand bij te werken. Als we een nieuwe status willen toevoegen, maken we gewoon een nieuwe klasse.

Met deze methode ziet onze tekenklasse er een beetje anders uit:

Het valt je misschien op dat de klassen ongeveer even groot zijn… maar dat is alleen omdat de eerste versie eigenlijk niets doet (vooral om het verteerbaar te houden). Als die eerste klasse alles zou doen wat het systeem dat ik je ga laten zien zou doen, zou het gigantisch zijn.

Hoe werkt het?

Als je naar de Update-methode kijkt, zie je dat deze eenvoudig de Tick()-methode aanroept voor de huidige status. Met die oproep bepaalt de toestand wat het personage elk frame zal doen.

We hebben ook een SetState methode die ons in staat stelt om een toestand door te geven die we willen dat het personage ingaat. Door SetState aan te roepen en verschillende toestanden door te geven, kunnen we het gedrag van dit personage volledig veranderen, zonder iets in de personageklasse zelf te veranderen.

Afhankelijk van je opstelling, kan het raadzaam zijn om toestanden te behouden en ertussen te schakelen om garbage collection te vermijden. Om het begrijpelijk te houden, heb ik dit overgeslagen, maar het is de moeite waard om in gedachten te houden als je snel van toestand verandert en je op een platform zit waar dit van belang is.

Wat is de staatsklasse?

De staatsklasse is een abstracte basisklasse die we zullen gebruiken voor alle toestanden die we maken. Het definieert een basiscontract voor wat een staat moet implementeren en andere dingen die het optioneel kan implementeren.

De tekenreferentie is ook toegevoegd aan deze basisklasse, omdat al onze staten in dit voorbeeld op de een of andere manier een interface zullen hebben met een teken. We nemen het karakter in de constructor van onze staat klasse en houden het in een beschermde variabele, zodat het beschikbaar is voor onze staat klassen.

We hebben ook de abstracte Tick() methode. Door deze abstract te maken, worden onze toestandsklassen gedwongen deze te implementeren.

In tegenstelling daarmee hebben we twee virtuele methoden voor OnStateEnter() & OnStateExit() die optioneel zijn. Omdat ze virtueel zijn, hebben ze een standaard implementatie die niets doet, en zijn ze niet verplicht om geïmplementeerd te worden.

Dat is alles van de state class… vrij simpel toch?

Een state maken – Return Home

Nu we de basis hebben, laten we een implementatie van een state maken die we aan ons karakter kunnen koppelen.

De return home state zorgt er simpelweg voor dat het karakter terugkeert naar zijn thuis-locatie. Omdat we niet echt de bestemming voor thuis definiëren, is het de standaard Vector3.zero.

Tick

De Tick methode vertelt het karakter om MoveToward naar de bestemming (0, 0, 0).

De MoveToward methode staat op een versie van het karakter aan het eind van deze post. In een echt project zou deze beweging waarschijnlijk worden bestuurd door een andere component, zoals een ‘CharacterMovement’-klasse of iets anders.

Dan wordt gecontroleerd of het personage thuis is gekomen, en zo ja, dan wordt het personage verteld dat het moet overschakelen naar een ‘zwerftoestand’. Merk op dat we het karakter doorgeven bij het creëren van de nieuwe status. Dit is omdat onze status is ontworpen om een karakter te vereisen.

EnterState

We veranderen ook de kleur van het karakter wanneer de ‘return home’ status wordt ingevoerd via OnStateEnter. Dus als een karakter naar huis gaat, is het blauw.

Een andere status maken – Wander

Om een toestandsmachine zinvol te laten zijn, hebben we meer dan één toestand nodig. In feite wil onze return home state het personage in een wander state zetten als het klaar is, dus moeten we die wander state maken.

De wander state zal een willekeurige plaats kiezen en het personage naar die positie toe bewegen. Wanneer het die positie bereikt, zal het een andere plek kiezen om naar toe te dwalen.

Het zal dit blijven doen totdat het gedurende 5 seconden heeft gedwaald (gebaseerd op onze wanderTime variabele). Op dat moment zal het vertellen het karakter om terug te gaan naar een ReturnHome staat (lijn 43).

De dwaaltoestand verandert ook de kleur van ons karakter naar groen via de OnStateEnter methode.

Laten we eens kijken hoe het eruit ziet

Hier, ik heb een scène gemaakt met een dozijn van de ‘personages’ vertegenwoordigd als kubussen.

Je ziet ze rondlopen als ze groen zijn, dan weer naar huis gaan als ze blauw zijn, en het proces herhalen.

Het volledige personage

Ik zei hierboven dat de volledige personageklasse hier is met de methode MoveToward. Nogmaals, in een echt project zou ik aanraden om de basisbeweging in een andere component onder te brengen, maar om het eenvoudig te houden, staat het hier in de Character-klasse.

Conclusies

Het state-patroon is verbazingwekkend krachtig. Je kunt AI-systemen, menu’s en meer met gemak bouwen als je eenmaal vertrouwd bent met dit patroon.

Deze toestandsmachine is ook heel eenvoudig en kan worden uitgebreid om dingen te doen zoals caching toestanden (om nieuwe geheugentoewijzingen te vermijden) of meer aangepast aan uw behoeften.

Een andere geweldige manier om toestandsmachines te bouwen is via scriptableobjects. Een toestandsmachine die is opgezet met behulp van scriptableobjects kan gemakkelijk worden geconfigureerd in de editor/inspector door ontwerpers zonder ooit code aan te raken, maar dat is een wat ingewikkelder onderwerp waar ik later misschien in duik.

De volgende keer dat je een beetje spellogica moet bouwen, probeer dan zelf een toestandsmachine op te zetten (of kopieer deze als uitgangspunt), ik weet zeker dat het zal helpen en dat je zult genieten van het nieuwe patroon.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.