Le macchine a stati sono perfette per nemici semplici, ma quando i comportamenti si moltiplicano il numero di transizioni cresce in fretta e il diagramma diventa una ragnatela. I behavior tree (alberi di comportamento) offrono un'alternativa diffusa nell'industria: invece di descrivere stati e passaggi tra stati, descrivono decisioni gerarchiche composte da blocchi riutilizzabili.
La struttura di base
Un behavior tree è un albero di nodi. A intervalli regolari, spesso più volte al secondo, il sistema lo «visita» partendo dalla radice. Ogni nodo, quando viene eseguito, restituisce uno di tre risultati:
- successo: il compito è riuscito;
- fallimento: il compito non è possibile o non è riuscito;
- in corso: il compito richiede altro tempo, per esempio un movimento non ancora concluso.
I nodi si dividono in due grandi famiglie: le foglie, che fanno qualcosa o verificano una condizione, e i nodi composti, che decidono in che ordine eseguire i figli.
I nodi composti principali
Sequenza
Esegue i figli da sinistra a destra. Si ferma e restituisce fallimento al primo figlio che fallisce; restituisce successo solo se tutti riescono. Corrisponde a un «e poi»:
Sequenza
├─ Condizione: giocatore entro portata?
├─ Azione: girati verso il giocatore
└─ Azione: attacca
Selettore
Esegue i figli da sinistra a destra finché uno riesce. Restituisce successo al primo successo e fallimento solo se falliscono tutti. Corrisponde a un «altrimenti», ed è il modo naturale di esprimere priorità:
Selettore
├─ Sequenza "fuggi se ferito"
├─ Sequenza "attacca se vicino"
├─ Sequenza "insegui se visibile"
└─ Azione: pattuglia
L'ordine dei figli è la lista delle priorità: il nemico prova prima a fuggire, poi ad attaccare, poi a inseguire, e solo se nulla di tutto ciò è applicabile pattuglia.
Parallelo
Esegue più figli contemporaneamente, per esempio muoversi verso una copertura mentre si spara. La regola di successo va definita: basta che riesca un figlio, oppure devono riuscire tutti.
Decoratori
Un decoratore ha un solo figlio e ne modifica il risultato o l'esecuzione. Alcuni esempi molto usati:
| Decoratore | Effetto |
|---|---|
| Inverti | Trasforma successo in fallimento e viceversa |
| Ripeti | Esegue il figlio un numero fissato di volte o all'infinito |
| Raffreddamento | Impedisce di rieseguire il figlio prima di un certo tempo |
| Limite di tempo | Fa fallire il figlio se dura troppo |
| Riesci sempre | Restituisce successo qualunque sia l'esito |
Il raffreddamento, per esempio, permette di dire «lancia una granata, ma non più di una ogni otto secondi» senza aggiungere variabili alla logica dell'attacco.
La blackboard
I nodi devono condividere informazioni: la posizione dell'ultimo avvistamento, il bersaglio attuale, la copertura scelta. Invece di collegare i nodi direttamente, si usa una blackboard: una tabella di chiavi e valori accessibile a tutto l'albero. Un'azione «trova copertura» scrive la posizione nella chiave copertura, e un'azione successiva «vai a» la legge.
Questo mantiene i nodi indipendenti e riutilizzabili: la stessa azione «vai a» funziona con qualunque chiave le venga indicata.
Il ruolo dello stato «in corso»
Lo stato «in corso» è ciò che distingue un behavior tree da una semplice serie di condizioni. Un'azione come «cammina fino al punto» non termina in un solo aggiornamento: restituisce «in corso» finché il personaggio non arriva. Alla visita successiva l'albero riprende da lì.
C'è però una scelta di progetto importante. Se a ogni visita si rivalutano anche i rami a priorità più alta, il nemico può interrompere la camminata non appena vede il giocatore. Se invece si riprende sempre dal nodo in corso, il comportamento è più stabile ma meno reattivo. Molte implementazioni offrono entrambe le modalità, con nodi che possono «interrompere» quelli a priorità inferiore quando la loro condizione diventa vera.
Behavior tree o macchina a stati?
| Aspetto | Macchina a stati | Behavior tree |
|---|---|---|
| Comportamenti semplici | Molto chiara | Leggermente più verbosa |
| Molti comportamenti | Transizioni difficili da gestire | Si aggiungono rami |
| Priorità | Codificate nelle transizioni | Espresse dall'ordine dei figli |
| Riutilizzo | Stati riutilizzabili con fatica | Sottoalberi riutilizzabili |
| Debug | Facile vedere lo stato attuale | Serve visualizzare il percorso nell'albero |
Le due tecniche non si escludono. È comune usare una macchina a stati per le modalità generali, come esplorazione, combattimento e allarme, e un behavior tree all'interno di ciascuna.
Consigli pratici
- Mantenere le foglie piccole: una condizione o un'azione per nodo.
- Dare nomi chiari ai sottoalberi e riutilizzarli tra nemici diversi.
- Costruire o adottare un visualizzatore che mostri in tempo reale quali nodi sono attivi: senza, il debug diventa difficile.
- Non valutare l'albero a ogni fotogramma se non serve: pochi aggiornamenti al secondo bastano per la maggior parte dei nemici.
In sintesi
- Un behavior tree organizza le decisioni come un albero di nodi riutilizzabili.
- Ogni nodo restituisce successo, fallimento o in corso.
- Sequenza significa «e poi», selettore significa «altrimenti» ed esprime le priorità.
- Decoratori e blackboard aggiungono controllo e condivisione dei dati.
- Behavior tree e macchine a stati si possono combinare in modo efficace.