Unity3D Design Patterns – State – Building a Unity3D State Machine

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

Designmönster är återanvändbara lösningar på vanliga problem som förekommer i mjukvaruprojekt. Genom att använda bra designmönster i Unity kan du hjälpa till att hålla ditt projekt rent, underhållbart och lätt att utöka. Det gör dem också lättare att bygga. Idag ska jag täcka State-mönstret och grunderna för att bygga en State Machine i Unity3D.

State-mönstret är ett beteendemässigt mjukvarudesignmönster som implementerar en state machine på ett objektorienterat sätt. … Det här mönstret används inom datorprogrammering för att kapsla in varierande beteende för samma objekt baserat på dess interna tillstånd.

En implementering som jag inte gillar…

Det finns några sätt att implementera ett tillståndsmönster i Unity. En av de vanligaste metoderna jag ser görs genom att använda switch statement. Det ser i allmänhet ut ungefär så här.

Problem

Men även om detta tekniskt sett fungerar finns det några allvarliga problem som kommer att uppstå med tiden.

För det första är all vår logik nu inne i denna enda klass. Att attackera, försvara, återvända hem och alla andra tillstånd som vi vill skapa måste alla läggas till här. Detta kommer att leda till en stor uppblåst huvudklass för vår AI. Det kommer också att göra vår kod mer benägen att gå sönder eftersom vi redigerar samma klass för varje typ av logik. Inom kort kan den här klassen lätt förvandlas till en katastrof med över 1000 rader.

Det finns inte heller något bra sätt att hantera händelser som inträffar när ett tillstånd går in och ut. Ganska ofta vill vi att en karaktär eller ett objekt ska göra något när den går in i ett tillstånd, upprepa en handling medan den är i det tillståndet och göra något helt annat när den lämnar tillståndet.

Med den här uppläggningen skulle vi behöva lägga till fler switchar för att gå in i och ut ur tillståndet, vilket skulle ytterligare fylla klassen och göra allting svårare att förstå.

En bättre implementering

En bättre metod är att låta varje tillstånd vara en egen klass, i allmänhet med en basklass ”state” som de ärver från.

På så sätt behöver karaktären eller objektet bara ha en referens till sitt nuvarande tillstånd och ett anrop för att göra en uppdatering av detta tillstånd. När vi vill lägga till ett nytt tillstånd skapar vi bara en ny klass.

Med den här metoden ser vår karaktärsklass lite annorlunda ut:

Du kanske märker att klasserna är ungefär lika stora… men det beror bara på att den första versionen faktiskt inte gör något (främst för att hålla den lättsmält). Om den första klassen skulle göra allt som systemet jag ska visa dig gjorde skulle den vara enorm.

Hur fungerar det?

Om du tittar på Update-metoden ser du att den helt enkelt anropar Tick()-metoden på det aktuella tillståndet. Med det anropet styr tillståndet vad karaktären kommer att göra varje bildruta.

Vi har också en SetState-metod som gör att vi kan skicka in ett tillstånd som vi vill att karaktären ska gå in i. Genom att anropa SetState och skicka in olika tillstånd kan vi helt ändra den här karaktärens beteende, utan att ändra något i själva karaktärsklassen.

Avhängigt av din installation kan det vara tillrådligt att ha tillstånd kvar och växla mellan dem för att undvika garbage collection. För att hålla detta lättförståeligt har jag hoppat över detta, men det är värt att tänka på om dina tillståndsbyten är snabba och du är på en plattform där det spelar roll.

Vad är State-klassen?

State-klassen är en abstrakt basklass som vi kommer att använda för alla tillstånd som vi skapar. Den definierar ett mycket grundläggande kontrakt för vad ett tillstånd måste implementera och andra saker som det kan implementera valfritt.

Karakterreferensen läggs också till i den här basklassen för tillstånd, eftersom alla våra tillstånd i det här exemplet kommer att ha ett gränssnitt mot ett tecken på något sätt. Vi tar karaktären i konstruktören för vår statsklass och behåller den i en skyddad variabel så att den är tillgänglig för våra statsklasser.

Vi har också den abstrakta metoden Tick(). Att göra den abstrakt innebär att våra statsklasser är tvungna att implementera den.

Däremot har vi två virtuella metoder för OnStateEnter() & OnStateExit() som är valfria. Eftersom de är virtuella har de en standardimplementation som inte gör något och behöver inte implementeras.

Det är hela statsklassen… ganska enkelt eller hur?

Skapa ett tillstånd – Return Home

Nu när vi har grunderna, låt oss skapa en implementering av ett tillstånd som vi kan koppla till vår karaktär.

The return home state gör helt enkelt att karaktären återvänder till sin hemort. Eftersom vi faktiskt inte definierar destinationen för hem är det standard Vector3.zero.

Tick

Metoden Tick talar om för karaktären att MoveToward till destinationen (0, 0, 0).

Metoden MoveToward finns på en version av karaktären i slutet av det här inlägget. I ett riktigt projekt skulle den här rörelsen förmodligen styras av en annan komponent som en ”CharacterMovement”-klass eller något annat.

Därefter kontrollerar den om karaktären klarade sig hem, och om så är fallet talar den om för karaktären att den ska övergå till ett ”wander”-tillstånd. Lägg märke till att vi skickar in karaktären när vi skapar det nya tillståndet. Detta beror på att vårt tillstånd är utformat för att kräva en karaktär.

EnterState

Vi ändrar också färgen på karaktären när tillståndet för hemresa förs in via OnStateEnter. Så när en karaktär är på väg hem är den blå.

Skapa ett annat tillstånd – Wander

För att en tillståndsmaskin ska vara meningsfull behöver vi mer än ett tillstånd. Faktum är att vårt hemvändartillstånd faktiskt vill växla karaktären till ett vandringstillstånd när det är klart, så vi måste skapa det vandringstillståndet.

Vandringstillståndet kommer att välja en slumpmässig plats och flytta karaktären mot den positionen. När den når den positionen väljer den en annan plats att vandra till.

Den fortsätter att göra detta tills den har vandrat i 5 sekunder (baserat på vår wanderTime-variabel). Vid den tidpunkten kommer den att tala om för karaktären att gå tillbaka till ett ReturnHome-tillstånd (rad 43).

Vandringstillståndet ändrar också färgen på vår karaktär till grönt via OnStateEnter-metoden.

Vi ska se hur det ser ut

Här har jag skapat en scen med ett dussintal av ”karaktärer” representerade som kuber.

Du kommer att se dem vandra runt medan de är gröna, sedan gå hem igen när de är blå och upprepa processen.

Den fullständiga karaktären

Jag nämnde ovan att den fullständiga karaktärsklassen finns här med MoveToward-metoden. Återigen, i ett riktigt projekt skulle jag rekommendera att hålla din grundläggande rörelse separerad i en annan komponent, men för att hålla det här enkelt är det här i Character-klassen.

Slutsatser

Det statliga mönstret är otroligt kraftfullt. Du kan bygga AI-system, menyer och mycket mer med lätthet när du blir bekväm med det här mönstret.

Den här tillståndsmaskinen är också mycket grundläggande och kan utökas för att göra saker som att cacha tillstånd (för att undvika nya minnesallokeringar) eller anpassas mer till dina behov.

Ett annat bra sätt att bygga tillståndsmaskiner är via scriptableobjects. En tillståndsmaskin som är konfigurerad med hjälp av scriptableobjects kan enkelt konfigureras i editor/inspector av designers utan att någonsin röra koden, men det är ett lite mer komplicerat ämne som jag kanske dyker ner i senare.

Nästa gång du behöver bygga lite spellogik, prova att sätta upp en egen tillståndsmaskin (eller kopiera den här som utgångspunkt), jag är säker på att det kommer att hjälpa dig och att du kommer att njuta av det nya mönstret.

Lämna ett svar

Din e-postadress kommer inte publiceras.