Unity3D Design Patterns – State – Building a Unity3D State Machine

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

I design pattern sono soluzioni riutilizzabili a problemi comuni che si trovano nei progetti software. Usare buoni design pattern in Unity può aiutare a mantenere il tuo progetto pulito, mantenibile e facile da estendere. Li rende anche più facili da costruire. Oggi, coprirò lo State pattern e le basi della costruzione di una macchina a stati in Unity3D.

Lo state pattern è un pattern di progettazione software comportamentale che implementa una macchina a stati in un modo orientato agli oggetti. … Questo pattern è usato nella programmazione dei computer per incapsulare comportamenti diversi per lo stesso oggetto in base al suo stato interno.

Un’implementazione che non mi piace..

Ci sono alcuni modi per implementare un pattern di stato in Unity. Uno dei metodi comuni che vedo è fatto usando l’istruzione switch. In genere assomiglia a questo.

Problemi

Mentre questo tecnicamente funziona, ci sono alcuni seri problemi che sorgeranno nel tempo.

In primo luogo, tutta la nostra logica è ora dentro questa singola classe. Attaccare, difendere, tornare a casa e qualsiasi altro stato che vogliamo creare deve essere aggiunto qui. Questo porterà ad una grande classe principale gonfiata per la nostra IA. Renderà anche il nostro codice più probabile che si rompa poiché stiamo modificando la stessa classe per ogni tipo di logica. Tra non molto, questa classe potrebbe facilmente trasformarsi in un disastro di più di 1000 linee.

Non c’è anche un gran modo di trattare gli eventi che accadono all’entrata e all’uscita di uno stato. Molto spesso, vogliamo che un personaggio o un oggetto faccia qualcosa quando entra in uno stato, ripeta un’azione mentre è in quello stato, e faccia qualcosa di totalmente diverso quando lascia lo stato.

Con questa configurazione, dovremmo aggiungere più interruttori per entrare e uscire dallo stato, gonfiando ulteriormente la classe e rendendo tutto più difficile da capire.

Un’implementazione migliore

Un approccio migliore è quello di avere ogni stato come una propria classe, generalmente con una classe ‘stato’ di base da cui ereditare.

In questo modo, il personaggio o l’oggetto ha solo bisogno di avere un riferimento al suo stato attuale e una chiamata per aggiornare quello stato. Quando vogliamo aggiungere un nuovo stato, creiamo semplicemente una nuova classe.

Utilizzando questo metodo, la nostra classe personaggio appare un po’ diversa:

Si può notare che le classi hanno circa la stessa dimensione… ma questo è solo perché la prima versione non fa effettivamente nulla (principalmente per mantenerla digeribile). Se la prima classe facesse tutto quello che fa il sistema che sto per mostrarvi, sarebbe enorme.

Come funziona?

Se guardate il metodo Update, vedrete che chiama semplicemente il metodo Tick() sullo stato corrente. Con questa chiamata, lo stato controlla cosa farà il personaggio ad ogni fotogramma.

Abbiamo anche un metodo SetState che ci permette di passare uno stato in cui vogliamo che il personaggio entri. Chiamando SetState e passando diversi stati, possiamo cambiare completamente il comportamento di questo personaggio, senza cambiare nulla nella classe del personaggio stesso.

A seconda della vostra configurazione, potrebbe essere consigliabile mantenere gli stati e passare da uno all’altro per evitare la garbage collection. Per mantenerlo facile da capire, l’ho saltato, ma vale la pena tenerlo a mente se i tuoi cambiamenti di stato sono veloci e se sei su una piattaforma in cui conta.

Cos’è la classe State?

La classe state è una classe base astratta che useremo per tutti gli stati che creiamo. Definisce un contratto molto basilare per ciò che uno stato deve implementare e altre cose che può opzionalmente implementare.

Il riferimento al carattere è anche aggiunto a questa classe base dello stato, perché tutti i nostri stati in questo esempio si interfacceranno con un carattere in qualche modo. Prendiamo il carattere nel costruttore della nostra classe stato e lo teniamo in una variabile protetta in modo che sia disponibile alle nostre classi stato.

Abbiamo anche il metodo astratto Tick(). Renderlo astratto significa che le nostre classi di stato sono costrette ad implementarlo.

Al contrario, abbiamo due metodi virtuali per OnStateEnter() & OnStateExit() che sono opzionali. Poiché sono virtuali, hanno un’implementazione predefinita che non fa nulla, e non è necessario implementarli.

Questa è la totalità della classe state… abbastanza semplice, vero?

Creazione di uno stato – Return Home

Ora che abbiamo le basi, creiamo un’implementazione di uno stato che possiamo collegare al nostro personaggio.

Lo stato return home fa semplicemente tornare il personaggio alla sua posizione di casa. Dato che non stiamo definendo la destinazione per casa, è il Vector3.zero di default.

Tick

Il metodo Tick dice al personaggio di muoversi verso la destinazione (0, 0, 0).

Il metodo MoveToward è su una versione del personaggio alla fine di questo post. In un progetto reale, questo movimento sarebbe probabilmente controllato da un altro componente come una classe ‘CharacterMovement’ o qualcos’altro.

Poi controlla se il personaggio è arrivato a casa, e se è così dice al personaggio di passare ad uno stato ‘wander’. Notate che passiamo il personaggio quando creiamo il nuovo stato. Questo perché il nostro stato è progettato per richiedere un personaggio.

EnterState

Cambiamo anche il colore del personaggio quando lo stato di ritorno a casa viene inserito tramite OnStateEnter. Così quando un personaggio sta andando a casa, è blu.

Creazione di un altro stato – Wander

Perché una macchina a stati abbia senso, abbiamo bisogno di più di uno stato. Infatti, il nostro stato return home vuole effettivamente passare il personaggio ad uno stato wander una volta che ha finito, quindi abbiamo bisogno di creare questo stato wander.

Lo stato wander sceglierà una posizione casuale e muoverà il personaggio verso quella posizione. Quando raggiunge quella posizione, sceglierà un altro punto in cui vagare.

Continuerà a fare questo finché non avrà vagato per 5 secondi (in base alla nostra variabile wanderTime). A quel punto, dirà al personaggio di tornare allo stato ReturnHome (linea 43).

Lo stato wander cambia anche il colore del nostro personaggio in verde attraverso il metodo OnStateEnter.

Vediamo come appare

Qui, ho creato una scena con una dozzina di ‘personaggi’ rappresentati come cubi.

Li vedrete gironzolare finché sono verdi, poi tornare a casa quando sono blu, e ripetere il processo.

Il personaggio completo

Ho detto sopra che la classe del personaggio completo è qui con il metodo MoveToward. Di nuovo, in un progetto reale, raccomanderei di tenere il movimento di base separato in un altro componente, ma per mantenere questo semplice, eccolo nella classe Character.

Conclusioni

Il modello di stato è incredibilmente potente. Puoi costruire sistemi AI, menu e molto altro con facilità una volta che hai preso confidenza con questo pattern.

Questa macchina a stati è anche molto semplice e può essere estesa per fare cose come il caching degli stati (per evitare nuove allocazioni di memoria) o personalizzata in base alle tue esigenze.

Un altro ottimo modo per costruire macchine a stati è tramite scriptableobjects. Una macchina a stati impostata usando scriptableobjects può essere facilmente configurata nell’editor/inspector dai designer senza mai toccare il codice, ma questo è un argomento un po’ più complicato che potrei approfondire più avanti.

La prossima volta che hai bisogno di costruire un po’ di logica di gioco, prova a impostare una tua macchina a stati (o copia questa come punto di partenza), sono sicuro che ti aiuterà e ti piacerà il nuovo pattern.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.