Gli FPS sono da sempre il genere di gioco più in voga, forse per l’immersione derivante dalla prospettiva in soggettiva, o più semplicemente perchè ci piace far saltare le cose in aria. Come programmatori, non possiamo esimerci dall’esercizio di implementarne uno, ma che si scelga la strada facile del multiplayer, o si accetti la sfida e ci si imbarchi nella creazione di una storia single player, rimane la necessità di codificare opponenti (apparentemente) intelligenti e controllati dalla macchina. Ovviamente, essendo la programmazione l’arte di lasciare alla macchina la maggior parte del lavoro, si vorrà un approccio di minimo sforzo – massimo risultato, che è poi l’argomento del presente articolo.
L’idea è di dare all’AI un insieme di goal. Questi goal sono obiettivi quali piantonare una postazione, pattugliare lungo un percorso, indagare suoni sospetti e ovviamente, eliminare i nemici. Ogni singolo goal per se è di semplice implementazione, la parte difficile arriva quando più goal vanno in conflitto tra loro. Prendiamo l’esempio di un’AI che stia seguendo un percorso (stia ad esempio ritornando la bandiera), e debba anche fare fuoco su un nemico. Limitando l’esecuzione dei goal ad un aut-aut, si genererà una situazione in cui o il bot ignorerà l’opponente (finendo per farsi sparare), oppure gli sparerà (ignorando l’obbiettivo del gioco).
Non può fare entrambe le cose?
Per meglio gestire tali situazioni definiamo i servo. Un servo è una abilità a disposizione di ognuna delle unità presenti nel gioco (il giocatore ed i bot sono uguali in questo, ma ci torneremo). Nello scenario descritto, vogliamo un movement servo, che consenta il movimento nella mappa di gioco in una data direzione, ed uno shoot servo. Un ulteriore servo meno ovvio lo chiameremo torso twist. Ogni FPS permette ai giocatori di muoversi in una qualunque direzione (tramite wasd), mentre guardano in una qualunque altra direzione (tramite mouse) (per i consolari: è il motivo per cui i gamepad hanno due funghetti). Il nostro bot userà quindi il movement servo per muoversi lungo la mappa e ritornare la bandiera, mentre userà il torso servo per mirare al giocatore. Quindi userà lo shoot servo per prendersene cura.
In ogni istante, ogni servo può essere disponibile o meno. Se il movement servo è in uso per muoversi in una direzione non potrà essere usato per dirigersi in un’altra. Se l’arma sta ricaricando lo shoot servo non potrà essere usato. Per gestire la disponibilità dei servo, ogni goal definisce di quali servo necessita, (non ce motivo di cercare di eseguire lo shoot-the-player goal se il torso servo non è disponibile), e quali goal preferisce avere (se lo shoot servo non è disponibile, il torso servo può comunque essere usato per iniziare a puntare l’arma).
Infine definiamo le condizioni di attivazione ed i pesi. Ad ogni iterazione del gioco, le condizioni di attivazione di ogni goal dovono essere controllate: queste condizioni definiscono quando un goal può eseguire. Ad esempio, il goal capture-the-flag può eseguire ogni volta che il bot non ha la bandiera, ma la stessa è nel suo campo visivo. I goal attivi verranno quindi messi in una lista, e la lista ordinata per peso dei singoli goal, in modo che i goal prioritari ne siano in testa.
Quindi arriva la parte facile: i goal presenti nella lista vengono eseguiti, i goal di maggior peso ricevono i servo per primi. Ogni goal esegue unicamente quando tutti i servo di cui necessita sono disponibili. Ogni volta che un servo viene usato, viene segnato come non disponibile. In questo modo, i goal di bassa priorità eseguono unicamente quando non c’è nulla di più produttivo da fare.
Ma come fanno i goal a muovere i bot?
Come scritto, non vogliamo che i bot abbiano un comportamento differente dai giocatori: tutto cio che il giocatore può fare, possono farlo anche i bot e vice versa. Questo include guidare veicoli e usare torrette. Lo scenario tipico: avete appena codificato i controlli per i veicoli (ad esempio via tastiera o gamepad), e per nessuna ragione al mondo intendete ripetere il lavoro per i bot. La soluzione arriva dalle tastiere virtuali.
Una tastiera virtuale è un array di booleani, ognuno rappresentante un tasto (in termini di avanti, indietro, spara… non intendiamo qui le lettere). In questo modo tutto, dai mezzi alle torrette fino alle unità di fanteria sono egualmente considerati dal codice come veicoli, connessi a tastiere virtuali, ognuna di proprietà di un cervello. I cervelli siete voi che giocate davanti alle tastiere, o le AI che cercano di battervi.
E questo è quanto. Implementando questi semplici concetti è possibile realizzare un semplice ma flessibile FPS engine.
Happy coding.