Università degli Studi di Padova - SIAGAS
←
→
Trascrizione del contenuto della pagina
Se il tuo browser non visualizza correttamente la pagina, ti preghiamo di leggere il contenuto della pagina quaggiù
Università degli Studi di Padova Dipartimento di Matematica "Tullio Levi-Civita" Corso di Laurea in Informatica Gameplay Programming: analisi e progettazione di un gioco strategico Tesi di laurea triennale Relatore Prof. Paolo Baldan Laureando Marco Focchiatti Anno Accademico 2017-2018
Marco Focchiatti: Gameplay Programming: analisi e progettazione di un gioco strategi- co, Tesi di laurea triennale, c 27 Settembre 2018.
Sommario Il presente documento descrive il lavoro da me svolto durante il periodo di stage, della durata di trecentoventi ore, presso l’azienda Ubisoft. Lo scopo principale dello stage consisteva nella mia integrazione in due task force[g] di ricerca e sviluppo riguardanti l’analisi di problematiche di un gioco strategico a turni. In primo luogo era richiesta la conoscenza dell’engine e delle convenzioni di codifica aziendali, prerequisito fondamentale per poter analizzare i problemi e progettarli. In secondo luogo era richiesta una continua interazione con i team di design per il feedback sul risultato da ottenere e con il team di engine developer per la compatibi- lità con quest’ultimo. L’applicativo su cui ho lavorato è l’engine Snowdrop, engine proprietario di Ubisoft, sviluppato da Massive Entertainment. v
“No matter what happen I’ll keep on moving. Until this life runs out of me I’ll keep on walking” — Allen Walker, D-Gray Man Ringraziamenti Innanzitutto, vorrei esprimere la mia gratitudine alla Professore Paolo Baldan, relatore della mia tesi, per l’aiuto e il sostegno fornitomi durante la stesura del lavoro. Ringrazio sentitamente Tiziano Sardone, mio tutor aziendale, per avermi aiutato a inte- grami nel gruppo di lavoro, e ringrazio il team di development per l’aiuto e la disponibilà. Padova, 27 Settembre 2018 Marco Focchiatti vii
Indice 1 Introduzione 1 1.1 L’azienda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1.1 Ubisoft Milan Studios SRL . . . . . . . . . . . . . . . . . . . . 1 1.2 L’idea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.2.1 Obiettivi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 Organizzazione del testo . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.4 Resoconto Orario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2 Strumenti e Tecnologie 5 2.1 Tecnologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.1.1 C++ 11 e C++ 14 . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.2 Strumenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2.1 Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2.2 Perforce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.2.3 Skype for business . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.2.4 Confluence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2.5 Mattermost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2.6 Jira . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.2.7 Swarm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.2.8 Snowdrop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3 Introduzione al progetto 15 3.1 Pattern architetturali . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.1.1 Entity-Component-System . . . . . . . . . . . . . . . . . . . . . 15 3.1.2 ECS Strict . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.2 Confronto ECS e OOP . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.3 Struttura del gioco e modularità . . . . . . . . . . . . . . . . . . . . . 16 3.4 Character . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.5 Layers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.6 Character life cycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4 Combat System 19 4.1 Interfaccia del sistema di combattimento . . . . . . . . . . . . . . . . . 19 4.1.1 Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.1.2 Element Factory . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.1.3 Combat Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2 I componenti del sistema di combattimento . . . . . . . . . . . . . . . 20 4.3 I Player . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 ix
x INDICE 4.4 Elementi e Fighter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.4.1 Interfaccia degli elementi . . . . . . . . . . . . . . . . . . . . . 23 4.5 Sistemi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.6 Azioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5 AI 27 5.1 Finite State Machine . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 5.1.1 Vantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 5.1.2 Svantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 5.2 Behavior Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 5.2.1 Vantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 5.2.2 Svantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 5.3 Goal Oriented Action Planning . . . . . . . . . . . . . . . . . . . . . . 29 5.3.1 Vantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.3.2 Svantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.4 Prototipazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.4.1 Flow dell’AI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.4.2 Comportamento . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.5 Planner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5.6 A* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5.7 Iterative-Deepening A* . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5.8 Ricerca Euristica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5.9 Depth-First Branch-And-Bound . . . . . . . . . . . . . . . . . . . . . . 32 5.10 Recursive Best-First Search . . . . . . . . . . . . . . . . . . . . . . . . 32 5.11 MiniMax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 5.12 MiniMax AlphaBeta pruning . . . . . . . . . . . . . . . . . . . . . . . 34 5.13 MonteCarlo Tree Search . . . . . . . . . . . . . . . . . . . . . . . . . . 34 5.14 Confronto riassuntivo . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.14.1 Scelta dell’algoritmo . . . . . . . . . . . . . . . . . . . . . . . . 36 6 Conclusioni 37 6.1 Raggiungimento degli obiettivi . . . . . . . . . . . . . . . . . . . . . . 37 6.1.1 ECS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 6.2 Conoscenze acquisite . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 6.3 Valutazione personale . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Glossario 39 Acronimi 45 Bibliografia 47
Elenco delle figure 1.1 Logo Ubisoft . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2.1 Dichiarazione Lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2 Schermata di Visual Studio . . . . . . . . . . . . . . . . . . . . . . . . 10 2.3 Logo Perforce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.4 Logo Skype for business . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.5 Schermata di Confluence . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.6 Schermata di Mattermost . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.7 Logo di Jira . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.8 Logo di Swarm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.9 Logo di Snowdrop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.1 ECS idea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.1 Gerarchia dell’interfaccia di combattimento . . . . . . . . . . . . . . . 20 4.2 Ciclo di Update del Combat . . . . . . . . . . . . . . . . . . . . . . . . 21 4.3 Ciclo di vita di un azione . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.4 Implementazione dell’Elemento . . . . . . . . . . . . . . . . . . . . . . 24 4.5 Interfaccia di un sistema . . . . . . . . . . . . . . . . . . . . . . . . . . 24 4.6 Interfaccia di un Azione . . . . . . . . . . . . . . . . . . . . . . . . . . 25 5.1 Esempio di Finite State Machine . . . . . . . . . . . . . . . . . . . . . 28 5.2 Esempio di Behavior Tree . . . . . . . . . . . . . . . . . . . . . . . . . 28 5.3 Esempio di Goal Oriented Action Planning . . . . . . . . . . . . . . . 29 5.4 Esempio di algoritmo A* . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5.5 Esempio di algoritmo Branch and Bound . . . . . . . . . . . . . . . . . 33 5.6 Esempio di algoritmo MiniMax applicato al gioco del TicTacToe . . . 34 5.7 Esempio di algoritmo Montecarlo . . . . . . . . . . . . . . . . . . . . . 35 xi
xii ELENCO DELLE TABELLE Elenco delle tabelle 1.1 Resoconto Orario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 5.1 Resoconto finale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Capitolo 1 Introduzione 1.1 L’azienda Ubisoft è stata fondata nel 1986 (chiamata fino al 2003 Ubi Soft Entertainment), ed è un’azienda multinternazionale francese sviluppatrice ed editrice di videogiochi, con sede centrale a Montreuil. La società è presente nel mondo con venticinque studi principali in circa diciassette paesi, e vanta titoli fra i quali Far Cry, Assassins Creed, Mario + Rabbids Kingdom Battle, Tom Clancy’s Ghost Recon, For Honor e Tom Clancy’s Rainbow Six. Figura 1.1: Logo Ubisoft 1.1.1 Ubisoft Milan Studios SRL Ubisoft Milan è stata fondata nel 1998 a Milano. Lo studio ha iniziato sviluppando giochi per Console portatili[g] quali: Rayman per Game Boy Color[g] e Lara Croft Tomb Raider: The Prophecy per Game Boy Advance[g] . Lo studio ha anche fatto da supporto per altri studi Ubisoft nello sviluppo di giochi tra i quali: Beyond Good & Evil, Tom Clancy’s Rainbow Six: Rogue Spear, Tom Clancy’s Rainbow Six 3: Athena Sword, Tom Clancy’s Splinter Cell: Chaos Theory, Assassin’s Creed III: Liberation, 1
2 CAPITOLO 1. INTRODUZIONE Assassin’s Creed Rogue e Just Dance. Lo studio si è poi mosso verso giochi che usavano Motion control[g] come: MotionSports e Raving Rabbids: Alive and Kicking assieme agli studi di Barcelona e Parigi. L’ultimo obbiettivo raggiunto è stato lo sviluppo come team in co-development con lo studio di Parigi del primo gioco in collaborazione con Nintendo per la piattaforma Switch[g] : Mario + Rabbids: Kingdom Battle. 1.2 L’idea Lo stage prevedeva l’inserimento dello studente con il ruolo di Junior Gameplay[g] Programmer; sarei stato inserito nel team coprendo il ruolo di ricerca e sviluppo, finalizzato alla comprensione della fattibilità dell’utilizzo di un sistema ECS nello sviluppo di giochi. 1.2.1 Obiettivi Gli obiettivi dello stage erano quelli di: 1. Ampliare le conoscenze e le competenze nel linguaggio di programmazione C++; 2. imparare a interagire con tutti i team necessari allo sviluppo di un videogioco, per capire in profondità le relazioni tra di essi; 3. imparare ad utilizzare tutti gli strumenti necessari per sviluppare un videogioco; 4. comprendere ed applicare gli standard di qualità aziendali per la scrittura del codice e il ciclo di vita che questo ha nello sviluppo; 5. svolgere attività di ricerca e sviluppo in modo autonomo. Lo stage infatti prevedeva l’inserimento dello studente nel team di sviluppo per la ricerca di problematiche sullo sviluppo di un Gioco strategico a turni[g] , focalizzandosi in particolare su temi quali AI[g] e Combat System. 1.3 Organizzazione del testo Il secondo capitolo descrive le tecnologie che ho utilizzato durante il periodo di stage e con cui ho acquisito familiarità; Il terzo capitolo approfondisce le tematiche dello stage che ho affrontato; Il quarto capitolo descrive le transizioni tra le modalità per la creazione di un gioco modulare; Il quinto capitolo approfondisce l’analisi effettuata sul sistema di combattimento che guida un videogioco strategico a turni; Il sesto capitolo approfondisce la tematica degli algoritmi di Intelligenza Artificiale analizzati; Nel settimo capitolo vengono riportate le conclusioni tratte dal periodo di stage. Riguardo la stesura del testo, relativamente al documento sono state adottate le seguenti convenzioni tipografiche:
1.4. RESOCONTO ORARIO 3 • gli acronimi, le abbreviazioni e i termini ambigui o di uso non comune menzionati vengono definiti nel glossario, situato alla fine del presente documento; • per la prima occorrenza dei termini riportati nel glossario viene utilizzata la seguente nomenclatura: parola[g] ; • i termini in lingua straniera o facenti parti del gergo tecnico sono evidenziati con il carattere corsivo; • i titoli dei videogiochi saranno riportati con il carattere corsivo. 1.4 Resoconto Orario Fase Ore Obiettivo Formazione 30 Formazione sull’engine 30 formazione su C++11 e C++14 10 formazione sugli strumenti aziendali Combat Sy- 40 Analisi e discussione del problema con il team di stem design 40 Progettazione del sistema di Combattimento 8 Discussione e presentazione del sistema con il team di sviluppo e di design 10 Discussione e presentazione del sistema al team dell’Engine 12 Ristrutturazione del sistema dati i feedback 20 Sviluppo del prototipo di sistema AI 50 Analisi e discussione dei problemi con il team di design 16 Analisi e studio degli algoritmi di ricerca 24 Analisi e studio di FSM,Behavior tree,GOAP 10 Ideazione prototipo 20 Discussione e presentazione delle scelte Tabella 1.1: Resoconto Orario
Capitolo 2 Strumenti e Tecnologie 2.1 Tecnologie Per il processo di sviluppo, ho ampliato le mie conoscenze del linguaggio di program- mazione C++ apprese in ambito accademico con le nuove funzionalità introdotte nelle versioni C++ 11 e alcune di C++ 14. 2.1.1 C++ 11 e C++ 14 C++ è un linguaggio di programmazione orientato agli oggetti, con tipizzazione statica. È stato sviluppato da Bjarne Stroustrup ai Bell Labs nel 1983 come un miglioramento del linguaggio C tramite l’introduzione del paradigma di programmazione a oggetti, funzioni virtuali, overloading degli operatori, ereditarietà multipla, template e gestione delle eccezioni. [5] Il linguaggio venne standardizzato nel 1998 (ISO/IEC 14882:1998 "Information Technology - Programming Languages - C++", aggiornato nel 2003). C++11, conosciuto anche come C++0x, è il nuovo standard per il linguaggio di programmazione C++ che sostituisce la revisione del 2003, mentre C++14 è stata una revisione minore nel 2014, l’attuale standard è C++17. Nella versione 11 le principali modifiche sono state: • espressioni Lambda; • utility per template; • deduzione di tipo; • smart Pointer; • costruttore di Move e rvalue reference. Mentre nella versione 14 sono state introdotte migliorie nelle espressioni lambda quali la deduzione di tipo e valori di default, shared Mutex[g] ed altre feature per il Multithreading[g] . Espressioni lambda In C++11 un’espressione lambda, spesso definita semplicemente lambda, costituisce un modo efficace per definire un oggetto funzione anonima nella posizione in cui viene richiamato o passato come argomento a una funzione. In genere le lambda vengono 5
6 CAPITOLO 2. STRUMENTI E TECNOLOGIE usate per incapsulare alcune righe di codice passate agli algoritmi o ai metodi asincroni. Le espressioni lambda sono composte da sei parametri: 1. Clausola di acquisizione: nota anche come lambda-introducer nella specifica C++, che specifica le variabili da acquisire e se l’acquisizione viene effettuata per valore o per riferimento, esistono due modalità di acquisizione automatica: = e &. = acquisirà tutte le variabili necessarie per copia mentre & per reference. 2. Elenco parametri: facoltativo. Una lambda può accettare parametri di input. L’elenco di parametri (lambda declarator nella sintassi standard) è facoltativo e da molti punti di vista è simile all’elenco di parametri per una funzione. 3. Specifica modificabile: facoltativa. La specifica modificabile consente al corpo di un’espressione lambda di modificare le variabili acquisite per valore. 4. Exception-specification: facoltativa, utilizzata per indicare che l’espressione lambda non generi alcuna eccezione 5. Trailing-return-type: facoltativo. Il tipo restituito di un’espressione lambda viene dedotto automaticamente. Non è necessario usare la parola chiave auto a meno che non venga specificato un trailing-return-type. Trailing-return-type è simile alla parte del tipo restituito di un metodo o funzione ordinaria. Tuttavia, il tipo restituito deve seguire l’elenco di parametri ed è necessario includere la parola chiave di trailing-return-type -> prima del tipo restituito. 6. Corpo dell’espressione lambda: può contenere qualsiasi elemento che può essere contenuto nel corpo di un metodo o di una funzione ordinaria. Figura 2.1: Dichiarazione Lambda Deduzione di tipo Spesso la dichiarazione di una variabile non è molto agevole, soprattutto quando si tratta di tipi definiti all’interno di particolari Template[g] ; prendiamo ad esempio la seguente dichiarazione std::vector iterator = myVector.begin()
2.1. TECNOLOGIE 7 Come si può notare, è una dichiarazione lunga e intricata, motivo per cui è facile cadere in errore. Con la keyword auto informiamo il compilatore che il tipo della variabile da noi dichiarata sarà uguale al tipo che verrà ritornato dall’espressione assegnata. In questo modo la variabile può essere dichiarata semplicemente con auto iterator = myVector.begin() permettendo così una scrittura più veloce del codice con meno possibilità di errore. Gli utilizzi di questa keyword sono diversi: 1. Inizializzazione di variabile: auto aVar = anotherVar; 2. trailing return: auto func() -> aType; 3. dichiarazione di funzione: auto func(); 4. dichiarazione di tipo: decltype(auto) a = aVar of function; Smart Pointers La gestione dell’allocazione dinamica della memoria è sempre stata, fin dai primi computer, un punto delicato della programmazione. Molti moderni linguaggi di programmazione (tipo il Java) offrono strumenti per la gestione automatica della memoria. Per aiutare la gestione della memoria sono stati introdotti diversi Smart pointer[g] : • Unique pointer: non è copiabile o copia-assegnabile, due istanze di unique ponter non possono gestire lo stesso oggetto. Un non const unique pointer può trasferire la proprietà dell’oggetto gestito in un altro unique pointer, quando viene distrutto l’oggetto puntato viene distrutto; • Shared pointer: la proprietà dell’oggetto viene ripartita egualmente a tutte le copie, all’ultima istanza rimasta viene delegata la responsabilità di distruggere l’oggetto; • Weak pointer: non incide sul ciclo di vita dell’oggetto puntato, questo significa che in ogni momento è possibile che il weak pointer venga invalidato. In questo modo è permesso a qualsiasi funzione o classe di mantenere un riferimento ad un oggetto senza influenzarne il ciclo di vita, a discapito però di una maggiore difficoltà di implementazione del codice. Move constructor e rvalue reference La rvalue reference contiene riferimenti a un rvalore e la sua dichiarazione è la seguente: Type && a; Questi riferimenti possono essere utilizzati da funzioni per ritornare oggetti temporanei creati all’interno della stessa senza doverli allocare nello heap, grazie a operatori quali std::forward e il costruttore di move. Il move contructor prende le informazioni di un oggetto e la sua ownership, trasferendole nell’oggetto corrente, lasciando però l’oggetto derubato in uno stato valido. La sua dichiarazione è la seguente:
8 CAPITOLO 2. STRUMENTI E TECNOLOGIE Class(Class &&another_Class_item); Questo permette di ridurre al minimo costruzioni di copia là dove il passaggio per riferimento di una variabile non è possibile, ottimizzando così il codice come mostrato nel seguente esempio. using namespace std ; vector < int > doubleValues ( const vector < int >& v ) { vector < int > new_values ; new_values . reserve ( v . size () ) ; for ( auto itr = v . begin () , end_itr = v . end () ; itr != end_itr ; ++ itr ) { new_values . push_back ( 2 * * itr ) ; } return new_values ; } int main () { vector < int > v ; for ( int i = 0; i < 100; i ++ ) { v . push_back ( i ) ; } v = doubleValues ( v ) ; } In questo esempio si vuole dato un vettore creare un nuovo vettore con valori raddoppiati senza modificare il vettore di origine, in questo esempio si devono effettuare 3 costruzioni la prima per il vettore vuoto che poi dovrà essere copiato quando verrà restituito e copiato nel vettore v, il compilatore ottimizzerà queste ultime due copie copiando direttamente in v. const vector < int >&& doubleValues ( const vector < int >& v ) { vector < int > new_values ; new_values . reserve ( v . size () ) ; for ( auto itr = v . begin () , end_itr = v . end () ; itr != end_itr ; ++ itr ) { new_values . push_back ( 2 * * itr ) ; } return std :: move ( new_values ) ; } int main () {
2.2. STRUMENTI 9 vector < int > v ; for ( int i = 0; i < 100; i ++ ) { v . push_back ( i ) ; } v = doubleValues ( v ) ; } Con la move semantics invece verrà solamente costruito il vettore new values e la sua area di memoria verrà spostata in v al ritorno della funzione evitando ogni copia aggiuntiva del vettore. 2.2 Strumenti L’azienda ha imposto vari strumenti che devono essere utilizzati, sia per lo sviluppo che per la coordinazione interna. In particolar modo è stato indicato Visual Studio 2015 come IDE[g] di sviluppo software e i seguenti strumenti per la coordinazione: • Perforce; • Skype for business; • Confuence; • Mattermost; • Jira; • Swarm; • Snowdrop. 2.2.1 Visual Studio Visual studio è un ambiente di sviluppo integrato sviluppato da Microsoft, che supporta attualmente diversi tipi di linguaggio, quali C, C++, C#, F#, Visual Basic .Net, Html e JavaScript. Questo ambiente di sviluppo è tra i migliori per il linguaggio di programmazione C++ , supporta C++17 e offre diversi plugin per agevolare lo sviluppo quali Visual Assist[g] , controllore della sintassi Clang[g] C++, Profiling Tools[g] per la memoria CPU[g] e GPU[g] . Supporta Windows 10, 8.1 SDK[g] , e per questo è tra i software più usati tra le aziende produttrici di giochi.
10 CAPITOLO 2. STRUMENTI E TECNOLOGIE Figura 2.2: Schermata di Visual Studio 2.2.2 Perforce Perforce è un software di Version control[g] venduto sotto il brand di Helix and Hansoft. Questo è largamente utilizzato nell’industria dei videogiochi rispetto a Git e altri competitor grazie ad una gestione migliore dei file binari, come Texture[g] e Asset[g] , molto presenti nello sviluppo di un gioco. Figura 2.3: Logo Perforce 2.2.3 Skype for business Skype for business è un client di messaggistica istantanea, ideato per funzioni di comunicazione aziendale. A differenza di Windows Live Messenger, infatti, ha funzioni differenti ed è stato sviluppato e progettato appositamente per le esigenze aziendali e soprattutto per l’introduzione del telelavoro. Tra le funzioni implementate, permette di collaborare attraverso una pagina vuota per note, disegni o immagini importate sui quali i partecipanti alla riunione possono lavorare insieme. Inoltre possono interagire simultaneamente durante una presentazione in PowerPoint e l’organizzatore della riunione può creare dei sondaggi a cui i partecipanti alla riunione possono votare e visualizzare i risultati. Molto utili sono la condivisione desktop e la condivisione di applicazioni. In questo modo, viene di molto agevolato il poter effettuare riunioni con partecipanti di altre sedi dell’azienda stessa.
2.2. STRUMENTI 11 Figura 2.4: Logo Skype for business 2.2.4 Confluence Confluence è un software di collaborazione sui contenuti sviluppato da Atlassian, utilizzato per produrre la documentazione del lavoro svolto da condividere con tutte le sedi aziendali. Confluence permette l’utilizzo e lo sviluppo di macro per inserire grafici, code snippet, link e riferimenti interni alla documentazione e di strutturare il documento. Figura 2.5: Schermata di Confluence 2.2.5 Mattermost Mattermost è una soluzione Open source[g] , con server privato alternativa a Slack[g] . Come alternativa a software proprietari di messaggistica, Mattermost offre tutte le funzionalità di comunicazione, rendendole ricercabili e reperibili ovunque. Questo strumento è stato usato per il coordinamento del team di lavoro e sviluppo e risulta facile ed intuitivo utilizzarlo.
12 CAPITOLO 2. STRUMENTI E TECNOLOGIE Figura 2.6: Schermata di Mattermost 2.2.6 Jira Jira è un software proprietario per il tracking di problemi, sviluppato da Atlassian. Jira prevede funzionalità di Bug Tracking[g] , Issue Tracking[g] e Project Management[g] . Jira è al giorno d’oggi il più popolare tra i tools di tracking. Figura 2.7: Logo di Jira 2.2.7 Swarm Swarm è un software del brand Helix and Hansoft per revisione di codice, compatibile con Perforce. Tra i benefici della code review ci sono: • Imporre gli standard di codifica, migliorando così leggibilità e affidabilità del codice; • condivisione di esperienza e conoscenza; • scoperta di errori preventivamente; • miglioramento del codice scritto. Swarm offre queste funzionalità senza aggiungere troppo overhead di lavoro al team di sviluppo.
2.2. STRUMENTI 13 Figura 2.8: Logo di Swarm 2.2.8 Snowdrop Snowdrop è un engine proprietario di Ubisoft sviluppato da Massive Entertainment per lo sviluppo di giochi su Microsoft Windows, Nintendo Switch, PlayStation 4, e Xbox One. L’engine è principalmente sviluppato in C++ e offre scripting basato su nodi per lo sviluppo di AI, UI[g] ed eventi. Figura 2.9: Logo di Snowdrop
Capitolo 3 Introduzione al progetto L’universo dei videogiochi è ampio e variegato, poiché sono moltissime le categorie che ne fanno parte. Ognuna di esse presenta problematiche peculiari, ma molte sono condivise. Il progetto di stage è focalizzato nell’analisi dei problemi che riguardano in maniera specifica la creazione di una battaglia a turni, presente in tutti i giochi strategici a turni, ponendo particolare attenzione al mantenere la compatibilità con l’engine Snowdrop e soddisfacendo i vincoli di design posti e il pattern architetturale richiesto (ECS in questo caso). Il mio compito è stato quello di verificare se un sistema ECS fosse applicabile a un gioco strategico a turni. 3.1 Pattern architetturali Nella programmazione molti sono i pattern architetturali applicabili a uno specifico software. La scelta di uno rispetto a un altro è dato dall’equilibrio di più fattori, come perseguire la manutenibilità del prodotto, la testabilità delle parti, per seguire una necessità di divisione tra dati e logica oppure per mantenere separati i compiti di classi diverse. Per questo motivo si presenta una scelta che deve essere fatta in maniera oculata: in questo caso, è ricaduta sul pattern Entity-Component-System (ECS), che permette una divisione tra la logica del software e i suoi dati, permettendo il Data-Driven Development[g] . 3.1.1 Entity-Component-System L’Entity-Component-System (ECS d’ora in poi) è un Pattern architetturale[g] che è maggiormente usato nello sviluppo di giochi. ECS segue il principio di composition over inheritance (cioè viene preferita la composizione di classi che l’ereditarietà fra esse) così da permettere una maggiore flessibilità nel definire entità, tema molto importante soprattutto nel campo dei videogiochi dove ogni oggetto nella scena è un entità. Un esempio di entità può essere una qualsiasi figura che prende parte in un combattimento. In ECS, ogni entità viene arricchita da uno o più componenti che le conferiscono proprietà o comportamenti aggiuntivi, permettendo così la capacità di modificare a runtime il suo comportamento. Questa modifica è facilmente attuabile tramite un’aggiunta o rimozione di componenti, eliminando così problemi di ereditarietà troppo profonda e gerarchie troppo grandi, difficili da comprendere, mantenere ed estendere. 15
16 CAPITOLO 3. INTRODUZIONE AL PROGETTO ECS è molto utilizzato nell’ambito videoludico per la grande compatibilità con il data-driven development, dove il codice deve definire comportamenti in base ai dati forniti esternamente. Figura 3.1: ECS idea 3.1.2 ECS Strict ECS strict è una specifica del pattern ECS che punta alla divisione totale dei dati dalla logica con la creazione di entity composte da components, in modo da creare contenitori di dati che non definiscono nessun comportamento e sistemi stateless che ottengono le entity con specifici components e applicano la logica sulla entity stessa, modificandola. Questo porta i massimi vantaggi derivanti dal scegliere ECS derivanti dalla completa separazione delle componenti dati da quelle logiche, permettendo di raggiungere un elevato livello di testabilità e manutenibilità. 3.2 Confronto ECS e OOP Nella programmazione orientata agli oggetti () la separazione fornita da ECS non è possibile ottenerla dato che dovrebbero esistere oggetti che conoscono come elaborare dati di altre classi, portando così altre classi ad essere solamente raccolte di dati. Se si volessero rispettare i paradigmi della OOP, porterebbe a una grande quantità di duplicazione di codice e inefficienza, poiché ci sarebbero moltissimi oggetti che dovrebbero essere aggiornati ad ogni frame, a differenza di ECS nel quale solo i systems devono essere aggiornati. Per questi motivi, l’OOP nel game developing non è la scelta ottimale da perseguire per ottenere la massima testabilità e manutenibilità del codice, permettendo un lavoro di manutenzione più preciso, rapido e mirato. 3.3 Struttura del gioco e modularità Ogni gioco strategico a turni è possibile vederlo come un insieme di due modalità di gioco differenti: una battaglia a turni (tipo scacchi) e una modalità di gestione
3.4. CHARACTER 17 del gioco. Solitamente alcuni dati tra esse sono condivisi, ma la maggior parte non è necessario che vengano condivisi tra i due sistemi. Questo poiché solitamente nel combattimento non è necessario conoscere tutte le risorse che il giocatore ha disponibili al di fuori dalla battaglia, escluse quelle scelte per quello specifico combattimento. Ad esempio, un giocatore sceglie una squadra di eroi da portare in battaglia e solo i loro dati sono necessari in quel momento, non serve invece conoscere tutto l’arsenale che il giocatore ha a disposizione. Date queste precondizioni, abbiamo pensato di strutturare le entità in maniera tale che offrano le informazioni e funzionalità strettamente necessarie per la modalità di gioco corrente, strutturando il gioco in moduli. Ogni modulo definisce una specifica entità che contiene solamente i dati necessari in un dato istante. 3.4 Character Il Character è l’entità comune tra tutte le modalità di gioco. Contiene tutti i dati condivisi tra le modalità necessari ai sistemi base per funzionare, per esempio la texture del personaggio. Il Character viene trasformato nell’entità specializzata all’ingresso della modalità di gioco specifica, per esempio entrando in combat il Character diventa un combattente, offrendo così i dati e le funzionalità richieste dai sistemi specifici del combattimento. Al termine della modalità di gioco le informazioni modificati nell’entità specifica di gioco dovranno essere trasportate nel Character per essere propagate nelle altre modalità di gioco: per esempio la vita del personaggio potrebbe essere richiesta al di fuori del combattimento per usare differenti animazioni o per essere guariti al di fuori di esso. Per ottenere tale obiettivo abbiamo creato un’entità specifica per il combat, cioè il Combattente. Questa espone solamente i dati e le funzionalità del personaggio utilizzabili e utili nel combat, escludendo le informazioni delle altre fasi di gioco non necessarie. Questa divisione permette di sviluppare entità specifiche per ogni modalità senza dover contenere anche le funzionalità delle altre modalità di gioco. Questo permetterà anche di lavorare in parallelo sulle varie feature e una maggior semplicità nello unit- testing grazie a un accoppiamento ridotto. 3.5 Layers Affinché la divisione tra modalità sia definita, il gioco deve essere strutturato per far sì che ogni modalità sia ben delineata e organizzata: • Una struttura base (Game) deve definire tutto ciò che è condiviso tra le modalità di gioco e i Character esistono come entità solamente in questo layer; • Sopra al Game poi vengono costruite le varie modalità di gioco, in cui vengono definite le specifiche regole per quella modalità. Di conseguenza devono saper costruire la propria entità da un Character e saperlo decomporre in un Character. Questo approccio permette una grande flessibilità per quanto riguarda lo sviluppo del gioco, poichè permette di poter lavorare su una qualsiasi modalità di gioco in maniera modulare o per specializzazione di modalità già sviluppate. In questo modo, ogni modalità può funzionare esclusivamente su un tipo di entità specifica ed avere
18 CAPITOLO 3. INTRODUZIONE AL PROGETTO delle funzionalità caratteristiche, creando così un ambiente totalmente modulare. Ogni intervento fatto sul gioco potrà quindi essere totalmente indipendente, non andando a interferire con altre parti, creando una altissima variabilità che può essere espansa facilmente oppure modificata senza danneggiare altri moduli. 3.6 Character life cycle Quando si entra in combat, un Character viene elaborato in un combattente, alcuni dati precedentemente posseduti verranno mantenuti, ad esempio la quantità di vita rimanente. Durante il combat, i dati non vengono modificati nel Character ma nel combattente, che alla fine della battaglia dovrà essere sincronizzato con il Character, che poi verrà trasformato nella prossima entità specifica per la successiva modalità di gioco. Il passaggio per il Game layer tra le modalità e quindi la transizione dell’entità specifica nel generico Character è un vincolo posto per evitare il passaggio "nascosto" di dati tra le diverse modalità e oltretutto semplifica la modularità delle modalità di gioco consentendo solo la trasformazione da Character ad entità specifica e viceversa.
Capitolo 4 Combat System In questo capitolo si parlerà del sistema di gestione delle azioni nel combattimento. Il sistema è stato diviso in modo tale che i Fighter siano raccolte di dati e possano richiedere delle azioni, che verranno eseguite dai sistemi durante l’Update[g] . 4.1 Interfaccia del sistema di combattimento L’interfaccia del sistema di combattimento è divisa in Combat, Console e Factory, che sono classi astratte da implementare. Il Combat ha la responsabilità di gestire l’ingresso e l’uscita dal combattimento e di chiamare l’update su tutti i suoi sistemi. La Console contiene tutte le azioni che un Fighter può richiedere e gestisce il loro smistamento verso i sistemi. La Factory è l’unico modo per creare i Fighter e i loro component. Il Combat estende privatamente le altre in modo da essere l’unico punto d’accesso per poter costruire il Fighter ed i player visto che questi richiederanno una console e l’unico possessore di questa sarà il Combat. 4.1.1 Console La classe Console contiene i comandi che gli elementi e i Fighter possono richiedere. Questa serve ai Fighter per richiedere azioni che poi verranno smistate verso i sistemi di combattimento corretti per essere elaborate. Un azione potrebbe essere la richiesta di un movimento o la creazione di un componente aggiuntivo per il Fighter. Extended Console L’ExtendedConsole è una classe che unisce la elementFactory e la Console per per- metter ai player che ne avranno accesso di creare azioni e creare o rimuovere elementi per costruire i Fighter. In questo modo ogni player potrà gestire i propri Fighter nel combattimento. 4.1.2 Element Factory La ElementFactory può essere usata dai sistemi per iniettare nuovi elementi nel combat se necessario, poiché ai sistemi non è permesso distruggere elementi o effettuare azioni, ma un azione potrebbe essere creata come risultato della creazione di un nuovo 19
20 CAPITOLO 4. COMBAT SYSTEM elemento. La ElementFactory è anche usata dal CombatLoader per creare istanze di tutti i combatElement all’inizio del combattimento, permettendo la transizione da Character a Fighter all’inizio della modalità di gioco. 4.1.3 Combat Interface Il Combat implementa privatamente l’ExtendedConsole in modo da fornire l’unico punto di accesso per costruire Players, Fighter, Entità e Sistemi attraverso i suoi metodi factory. La CombatInterface è un implementazione privata per fare in modo che nessuno oltre a lei sia consapevole che essa è un ExtendedConsole forzando così gli utilizzatori a usare i suoi metodi factory. La CombatInterface ha la responsabilità della creazione dei componenti fondamentali per la gestione del combattimento, assicura la loro corretta serializzazione, permettendo di salvare lo stato del combattimento e dei Fighter su supporto fisico, e all’uscita del combat effettua la transizione da Fighter a Character. Figura 4.1: Gerarchia dell’interfaccia di combattimento 4.2 I componenti del sistema di combattimento Basandosi sulle interfacce sopra descritte il sistema di combattimento creerà i Fighter e gli elementi alla creazione del contesto di combattimento, lasciando poi la gestione del flow del combat ai sistemi che gestiranno le azione dei componenti del combat, passate
4.2. I COMPONENTI DEL SISTEMA DI COMBATTIMENTO 21 dalla Console. Ogni azione verrà richiesta da un Fighter, un player o un element Figura 4.2: Ciclo di Update del Combat tramite la propria Console, poi questa azione verrà data al Combat che la salverà nel Log, per avere un resoconto delle azioni effettuate in tutta la battaglia, per infine darla al sistema competente per essere eseguita alla successiva update. Questo porta il Combat ad essere suddiviso nelle seguenti parti: • Player; • Elementi; • Fighter; • Sistemi; • Azioni.
22 CAPITOLO 4. COMBAT SYSTEM Figura 4.3: Ciclo di vita di un azione 4.3 I Player Il Player è un astrazione del giocatore reale, permette quindi di gestire le risorse della squadra e gli input. Un player contiene la raccolta di Fighter della sua squadra e permette la gestione dei turni, passando da un player all’altro, e la gestione delle azioni dei Fighter dello stesso team. Il suo compito è quello di fornire funzionalità per la gestione della squadra e gestire gli input per i propri Fighter. 4.4 Elementi e Fighter Gli elementi e i Fighter sono le entità che entrano a far parte del combattimento. Essi infatti non sono altro che raccolte di componenti che possono richiedere di svolgere azioni e sono coloro che subiranno gli effetti di queste. Un elemento è la parte fondamentale del Fighter mentre un Fighter è una composi- zione di elementi, che a loro volta sono composizioni di componenti base che descrivono il suo stato. Gli elementi contengono i dati e descrivono le capacità del Fighter e il suo stato. Un elemento contiene la lista dei suoi componenti ed espone metodi per aggiungerne e ottenerli. Un elemento non permette di modificare i propri componenti che possono essere modificati solo tramite le azioni elaborate dai sistemi, questo per relegare la logica solo nelle azioni e nei sistemi. Sia gli elementi che i Fighter possono richiedere un azione e rimuovere se stessi o un
4.5. SISTEMI 23 elemento in loro possesso, ma non possono crearne di nuovi. Rimuovere i componenti dal Fighter permette di rispecchiare meglio il suo stato, per esempio se il Fighter viene disarmato perderà la sua arma dai componenti. Invece richiedere un’azione permette di modificare lo stato dei componenti o di richiederne di nuovi, alterando lo stato del Fighter stesso. Poiché è stato richiesto che il combattimento sia deterministico, quindi che dato uno stato ed una lista di azioni lo stato risultante sia sempre lo stesso, le azioni dovrebbero essere progettate e pensate considerando solo lo stato interno dei componenti. Questo si concretizza in un Fighter che possiede degli elementi quali un arma e una vita che sono descritti dai loro componenti. In tal modo è possibile espandere un Fighter aggiungendo elementi per descrivere in maniera più dettagliata lo stesso, per esempio se si volesse rendere colpibile in due punti diversi il bersaglio basterebbe dividere il corpo dalla testa e assegnare due componenti che possano essere mirati, in questo modo il sistema di mira riconoscerebbe che nel Fighter esistono due punti al quale mirare. Un elemento può contenere altri elementi per rendere ancora più flessibile il sistema e, tornando all’esempio precedente, ora si può pensare che se miriamo al braccio del bersaglio, che ora ha una vita, il colpo danneggerà in parte il bersaglio, ma sarà possibile "distruggere" il braccio rendendo il Fighter incapace di effettuare certe azioni che solo il braccio poteva richiedere. 4.4.1 Interfaccia degli elementi Per dividere i dati base come la posizione o la console dei comandi consentiti all’elemento, che non saranno mai duplicati in un element, dai dati forniti dai Component è stata creata un interfaccia : CoreElement. L’Elemento contiente un UniquePointer privato del suo CoreElement che verrà usato dal sistema per gestire proprietà intrinseche del combat come la posizione o per richiedere comandi fondamentali all’elemento. Shared pointer vs Handle Per gestire la lista degli elementi si è dovuto scegliere tra shared pointer e Handle[g] , la cui principale differenza risiede nel fatto che uno shared pointer non distrugge l’oggetto puntato alla distruzione mentre un handle lo fa. La scelta di usare gli shared pointer è derivata dal fatto che, potendo un elemento essere bersaglio di un’azione in corso, se il Fighter vuole rimuovere quell’elemento non potrà farlo poiché sarà l’altra azione a tenere in vita l’elemento finché questa non sarà completata. In pratica cancellare un elemento trasforma lo shared pointer all’interno del Fighter in un weak pointer, in modo che appena ogni utilizzatore di tale elemento termina le operazioni su tale elemento, questo verrà cancellato. 4.5 Sistemi I sistemi contengono la logica fondamentale per lo svolgimento delle azioni e per ogni sistema ad ogni frame del combat verrà chiamata la funzione di update. Un sistema ha il compito di prendere tutti i Fighter e gli elementi che devono gestire o che hanno richiesto l’azione, per poi applicare gli effetti della logica su di essi. Per esempio il sistema di animazione farà aggiornare del delta time trascorso l’animazione di ogni Fighter che avrà l’ipotetico component Animable. La selezione viene fatta tramite un filtro sui componenti, per aggiornare solo il subset minimo di Fighter. Essendo i componenti la raccolta di dati necessaria al sistema, filtrando
24 CAPITOLO 4. COMBAT SYSTEM Figura 4.4: Implementazione dell’Elemento per componenti si può rimuovere ogni controllo da parte del sistema sul Fighter per verificare che questi contengano le informazioni necessarie. Un Sistema ha un riferimento alla ElementFactory che offre la possibilità di istanziare nuovi elementi. Anche se un Sistema può creare Elementi, non ha la possibilità di cancellarli, poiché questa responsabilità è delegata al Fighter o all’elemento che contiene tale elemento, dato che essi hanno il possesso dell’elemento. Perciò ogni elemento generato dal sistema deve essere capace di gestire il proprio ciclo di vita indipendentemente, notificando ai sistemi tramite la callback OnUnregisterElement quando viene distrutto. Figura 4.5: Interfaccia di un sistema 4.6 Azioni Le azioni sono le richieste che vengono fatte dagli Elementi e dai Fighter ad un sistema per poter alterare il loro stato, per esempio muoversi in un punto modificherà l’elemento
4.6. AZIONI 25 di posizione del Fighter che l’ha richiesto. Vengono implementate partendo da un interfaccia. Figura 4.6: Interfaccia di un Azione Ogni azione verrà sempre eseguita al ciclo di update successivo, e non è possibile inviare azioni durante l’update dei sistemi. L’interfaccia delle azioni permette di control- lare l’ID di quest’ultima, il sistema che la elaborerà e un identificativo. L’identificativo sarà una stringa usata nella serializzazione del azione.
Capitolo 5 AI In questo capitolo parleremo delle possibilità analizzate per lo sviluppo di un’Intelligenza Artificiale (AI) per il combattimento che dia l’impressione ai giocatori di prendere scelte di gruppo e collaborative. Per AI in questo capitolo si intende la capacità da parte del computer di analizzare lo stato del gioco e decidere delle mosse in base ad esso. Per questo motivo sono stati analizzati i seguenti metodi per creare un AI: • Finite State Machine; • Behavior Tree; • Goal Oriented Action Planning. 5.1 Finite State Machine Una macchina a stati finiti (finite state machine), o FSM in breve, è un modello computazionale basato su una macchina ipotetica fatta di uno o più stati. Solo un singolo stato può essere attivo allo stesso tempo, per cui la macchina deve passare da uno stato all’altro per eseguire diverse azioni. Una macchina a stati finiti è un modello utilizzato per rappresentare e controllare un flusso di esecuzione. È uno dei primi metodi usati per produrre un AI nei giochi poiché pur rimanendo semplice da implementare produce buoni risultati comportamentali da parte dell’AI. 5.1.1 Vantaggi • Una macchina a stati finiti è facilmente implementabile; • rende semplice il debug conoscendo lo stato in cui il Combattente dell’AI si trova; • è veloce da espandere, basta creare un nuovo stato e le sue transizioni. 5.1.2 Svantaggi Il principale svantaggio di una FSM è la sua flessibilità, infatti, Una macchina a stati finiti definisce gli stati nel quale ogni combattente si trova e le transizione che può prendere da quello stato valutando il contesto nel quale si trova. Questo non è sufficientemente flessibile per supportare un obiettivo che può cambiare nel corso della 27
28 CAPITOLO 5. AI Figura 5.1: Esempio di Finite State Machine battaglia. Può esplodere e diventare enorme se si espande per ogni nuova necessità. Per implementare una collaborazione tra nemici si dovrebbe implementare una gerarchia di FSM, così facendo verrebbe meno il principale vantaggio di questo metodo: la semplicità implementativa e di lettura del codice. 5.2 Behavior Tree I Behavior tree[g] si concentrano nell’aumentare la modularità degli stati. L’albero è composto da tre elementi principali: • Sequence: L’AI analizzerà tutti i figli del nodo in sequenza, si interrompe se si fallisce un operazione; • Selector: L’AI prenderà il primo figlio la cui esecuzione è possibile; • Foglia: è un azione che viene svolta dal combattente. Ogni combattente parte dalla radice del proprio behavior tree scorrendolo e così pianificando le sue azioni da svolgere. Figura 5.2: Esempio di Behavior Tree
5.3. GOAL ORIENTED ACTION PLANNING 29 5.2.1 Vantaggi • Gli alberi possono essere espansi e permettono agli sviluppatori di creare sotto- alberi che gestiscano particolari comportamenti che possono essere concatenati per creare un AI. Questo permette di creare anche un processo incrementale nel quale si può creare un albero che gestisca un comportamento base per poi creare branch per gestire casi ed obiettivi specifici; • La sua possibilità di essere data-driven permette ai designer di creare l’AI personalmente senza dover interagire con il team di programmazione. 5.2.2 Svantaggi Per creare un comportamento abbastanza completo l’albero potrebbe diventare molto profondo e grande rendendo difficile il Debugging[g] . 5.3 Goal Oriented Action Planning Goal Oriented Action Planning o GOAP è un sistema AI che permette ad ogni combattente di prendere una decisione dato il proprio bacino di azioni e gli obiettivi che deve perseguire. La particolare sequenza di azioni non dipende soltanto dall’obiettivo ma anche dallo stato attuale del mondo, poiché uno stato diverso potrebbe portare ad un diverso bacino di azioni per l’entità che porterebbe a una diversa pianificazione. Figura 5.3: Esempio di Goal Oriented Action Planning
30 CAPITOLO 5. AI 5.3.1 Vantaggi Con GOAP è possibile, definendo specifici obiettivi, far pianificare dal sistema le azioni da concatenare per ottenerlo nel miglior modo possibile. Il sistema può essere progettato per permettere la definizione di obiettivi tramite dati, portando così a data-driven-development. Il coordinatore del sistema GOAP pianifica le azioni dei combattenti facendoli interagire tra loro. 5.3.2 Svantaggi GOAP è il più complesso dei sistemi da sviluppare e non porta sempre ad azioni prevedibili. Il combattente deve poter calcolare le proprie azioni e gli effetti sul mondo ed il Planner deve riuscire a comporre tali azioni nel miglior modo possibile molto rapidamente. 5.4 Prototipazione Dopo l’analisi delle precedenti tecnologie è stato scelto di utilizzare il GOAP per la capacità di essere data-driven e perché intrinsecamente gestiva già tutti i casi richiesti dall’obiettivo iniziale. 5.4.1 Flow dell’AI Data la scelta si è pensato al seguente flow generale per un turno dell’AI nel combat: 1. All’inizio del turno dell’AI un Decision tree[g] , valutando lo stato attuale, computa lo SquadDesire cioè l’obiettivo che la squadra ha in questo turno; 2. dato lo SquadDesire, un FighterBias (un modificatore del comportamento standard) è computato ed assegnato ad ogni combattente della squadra; 3. il Coordinator richiede ad ogni combattente quali sono le azioni che vorrebbe e potrebbe svolgere nello stato corrente e inizia la simulazione per decidere che azione prendere; 4. il pianificatore esplora lo spazio delle azioni e produce un piano di azioni, e a ogni azione pianificata si ritorna al punto 3; 5. creato il piano questo viene dato al coordinatore che dice a ogni combattente nell’ordine corretto l’azione da svolgere; 6. dopo ogni esecuzione di un azione, se lo stato risultante differisce dallo stato pianificato, si ritorna al punto 3. 5.4.2 Comportamento Per permettere la creazione di "classi" diverse di nemici che agiscano secondo i loro ideali, per esempio un guerriero che cerca sempre di attaccare, si è deciso di dare ad ogni combattente un comportamento standard rappresentato da un insieme di valori che rappresentano quanto ogni combattente pesa il risultato di un’azione rispetto a un’altra. Un esempio per il guerriero che vuole attaccare potrebbe essere che la sua maschera pesi le azioni di attacco a 1 e quelle difensive a 0, portando quindi il guerriero
5.5. PLANNER 31 a voler attaccare piuttosto che difendersi. Standardizzando questa maschera per ogni combattente è possibile creare degli archetipi di combattenti che, se non influenzati, prenderanno le stesse scelte. SquadDesire e FighterBias Il FighterBias è una maschera di bit che si applica al comportamento di base di un combattente per modificarne la scelta delle azioni. Questo viene calcolato dato uno SquadDesire, cioè un’altra maschera che dichiara quali sono gli obiettivi del team data una situazione, uno stato del combat o un combat in generale. Applicando il FighterBias a un combattente è così possibile alterare il comportamento base al FighterBias per portarlo a una scelta di azioni che mirino allo stesso obiettivo della squadra. Il FighterBias potrebbe sia essere un incentivo a svolgere altre azioni rispetto agli standard, sia un obbligo in caso il gameplay richieda una certa azione per il combattente, permettendo un maggiore controllo da parte del team di design. 5.5 Planner Il planner, dopo che il coordinatore ottiene le azioni possibili, deve pianificare le scelte da prendere. Questo può essere visto come un problema di esplorazione di grafi, nel quale si parte dallo stato di gioco attuale e ad ogni scelta corrisponde un nuovo stato di gioco. Definita una funzione che valuti uno stato di gioco è quindi possibile utilizzare diversi algoritmi per l’esplorazione del grafo. • A*; • Iterative-Deepening A*; • Pure Heuristic Search; • Depth-First Branch-And-Bound; • Recursive Best-First Search; • MiniMax AlphaBeta pruning; • MonteCarlo Tree Search. 5.6 A* A* è un algoritmo che ricerca il primo miglior risultato. In questo algoritmo ad ogni nodo è associato un costo f(n) = g(n) + h(n) dove g(n) è il costo del percorso dalla radice al nodo n e h(n) è il costo euristicamente stimato per raggiungere l’obiettivo. Quindi, f(n) stima il minor costo totale di ogni soluzione che passa attraverso tale nodo. Ad ogni iterazione il nodo con il minor valore viene scelto per l’espansione, in caso di parità verrà scelto il nodo con minore h. L’algoritmo termina quando verrà raggiunto l’obiettivo.
32 CAPITOLO 5. AI Figura 5.4: Esempio di algoritmo A* 5.7 Iterative-Deepening A* In questa variazione di A* ogni iterazione del algoritmo è una ricerca in profondità che tiene traccia del costo f(n) = g(n) + h(n), di ogni nodo generato. Se il costo del nodo generato eccede il limite della iterazione il branch viene tagliato. Il costo della iterazione viene inizializzato alla stima euristica dello stato iniziale ed in ogni successiva iterazione è aumentato al costo totale del nodo a minor costo che è stato scoperto durante l’iterazione precedente. L’algoritmo termina quando un obiettivo è stato raggiunto e non eccede il limite. 5.8 Ricerca Euristica La ricerca Euristica[g] espande i nodi in ordine al loro valore h(n), mantenendo una lista dei nodi che sono già stati espansi e una lista di quelli che sono stati generati ma non ancora espansi. L’algoritmo inizia con la radice come nella lista dei nodi non espansi, ad ogni iterazione un nodo con il minimo valore euristico h(n) viene espanso, generando i suoi figli e infine viene spostato nella lista dei nodi chiusi. La funzione euristica viene applicata ai figli che poi vengono messi nella lista dei nodi ancora da analizzare. L’algoritmo continua fino al raggiungimento dello stato scelto. 5.9 Depth-First Branch-And-Bound L’idea della ricerca depth-first branch-and-bound (DFBnB) è quella di mantenere traccia della soluzione a costo minore ottenuta. Dato che il costo di un cammino parziale è la somma dei costi degli archi camminati fino a quel punto, se il costo di tale cammino supera il costo della soluzione trovata tale cammino può essere eliminato. Oltretutto, con un funzione euristica come il costo del minimo Spanning tree[g] dei rimanenti nodi, può essere aggiunta al costo per velocizzare l’eliminazione dei branch. 5.10 Recursive Best-First Search La ricerca Recursive best-first funziona mantenendo nello stack di ricorsione il percorso completo al nodo corrente che viene espanso e a tutti i nodi fratelli diretti dei nodi in
5.11. MINIMAX 33 Figura 5.5: Esempio di algoritmo Branch and Bound quel percorso, assieme al costo del miglior nodo nel sotto-albero esplorato sotto ogni fratello. Ogni qualvolta il costo del nodo corrente superi quello di qualche altro nodo nella regione espansa precedente dell’albero, l’algoritmo ritorna all’antenato comune più profondo, e continua la ricerca lungo il nuovo cammino. Effettivamente, l’algoritmo mantiene una soglia separata per ogni sotto-albero che diverge dal cammino di ricerca corrente. 5.11 MiniMax L’algoritmo MiniMax è costituito da una funzione di valutazione posizionale che misura la bontà di una posizione (o stato del gioco) e indica quanto è desiderabile per il dato giocatore raggiungere quella posizione; il giocatore fa poi la mossa che minimizza il valore della migliore posizione raggiungibile dall’altro giocatore. Quindi l’algoritmo MiniMax assegna un valore ad ogni mossa legale, proporzionale a quanto essa diminuisce il valore della posizione per l’altro giocatore. Per valutare le posizioni finali di vittoria e di sconfitta, un metodo è assegnare infinito alle mosse che portano alla vittoria finale e -infinito a quelle di sconfitta. Il valore per il giocatore A di ogni mossa non immediatamente vincente è poi il valore minimo di tutte le possibili contromosse di B. Questo presuppone che sia possibile per chi computa valutare tutto l’albero delle mosse possibili del gioco; in realtà questo si può fare solo per giochi molto semplici, e in generale si può solo calcolare una stima della probabilità che una data mossa porti alla vittoria di uno dei giocatori. In pratica il lavoro dell’algoritmo MiniMax è di esplorare tutti i possibili nodi dell’albero di gioco: il numero medio di nodi figlio per ogni nodo in esame è detto fattore di diramazione, ed è pari al numero medio di mosse possibili in una generica posizione del gioco.
Puoi anche leggere