Intelligenza Artificiale parte 1

Introduzione

Questo è il primo tutorial a richiesta fatta da Nik Fiorentino nel gruppo di Facebook correlato a questo sito.

Il discorso sulle AI (artificial intelligence) è molto complesso ed è mirato al tipo di gioco che si crea e dipende molto da come si vuole che i nemici agiscano. Fondamentalmente ci sono due tipologie di nemici, la prima è semplice, il nemico si muove su un percorso stabilito ma non attacca il player, possiamo anche definirlo come un ostacolo mobile, vedremo in seguito come svilupparlo. Il secondo tipo è molto più complesso in quanto attacca il giocatore, la complessità varia a seconda del tipo di gioco e come si vuole creare l’attacco, ma di solito va dal semplice movimento verso il player al nemico che spara e si nasconde. 

Le software house che creano video giochi hanno implementato negli anni svariate AI contando molto sul comportamento umano, infatti in molti giochi i nemici fanno esattamente quello che fanno le persone in determinate circostanze. Ci sono anche tipologie di AI che possiamo definire scenografiche, come le persone che popolano i marciapiedi e le auto del traffico, cercherò di parlare anche di queste ultime in fondo a questa serie di tutorial, ma iniziamo.

AI semplice, ostacolo mobile.

Per semplificare il procedimento consiglio di usare il primo file degli articoli sul prototipo (il file si chiama prototipo01.blend), se non avete scaricato i file li potete trovare qua . Aperto il file la prima modifica da fare è cancellare la linea di mattoni che riguarda il salto perchè non ci serve per questo articolo. 

Sposta il player sull’asse Y in negativo, in modo da portarlo quasi sul bordo. Quello che voglio realizzare è un’AI di un nemico che va da una parte all’altra del piano, come una sentinella, a cosa serve ? A capire come inserire dei punti di passaggio dove il nemico si ancora per eseguire il comportamento sopra citato, i punti possono essere più di due in modo che faccia una ronda attraverso un percorso da noi stabilito. Ricordo che né il pc, né Blender sono in grado di pensare, siamo noi a dirgli come devono agire, ma non pensano. Aggiungi un cubo (Shift+A/Mesh->Cube) e spostalo sull’asse Z di 1 unità, dagli un materiale sul rosso (elimina la specularità) e spostalo sul lato opposto del player. Duplica il pavimento e posizionalo sul lato del nemico, dovresti avere una configurazione simile all’immagine qui sotto

Adesso inserisci un’altro cubo e posizionalo sul piano su un lato del nemico, a destra o a sinistra è irrilevante, duplica il nuovo cubo, non ha importanza il materiale perchè a lavoro ultimato sulla scena non verrà renderizzato, ma per comodità adesso gli puoi dare un materiale sul giallo (senza specularità). E’ importante rinominare l’oggetto, il nome lo puoi scegliere come al solito a tuo piacimento, io lo chiamo nodo1 (che mi ricorda che è il nodo di un percorso), a questo punto duplica il nodo e posizionalo dalla parte opposta del nemico. Adesso la scena si presenta come di seguito

Seleziona adesso il nemico, creerò il loop che lo fa andare da un nodo all’altro. Questa logica può essere creata in diversi modi, i due principali metodi sono chiuso o aperto, i due metodi si differenziano semplicemente dal fatto che su quello aperto è possibile aggiungere altri nodi, mentre in quello chiuso no, li vedremo entrambi anche se per questo esempio basterebbe quello chiuso. 

Metodo chiuso

Il metodo chiuso fa si che il nemico si sposti da un nodo all’altro usando la collisione come metodo di scambio tra un movimento e l’altro, questo metodo a collisione richiama due State Mask quindi due comportamenti diversi, andiamo per ordine. Seleziona uno ad uno i nodi e aggiungi loro una Property col nome del nodo, questa servirà per la collisione. Riseleziona il nemico dagli una fisica Dynamic (dalla finestra delle Proprietà dell’oggetto sull’ultimo tab) e nel primo State Mask (quello di default) aggiungi un Sensor Always, un Controller And e un’Actuator Motion, collegali. Il nemico si dovrà muovere sull’asse X, quindi inserisci sull’Actuator Motion 0.10 sull’asse X nel campo Loc. Adesso inserisci un Sensor Collision e un’Actuator State, collegali tra di loro con un Controller And, nello spazio della Property del Sensor Collision scrivi il nome della Property che hai usato per identificare il primo nodo (io ho usato nodo1), mentre sull’Actuator State clicca sul secondo State Mask, inserisco un’immagine per questi mattoni

Queste righe di mattoni dicono al nemico di muoversi sull’asse X positivo e quando entra in collisione con il nodo1 cambia State Mask, semplice ma efficacie. Nello State Mask 2 bisogna invertire i mattoni, cioè inserire sull’Actuator Motion -0.10 sempre sull’asse X, sul Sensor Collision il nodo2 come Property e sull’Actuator State seleziona lo State Mask 1. Così facendo il nemico si muoverà da una parte all’altra con regolarità. Il tocco finale è rendere i nodi invisibili dalle Proprietà della fisica. Si possono anche ridurre le dimensioni dei nodi, si può anche aggiungere un’Actuator Edit Object collegandolo al Controlle And della riga del Sensor Always in modalità Track to verso i nodi in modo che se dovesse capitare che il nemico si sposta dall’asse di movimento ritorni sui binari puntando al nodo, in questo esempio bisogna scegliere come Track Axis la X per il primo State Mask e l’asse -X per il secondo. 

In pratica è possibile creare più nodi in modo da implementare un percorso per una ronda, i contro sono parecchi (ecco perchè è un metodo chiuso) di sicuro in testa ci sta il fatto che bisogno inserire manualmente tutti i nodi e i vari State Mask e di volta in volta modificare il parametro per il movimento, richiede tempo e pazienza. Questo metodo è utile anche per creare dei platform mobili, sono sicuro che ti è venuto in mente.

Metodo aperto 

Per iniziare crea una nuova Scena Full copy, dalla barra del menù c’è una voce Scene alla sua destra un pulsante con un + cliccalo e poi scegli Full Copy. In questo modo abbiamo la stessa identica scena di prima che andremo a modificare senza perdere quella precedente. La prima cosa da fare è naturalmente eliminare tutti i mattoni fin’ora usati in tutti gli State Mask. Seleziona il nemico (se non è selezionato) ed inserisci una Property Int (integer), rinominala conto_nodi (oppure count_nodi, o anche index_nodi) sarà l’indice che controlla il movimento e dagli un valore di 1 che sarà il primo nodo. Avrai forse notato che una volta fatta la copia della Scena Blender ha aggiunto la numerazione a ogni oggetto, questo perchè il file è uno solo e gli oggetti fanno parte tutti dello stesso file, io li ho rinominati levando la numerazione di blender e mettendo 01 come numero di partenza, gli unici oggetti che aumentano di numerazione sono i nodi, quindi verranno 02, 03, 04 ecc ecc. 

Come prima cosa bisogna fare l’incremento dell’indice, quindi aggiungi un’Actuator Property, rinominalo incremento (in questo esempio è meglio rinominare tutti i mattoni perchè saranno parecchi), cambia il Mode in Add, scegli la Property conto_nodi (o quella col nome scelto da te) e inserisci una Value di 1, ogni volta che il nemico collide con un nodo il numero sale e di conseguenza si muoverà verso un’altro nodo. Siccome voglio creare una ronda a forma di quadrato ho bisogno di quattro nodi, seleziona i due nodi già nella scena e duplicali, ricordati di rinominarli nodo03 e nodo04 e di rinominare anche le Property di identificazione. Seguendo la semplice logica spiegata sopra inserisci tre Sensor Collision e assegna ad ogn’uno di loro una Property di un nodo (saranno nodo1, nodo2, nodo3), la collisione col quarto nodo resetterà l’indice facendolo tornare a 1, quindi lo inserirò per ultimo; ricorda di rinominare i mattoni. Adesso inserisci un Controller Xor e collega sia i tre Sensor che l’Actuator Property incremento, il Controller Xor invia un’impulso se uno dei Sensor è positivo ma non tutti contemporaneamente, in questo modo ho una logica più snella. Adesso dobbiamo dire al nostro nemico che quando l’indice ha un dato valore si deve muovere verso un dato nodo, per fare questo inserisci quattro Sensor Property, ogni Sensor deve avere un Equal associato a un nodo, quindi nel primo sarà 1, nel secondo 2, nel terzo 3 e nel quarto 4, della Property indice conto_nodi. Rinominali, io ho usato la formula Eq==1, Eq==2, ecc ecc, in programmazione il doppio uguale significa ‘è uguale a’, mentre quello singolo è per l’assegnazione delle variabili, ma non è questo l’argomento che ci interessa ora. Quindi come detto bisogna associare a ogni indice un nodo, inserisci quattro Actuators Edit Object e collegali in linea con i quattro Sensors Property appena inseriti attraverso quattro Controller And, rinominali come i nodi (nodo1, nodo2, nodo3, nodo4), sul menù a tendina Edit Object seleziona l’opzione Track to, sull’opzione Object scegli per ogni Actuator un nodo in sequenza, dal primo al quarto, sul campo Time inserisci 20, questo darà al nemico un tempo di rotazione che lo farà sembrare più realistico, sul campo Track Axis imposta tutti gli Actuators sull’asse X positivo, posto l’immagine di uno soltanto

Adesso hai l’incremento, una comparazione della Property e il nemico punta (col Track to) verso i vari nodi, a questo punto manca il movimento. Aggiungi un’Actuator Motion e sul campo Loc dell’asse X inserisci 0.05, questo Actuator va collegato a tutti i Sensors Property che eseguono la comparazione, perchè il movimento deve avvenire quando la Property cambia.
Per chiudere il loop basta azzerare la Property (conto_nodi), aggiungi un Sensor Collision e inserisci la Property nodo4, aggiungi un’Actuator Property, scegli l’unica Property e lasciando il Mode Assign dagli una Value di 1, in questo modo si dirigerà verso il primo nodo, collega i mattoni con un Controller And, salva e prova il file. Inserisco un’immagine di tutti i mattoni collassati, dai nomi potrei capire quali sono, è per farti vedere i collegamenti

Per questa prima parte è tutto, avevo detto che l’argomento è complesso, ho inserito poche immagini perchè intendo condividere il file creato in questo articolo, lo puoi scaricare QUI.
Nel prossimo articolo farò un’introduzione sulla Navigation Mesh e di seguito un’esempio pratico che includerà anche l’uso dell’opzione del motore di gioco Obstacle, alla prossima.