aplicarea tehnicilor inteligenței artificiale și ...alaiba/pub/absolvire/2017 iarna/wartank... ·...
Post on 27-Oct-2019
10 Views
Preview:
TRANSCRIPT
1
UNIVERSITATEA „ALEXANDRU IOAN CUZA” IAŞI
FACULTATEA DE INFORMATICĂ
LUCRARE DE LICENŢĂ
Aplicarea tehnicilor inteligenței artificiale și
comunicarea în timp real în jocurile de tip
browser
Propusă de
Hogaș E. Vlad Marius
Sesiunea: Februarie, 2017
Coordonator știinţific
Asistent, dr. Vasile Alaiba
2
UNIVERSITATEA „ALEXANDRU IOAN CUZA” IAŞI
FACULTATEA DE INFORMATICĂ
LUCRARE DE LICENŢĂ
Aplicarea tehnicilor inteligenței artificiale și
comunicarea în timp real în jocurile de tip
browser
Hogaș E. Vlad Marius
Sesiunea: Februarie, 2017
Coordonator știinţific
Asistent, dr. Vasile Alaiba
3
DECLARAŢIE PRIVIND ORIGINALITATEA ŞI
RESPECTAREA DREPTURILOR DE AUTOR
Prin prezenta declar că Lucrarea de licență cu titlul „ Aplicarea tehnicilor inteligenței
artificiale și comunicarea în timp real în jocurile de tip browser” este scrisă de mine
şi nu a mai fost prezentată niciodată la o altă facultate sau instituţie de învățământ
superior din ţară sau străinătate. De asemenea, declar că toate sursele utilizate,
inclusiv cele preluate de pe Internet, sunt indicate în lucrare, cu respectarea regulilor
de evitare a plagiatului:
− toate fragmentele de text reproduse exact, chiar şi în traducere proprie din altă
limbă, sunt scrise între ghilimele şi deţin referinţa precisă a sursei;
− reformularea în cuvinte proprii a textelor scrise de către alţi autori deţine
referinţa precisă;
− codul sursă, imagini etc. preluate din proiecte open source sau alte surse sunt
utilizate cu respectarea drepturilor de autor şi deţin referinţe precise;
− rezumarea ideilor altor autori precizează referinţa precisă la textul original.
Iaşi,
Absolvent Hogaș Vlad Marius
_________________________
(semnătura în original)
4
DECLARAŢIE DE CONSIMŢĂMÂNT
Prin prezenta declar că sunt de acord ca Lucrarea de licență cu titlul „ Aplicarea
tehnicilor inteligenței artificiale și comunicarea în timp real în jocurile de tip
browser”, codul sursă al programelor şi celelalte conţinuturi (grafice, multimedia,
date de test etc.) care însoţesc această lucrare să fie utilizate în cadrul Facultăţii de
Informatică.
De asemenea, sunt de acord ca Facultatea de Informatică de la Universitatea
„Alexandru Ioan Cuza” Iași să utilizeze, modifice, reproducă şi să distribuie în
scopuri necomerciale programele-calculator, format executabil şi sursă, realizate de
mine în cadrul prezentei lucrări de licenţă.
Iaşi,
Absolvent Hogaș Vlad Marius
_________________________
(semnătura în original)
5
Cuprins Introducere ................................................................................................................................................... 6
Motivație ................................................................................................................................................... 6
Context ...................................................................................................................................................... 7
Cerințe funcționale ................................................................................................................................... 7
Abordare tehnică ...................................................................................................................................... 8
Contribuții ................................................................................................................................................... 10
1 Informații generale despre arhitectură și joc .................................................................................... 11
1.1 Aplicații asemănătoare ............................................................................................................... 11
1.2 Implementare ............................................................................................................................. 12
1.3 Arhitectura aplicației .................................................................................................................. 13
2 Dezvoltarea aplicației client și al jocului ............................................................................................ 17
2.1 Interfețele jocului ....................................................................................................................... 19
2.1.1 Interfața de autentificare și înregistrare ............................................................................ 19
2.1.2 Interfața meniu ................................................................................................................... 21
2.1.3 Interfața de configurare a jocului ....................................................................................... 23
2.1.4 Interfața de așteptare a jucătorilor .................................................................................... 24
2.1.5 Interfața de joc .................................................................................................................... 26
2.2 Modul singleplayer și aplicarea tehnicilor de inteligență artificială ......................................... 31
2.3 Modul multiplayer și comunicarea în timp real dintre client și server ..................................... 36
3 Dezvoltarea aplicației server .............................................................................................................. 39
3.1 Cererile HTTP............................................................................................................................... 40
3.2 WebSockets ................................................................................................................................. 41
3.3 Validarea informațiilor primite prin WebSocket de la jucător ................................................. 43
Concluzii generale ....................................................................................................................................... 46
Bibliografie .................................................................................................................................................. 47
6
Introducere
WarTank este un joc cu o grafică 2D care poate fi jucat în browser de una sau mai multe
persoane. Este o variantă modificată și îmbunătățită a jocului Battle City, joc care a apărut pentru
prima oară în anul 1985 pe consolele de jocuri Family Computer de la Nintendo. În România a fost
cunoscut sub denumirea de Tank A 1990, o versiune modificată neoficial a jocului Battle City,
fiind jucat pe o consolă foarte populară în România, numită Terminator. (1)
Jucătorul, cel care controlează un tanc, trebuie să distrugă tancurile inamice controlate de
calculator sau de alți jucători reali, pe o hartă aleatorie cu diferite tipuri de teren şi obstacole care
pot fi distruse ori peste care nu poți trece. Jocul are mai multe tipuri de puteri pe care le pot avea
tancurile sau care pot apărea pe hartă.
Motivație
Nu sunt un amator al jocurilor de tip browser, dar am vrut să folosesc JavaScript-ul şi la
altceva față de până acum, și anume dezvoltarea unor funcționalități pentru pagini web și aplicații.
Am ales acest tip de joc pentru că a fost unul dintre jocurile mele preferate în copilărie și
știu că îi pot aduce îmbunătățiri, iar în acelasi timp el să rămână la fel de simplu și distractiv, fără
să îți ocupe prea mult din timpul liber jucându-l.
Statisticile arată o creștere treptată a jocurilor sociale din anul 2012, unde veniturile
dezvoltatorilor de jocuri din Statele Unite erau de 4,55 miliarde de dolari, aşteptându-se să crească
până la peste 12 miliarde în 2017. (2)
7
Figura 1: Veniturile dezvoltatorilor de jocuri din Statele Unite
Context
Jocurile sunt activitatea recreațională și distractivă a omului dintotdeauna, indiferent de
vârstă. Jocurile video au apărut în jurul sfârșitul secolului XX și au devenit rapid foarte apreciate
odată cu apariția calculatorului și a consolelor de jocuri. Ele au avut un loc special în copilăria
tuturor și chiar şi în ziua de astăzi, când am devenit mai ocupați, tot încercăm să ne bucurăm măcar
puțin de ele, dorind să ne amintim puțin de copilărie.
Jocurile video pe calculator și pe console sunt mai populare decât cele pe care le poţi juca
în browser, însă cu încetul acest aspect a început să se schimbe datorită faptului că nu mai trebuiesc
instalate pe sistem și pot fi jucate folosind doar o conexiune la internet. Practic este ceea ce numim
acum “jocuri sociale”, care îti dau sansa să joci într-un mod de colaborare, rare fiind cele jucate în
modul singleplayer.
Cerințe funcționale
Înregistrarea și autentificarea
Utilizatorii se vor putea înregistra și autentifica folosind o adresa de e-mail, pe care o pot
confirma în maxim o săptămână accesând un link trimis pe adresa lor de email.
8
Meniul jocului
Jucătorii vor putea alege să joace jocul singleplayer, jucând împotriva mai multor tancuri
controlate de calculator sau multiplayer jucând împotriva altor tancuri controlate de alți jucători,
prin simpla trimitere a unul link unic.
Accesând un alt meniu vor putea alege din mai multe tipuri de tancuri cu diferite
caracteristici; spre exemplu unul poate avea mai mult blindaj, dar poate fi mai încet, în timp ce un
altul va avea viteză mai mare, însă va lua mai puțin din viață.
Se va putea alege și dintre hărți, altele fiind mai complicate, fiind necesar să te miști mai
mult până se va ajunge la adversar, altele mai simple, care vor avea mai puține obstacole.
Autentificarea sau înregistrarea și trecerea prin meniu se cere a fi foarte rapidă, deoarece
vorbim de un joc ce poate fi jucat într-o scurtă pauză de lucru.
Jocul
Deoarece acest joc se va putea juca și singleplayer, trebuie să includem în dezvoltarea lui
și un sistem de IA (Inteligență Artificială) pentru tancurile controlate de calculator.
Partea de multiplayer trebuie să conțină o metodă de invitație a jucătorilor și o etapă a
jocului unde se va aștepta să între toți jucătorii pentru a începe simultan jocul.
Abordare tehnică
Javascript
Ca limbaj de programare, atât pentru partea de server, cât și pentru cea de client voi folosi
Javascript. Creat de Netscape în anul 1995 ca o extensie HTML pentru Netscape Navigator 2.0,
Javascript a avut ca funcție principală manipularea documentelor HTML și de validare a
formularelor. Din cauza numelor similare, lumea confundă Javascript cu Java. Cu toate că ambele
au structura lexicală a limbajelor de programare, ele sunt două limbaje diferite. Spre deosebire de
C, C# și Java, Javascript este un limbaj interpretat, asta însemnând că are nevoie de un
“interpretor”; în cazul de față Javascript este interpretat și executat de browser. (3)
9
Node.js
Node.js este o platformă software ce utilizează Javascript ca limbaj de scripting și este
folosită pentru a construi aplicații de rețea scalabile, în special aplicațiile de comunicare și jocurile
de tip browser, deoarece amândouă necesită o comunicare în timp real dintre server și un număr
foarte mare de clienți. (4)
Phaser
Phaser este un framework gratuit și rapid pentru crearea de jocuri HTML5 pentru browser.
El te ajută să grăbești procesul de dezvoltare, scăpându-te de părțile plictisitoare în dezvoltarea
unui joc. Am ales Phaser pentru că este unul dintre cele mai populare framework-uri, cu o
comunitate activă și cu un număr mare de plugin-uri. (5)
Socket.IO
Este o librărie Javascript pentru aplicațiile care necesită o comunicare bidirecțională între
server și client. Această librărie este formată din două părți: o librărie care rulează în browser pe
partea de client și o librărie pentru Node.js care rulează pe partea de server și este nevoie de
amândouă pentru a funcționa. (6)
MySQL
Pentru stocarea datelor voi folosi un sistem de gestiune a bazelor de date numit și MySQL,
cel mai popular sistem open-source la ora actuală, fiind folosit și de site-urile mari precum
Facebook, Twitter, YouTube, Yahoo! și multe altele. (7)
HTML5
HTML5 este ultima îmbunătățire a standardului care definește HTML-ul. Noul termen
reprezintă versiunea nouă a HTML-ului, prin noi elemente, atribute și comportamente, dar și printr-
un set mai larg de tehnologii ce permite o diversitate mai mare și mai puternică de site-uri și
aplicații web.
10
Contribuții
Lucrarea este structurată în 3 capitole, primul capitol conținând informații generale despre
aplicație și arhitectura acesteia, care este una de tip client-server, iar în celelalte două capitole sunt
oferite detalii de implementare și vor fi explicate în mod separat contribuțiile mele în procesul de
dezvoltare pe partea de client și pe cea de server.
Capitolul 1: Informații generale despre arhitectură și aplicație
Capitolul 2: Dezvoltarea aplicației client și a jocului - va conține trei subcapitole la care am
avut cea mai mare contribuție, unde am încercat să dobândesc cele mai multe cunoștințe și să-mi
satisfac unele curiozități:
utilizarea unei biblioteci pentru crearea de jocuri HTML5;
utilizarea unui sistem de reguli în modul singleplayer pentru dezvoltarea părții de
inteligență artificială;
comunicarea în timp real a mai multor clienți cu server-ul în modul multiplayer.
Capitolul 3: Dezvoltarea aplicației server
În dezvoltarea aplicației server m-am axat mai mult pe comunicarea dintre client și server,
atât prin cereri de tip HTTP, cât și prin cea în timp real cu ajutorul protocolului WebSockets:
înregistrarea și autentificarea clienților folosind cereri HTTP;
utilizarea unui sistem împotriva trișorilor și validarea informațiilor primite prin
WebSocket de la jucător.
11
1 Informații generale despre arhitectură și joc
WarTank este un joc multiplayer de tipul “arcade” și de acțiune, în care se pune accentul
pe provocările fizice, inclusiv coordonarea mână-ochi și reflexe, iar scopul jucătorului este de a
avea un scor cât mai mare prin distrugerea inamicilor. Jocul poate fi jucat împotriva prietenilor sau
a oricărei persoane care are o conexiune la internet și un browser, dar și în modul singleplayer,
adică împotriva tancurilor controlate de calculator.
1.1 Aplicații asemănătoare
Înainte de a începe dezvoltarea proiectului am căutat jocuri asemănătoare, pentru a vedea
ce funcționalități au, pe care dintre ele pot să le dezvolt și ce anume aș putea să adaug. Cele mai
interesante sunt:
Massive Tanks - massivetanks.com
Jocul se poate juca numai multiplayer, de către toată lumea care accesează site-ul. Nu poți
selecționa persoanele cu care să joci, nu are autentificare și nu există mai multe tipuri de
tancuri cu diferite caracteristici.
Tinny Tanks - multiplayer.gg
Acest joc poate fi jucat atât singleplayer, cât și multiplayer, are o multitudine de
funcționalități, de la crearea hărților până la upgradarea tancului, obstacolele sunt
instrumente de birou și nu pot fi distruse, iar proiectilele sunt respinse.
Figura 2: Jocul Massive Tanks Figura 3: Jocul Tinny Tanks
12
1.2 Implementare
La începerea proiectului am utilizat un generator Yeoman pentru creare de jocuri HTML5
cu Phaser, care m-a ajutat cu structura de bază a jocului și cu un cod sursă inițial, fiind folosite cele
mai bune practici.
Yeoman pune la dispoziție o listă de generatoare care ajută la demararea unui proiect rapid,
folosind cele mai bune practici și instrumente pentru a rămâne productiv. Un generator este o schelă
de proiect completă care vine cu anumite instrumente utile.
Dacă se dorește folosirea unuia dintre generatoarele puse la dispoziție pentru a începe un
proiect nou, trebuie mai întâi instalat Node.js și managerul de pachete NPM, apoi instalate
modulele necesare pentru NPM, folosind comanda npm install -g yo generator-phaser, care
instalează global Yeoman (librărie ce ajută la crearea proiectelor) și generatorul pentru Phaser, apoi
se poate invoca generatorul și crea schela proiectului folosind comanda yo phaser.
Pentru îmbunătățirea productivității în dezvoltarea unei aplicații web, fluxul de lucru
Yeoman cuprinde trei tipuri de instrumente:
instrumentul pentru schela proiectului (yo);
instrumentul pentru build, adică pregătirea proiectului pentru producție (Gulp,
Grunt, etc);
managerul pentru pachete de librării sau scripturi (NPM sau Bower).
Se poate vizualiza jocul în browser prin rularea comenzii npm start în directorul
proiectului, iar datorită unui pachet din cele instalate odată cu generatorul, numit budo, se va porni
un server de dezvoltare care va actualiza pagina web în timp ce se salvează fișierele.
Când proiectul este terminat sau se dorește să fie mutat în producție ceea ce este gata, se
rulează comanda npm run build și se vor creea fișierele care se pot urca pe server, în folderul
/build. Acțiunea comprimă și minimizează fișiere individuale într-un fișier mai mare, care poate fi
descărcat mai repede la accesarea pagini web, iar de aceasta se ocupă următoarele librării instalate
de managerul de pachete NPM pentru partea de dezvoltare:
clean-css este o librărie rapidă și eficientă pentru comprimarea fișierelor CSS;
aceasta ajută la o încărcare mai rapidă a site-ului;
13
htmlprocessor procesează fișierele html prin utilizarea comentariilor speciale;
mkdirp crează directoare și subdirectoare necesare, ca de exemplu directorul build
împreună cu restul subdirectoarelor;
ncp este o unealtă pentru copierea fișierelor asincron, de exemplu mută toate
fișierele necesare în folderul /build;
uglify-js comprimă și minimizează fișierele javascript;
browserify este un sistem simplu, bazat pe procedura de încărcare a modulelor în
Node.js. Practic a fost portată metoda “require(‘module’)” din Node.js și au
implementat-o să funcționeze similiar, în browser, pe client. Acesta caută toate
apelurile “require()” din aplicație și crează un singur fișier (bundle) cu aceste
resurse, pe care îl încarcă ulterior în aplicație.
1.3 Arhitectura aplicației
Modelul client-server este o structură sau arhitectură care partajează procesarea între
furnizorii de servicii, numiți servere și elementele care solicită servicii, numite clienți. Clienții și
serverele comunică printr-o rețea de calculatoare, de obicei prin Internet, având suporturi hardware
diferite, dar pot rula și pe același sistem fizic. Un server (fizic) rulează unul sau mai multe programe
server, care partajează resursele existente cu clienții. Clientul nu partajează niciuna dintre resursele
proprii, ci apelează la resursele serverului prin funcțiile server. (8)
Pentru comunicarea între client și server am folosit configurația recomandată pe site-ul
celor de la Socket.IO pentru utilizarea lui cu Express. În această configurație Express este utilizat
pentru a trata cererile HTTP care vin în aplicația Node.js. Modulul Socket.IO se atașează de
Express și “ascultă” pe același port pentru conexiunile WebSocket de intrare.
Când un browser se conectează la aplicație, accesează index.html care îi “servește” toate
fișierele necesare de JavaScript și CSS pentru început. JavaScript-ul în browser va iniția o
conexiune spre Socket.IO care va crea o conexiune WebSocket. Fiecare conexiune WebSocket are
un identificator unic pentru ca Node.js și Socket.IO să țină evidența cui i se trimite mesajul.
14
Figura 4: Modelul și arhitectura client-server
Comunicarea trebuie să fie una în timp real, dar trebuie să specificăm că browser-ele nu
comunică direct între ele, ci vor transmite mesaje către server, care sunt procesate și retransmise
înapoi la browser-ele adecvate. Toate aceste mesaje de la browser conțin informații despre jucător,
despre poziția lui, despre statusul lui, iar de la server vor fi trimise la alți jucători.
Mesajele care “curg” de la client la server și înapoi pot fi puțin mai greu de urmărit, mai
ales când sunt conectați mai mulți clienți odată. Din fericire, există un instrument foarte util în
browser-ul Google Chrome. Dacă deschideți fereastra cu instrumente pentru dezvoltatori și mergeți
la fila Network, veți putea monitoriza tot traficul WebSocket pentru respectiva fereastră făcând
click pe butonul WS de-a lungul barei de instrumente de jos.
15
În panoul WebSocket apare în coloană din stânga o listă de conexiuni. Dacă apăsați pe una
din conexiuni, apoi pe fila Frames se va afișa o listă de mesaje ce au avut loc prin acea conexiune
WebSocket. În imaginea de mai jos se poate vedea un exemplu:
Figura 5: Monitorizarea traficului și a schimbului de mesaje WebSocket
Modulele Express si Socket.IO nu vin la pachet cu Node.js, ele sunt dependențe externe
care trebuiesc descărcate și instalate separat. NPM (Node Package Manager) se ocupă de asta
folosind doar comanda npm install atâta timp cât toate dependențele sunt listate in fișierul
package.json. Acesta se poate găsi în rădăcina directorului de proiect și conține următorul fragment
JSON pentru a defini dependențele de proiect:
"dependencies": {
"async": "^2.1.4",
"body-parser": "^1.15.2",
"express": "^4.14.0",
"express-validator": "^3.1.2",
"mysql": "^2.12.0",
"nodemailer": "^2.7.0",
"phaser": "^2.6.2",
"socket.io": "^1.7.2"
}
Secțiune de cod 1: Fragment JSON pentru a defini dependențele de proiect din package.json
16
Pe lângă Express și Socket.IO mai sunt instalate și alte librării pe care le folosim în proiect:
async este un modul util și simplu pentru a lucra cu partea asincrona de Javascript;
body-parser este folosit pentru parsarea cererilor primite prin HTTP, iar în
proiectul de fața el se atașează la librăria Express;
express-validator validează parametrii primiți din cererile de la HTTP și se
atașează la fel de librăria Express. De exemplu, el se ocupă de validarea email-ului
la înregistrare, dar și de mulți alți parametri;
mysql este un modul de Node.js ce implementează protocolul MySQL. Cu ajutorul
lui putem crea o conexiune la baza de date în care putem să executăm interogări
SQL;
nodemailer este un modul cu ajutorul căruia se pot trimite email-uri simplu,
folosind protocolul SMTP;
phaser este biblioteca jocului.
17
2 Dezvoltarea aplicației client și al jocului
Folosind un generator Yeoman pentru un start mai rapid cum am specificat și în Capitolul
1, structura fișierelor și a folderelor a fost creată automat folosind cele mai bune practici pentru
dezvoltarea jocului. Se poate observa în imaginea de mai jos structura folderelor și a fișierelor:
Figura 6: Structura fișierelor
assets – aici se află toate imaginile si fișierele de configurare JSON necesare pentru
aplicație;
build – după rularea comenzii npm run build aici se vor găsi toate fișierele care
trebuie urcate în producție pentru rularea aplicației;
css – folderul cu toate fișierele CSS;
node_modules – folderul cu toate modulele instalate de NPM (ex: phaser, socket.io,
express etc);
server – fișierele javascript pentru partea de server a aplicației;
src – fișierele javascript pentru partea de client care include codul jocului;
index.html – primul fișier accesat în momentul în care un browser se conectează la
aplicație;
package.json – conține atât date despre aplicație (ex: nume, versiune) cât și numele
și versiunea pentru toate modulele și bibliotecile necesare ca aplicația să ruleze.
Pentru a începe procesul de dezvoltare al jocului trebuie să înțelegem mai întâi câteva
caracteristici de bază a bibliotecii Phaser.
18
Phaser este o bibliotecă pentru jocuri HTML5 care ajută dezvoltatorii la crearea de jocuri
foarte complexe, într-un timp cât mai scurt, singura cerință a browser-ului este să suporte tag-ul
HTML canvas.
Inițial canvas-ul a fost creat de Apple, în 2004, pentru a putea implementa widget-urile din
dashboard și pentru a suporta grafica în browser-ul Safari. Ulterior a fost adoptat de Firefox, Opera
și Google Chrome, pentru ca în prezent, să facă parte din specificațiile HTML5 și să fie folosit în
interiorul unui document HTML cu scopul de a desena folosind JavaScript. (9)
Se poate vedea cum prinde totul viață în următoarele linii de cod din fișierul main.js,
folderul src:
const game = new Phaser.Game(1024, 608, Phaser.AUTO, 'wartank-game');
game.state.add('login', require('./states/login'));
game.state.add('menu', require('./states/menu'));
game.state.add('menu-select', require('./states/menu-select'));
game.state.add('multiplayer', require('./states/multiplayer'));
game.state.add('game', require('./states/game'));
game.state.start('login');
Secțiune de cod 2: Codul din main.js
În prima linie de cod se crează o instanță a obiectului Phaser.Game ce se atribuie unei
constante numită game.
Primii doi parametri sunt lățimea și înălțimea elementului canvas, în acest caz, 1024 x 608
pixeli. Aceasta este dimensiunea în care va fi afișat jocul, însă acesta poate fi mult mai mare. Al
treilea parametru poate fi Phaser.CANVAS, Phaser.WEBGL sau Phaser.AUTO, cel din urmă fiind
contextul de randare pe care doresc să îl utilizez. Parametrul recomandat este Phaser.AUTO care
încearcă în mod automat să utilizeze WebGL, dar în cazul în care browser-ul sau dispozitivul nu
acceptă, va folosi canvas. Cel de-al patrulea parametrul este id-ul elementului DOM în care este
inserat elementul canvas.
O funcționalitate interesantă pe care o are Phaser este managerul de stări (en: states
management). Astfel dacă vrei să împarți jocul în blocuri de cod sau interfețe, pe fiecare dintre
aceste interfețe o poți dezvolta ca o stare (en: state) și la fiecare schimbare de stare se eliberează
memorie, resurse și se gestionează memoria de obiecte nefolosite din aplicație (en: manages
garbage collection) care cu siguranță e un plus.
19
2.1 Interfețele jocului
Jocul este împărțit în 5 stări numite și interfețe: login, menu, menu-select, multiplayer și
game. Primele 4 stări fac parte din meniul jocului, iar game este jocul în sine. Login este starea
care se încarcă automat la accesarea jocului.
2.1.1 Interfața de autentificare și înregistrare
Figura 7: Interfața de autentificare și înregistrare
Această interfață este starea login din managerul de stări Phaser care se încarcă automat la
accesarea aplicației. Aici jucătorul se va putea autentifica sau să-și creeze un cont nou. În cazul în
care jucătorul este deja autentificat, în locul formularului de autentificare îi va apărea numele și un
button de Play pentru a trece la interfața următoare.
La apăsarea butonului LOGIN sunt trimise datele formularului printr-o cerere HTTP de tip
POST la server, folosind funcția jQuery.post() care este o prescurtare a funcției jQuery.ajax() din
biblioteca jQuery.
20
$.post(config.SERVER + '/login', $('.login-form').serialize(), function (data)
{
if(data.success){
Cookies.set('user', data.data, { expires: 7 });
self.displayLoggedIn(data.data.username);
} else {
$('.login-page .error-mess').text(data.message).show();
}
});
Secțiune de cod 3: Cererea HTTP de tip POST pentru autentificare
În funcție de răspunsul primit de la server care este de tip JSON, ori afișăm mesajul de
eroare dacă autentificarea nu a fost realizată cu succes, ori stocăm cheia de acces primită într-un
cookie care expiră în 7 zile și afișăm numele utilizatorului împreună cu butonul de PLAY pentru
trecerea la următoarea interfață.
Figura 8: Autentificare sau înregistrare reușită
Un cookie este un fișier creat de un site web pentru a stoca informații pe calculatorul pe
care îl accesează. Cookie-urile sunt folosite pentru autentificare precum și pentru urmărirea
comportamentului utilizatorului.
Cheia de acces primită este un șir aleatoriu de litere și numere și este salvată într-un cookie
pentru ca jucătorul să nu mai trebuiască să se autentifice de fiecare dată când accesează jocul de pe
același calculator pentru o perioadă de timp. La fiecare accesare verificăm dacă există o cheie pe
browser-ul utilizatorului, iar dacă există, o trimitem server-ului să vedem dacă acea cheie este
validă și cărui jucător îi aparține.
21
Înregistrarea jucătorului se face prin accesarea formularului de înregistrare, unde datele sunt
transmise către server aproape la fel ca la autentificare, iar după validarea lor aceleași acțiuni sunt
luate în funcție de răspunsul primit.
În spatele panoului de autentificare și înregistrare rulează un “demo” identic cu cel din
interfața de joc, doar cu câteva tancuri controlate de calculator.
2.1.2 Interfața meniu
Figura 9: Interfața meniu
Sursa imaginii mecanicului de tanc - https://www.artstation.com/artwork/qQn8a
În interfața meniu, care corespunde cu starea menu, nu avem decât câteva imagini de design
și două butoane în care sunt prezentate cele două moduri în care poate fi jucat jocul, și anume
modulul singleplayer și cel multiplayer.
Phaser are câteva funcții rezervate care sunt executate la anumite puncte din joc și ar trebui
să conțină diferite tipuri de cod. Mai jos este o listă cu funcțiile folosite pe parcursul proiectului:
init() - Este prima funcție apelată la încărcarea unei interfețe. Este apelată înainte
de preload(), create() sau oricare altă funcție. În aceasta sunt primite ca parametru
variabilele trimise din alte interfețe.
22
preload() - Această funcție este folosită pentru încărcarea imaginilor și restul
datelor necesare jocului.
create() - Este apelată după ce funcția preload() s-a terminat de executat și de
încărcat. Aici putem să poziționăm în joc imaginile aduse în fucția preload() și să
pregătim alte date.
update() - Această funcție este apelată la fiecare cadru, deci pe un calculator, asta
ar însemna aproape de 60 de ori pe secundă. Aici putem muta jucătorul pe hartă sau
verifica obiectele de coliziune. Aici se întâmplă cele mai interesante lucruri.
Pentru că această interfață are cel mai puțin cod, voi prezenta structura de bază a unei stări
numită de mine interfață pentru că fiecare este diferită prin conținut și funcționalitate.
function Menu() {
}
Menu.prototype.init = function(){
}
Menu.prototype.preload = function () {
var game = this.game;
game.load.image('tank-driver', 'assets/tank-driver-resize.jpg');
game.load.image('logo', 'assets/menu-logo.png');
game.load.image('menu-single', 'assets/menu/menu-single.png');
game.load.image('menu-multi', 'assets/menu/menu-multi.png');
}
Menu.prototype.create = function () {
var game = this.game;
$('.login-page').remove();
$('.backdrop').remove();
game.add.sprite(0, 0, 'tank-driver');
game.add.sprite(430, 45, 'logo');
game.add.button(510, 200, 'menu-single', this.menuButtonAction, this);
game.add.button(510, 300, 'menu-multi', this.menuButtonAction, this);
};
Menu.prototype.update = function(){
}
Menu.prototype.menuButtonAction = function(){
this.game.state.start('menu-select', true, false, arguments[0].key);
};
module.exports = Menu;
Secțiune de cod 4: Codul din interfața meniu
Indiferent pe care din cele două butoane din meniu se dă clic, următoarea stare este menu-
select, unde este configurat jocul înainte de începerea lui prin alegerea tancului și a hărții.
23
2.1.3 Interfața de configurare a jocului
Figura 10: Interfața de configurare a jocului
Această interfață corespunde cu starea menu-select. Aici găsim meniul de unde putem
selecta tancul și harta pe care vrem să jucăm.
Cele patru tipuri de tancuri se deosebesc atât prin design, cât și prin diferite caracteristici,
ca de exemplu unul va avea mai mult blindaj, dar va fi mai încet, altul va avea viteză mai mare, dar
va lua mai puțin din viață etc. Mai jos avem o poză cu tipurile de tancuri și caracteristicile acestora.
Figura 11: Tipurile de tancuri
Sursa pentru design-ul tancurilor - http://opengameart.org/content/top-down-painted-tanks
24
Hărțile diferă prin mărime sau prin tipul și așezarea obstacolelor. Fiecare hartă poate avea
mai multe tipuri de obstacole, care se pot distruge mai greu sau mai ușor și au un număr predefinit
de inamici pentru jocul singleplayer.
La apăsarea butonului PLAY, în funcție de modul de joc selectat în meniul anterior,
jucătorul este trimis spre interfața de joc, unde începe jocul, dacă a ales modul singleplayer sau
spre interfață de așteptare a jucătorilor, numită și lobby, dacă a ales modul multiplayer.
2.1.4 Interfața de așteptare a jucătorilor
Figura 12: Interfața de așteptare a jucătorilor
Această interfață există doar pentru modul de joc multiplayer și este starea multiplayer din
managerul de stări Phaser, ce în diferite jocuri se mai numește și lobby. Pe scurt este un spațiu unde
jucătorii se adună și după ce toate cerințele sunt îndeplinite, jocul poate fi pornit de cel care creează
jocul.
În majoritatea jocurilor, lobby-ul este creat de un jucător care are control asupra lui. În jocul
acesta, proprietarul lobby-ului alege harta pe care o să joace și cu cine vrea să joace prin trimiterea
unui link unic celorlalte persoane.
La accesarea acestei interfețe, o conexiune WebSocket se inițializează cu serverul și se va
emite evenimentul newGame, împreună cu id-ul și numele de utilizator al jucătorului, cu tancul și
harta selectate și statusul dacă e pregătit sau nu să înceapă jocul.
25
Server-ul generează un token1 și creează o cameră folosind token-ul ca id al camerei pe care
îl trimite înapoi proprietarul lobby-ului. Conceptul de cameră este construit în Socket.IO, dar mai
multe informații despre acest concept veți putea găsi în capitolul despre dezvoltarea aplicației
server.
La primirea tokenului, acesta este afișat sub forma unui link, de exemplu
http://wartank.com/?gametoken=3f0baf6697 care poate fi trimis altor jucători pentru a se alătura
lobby-ului.
La accesarea acestui link, jucătorul trebuie să se autentifice sau să se înregistreze și va fi
trimis direct spre interfață de lobby, unde de asemenea se va crea o conexiune WebSocket. În
schimb, în loc de emiterea evenimentului de newGame, se va emite evenimentul joinGame, cu
datele utilizatorului, iar în loc de harta selectată se va trimite token-ul, ca serverul să știe în ce
cameră să îl atașeze.
socket.on('connect', function(){
console.log('WebSocket succesfully connected!');
if(self.queryUrl.gametoken){
socket.emit('joinGame',{
user : {
id: userData.id,
username: userData.username,
tank: userData.tank,
ready: false
},
gameToken: self.queryUrl.gametoken.replace('#', '')
});
} else {
socket.emit('newGame', {
user: {
id: userData.id,
username: userData.username,
tank: userData.tank,
ready: true
},
map: self.selectedMap
});
}
});
Secțiune de cod 5: Codul pentru inițializarea unei conexiuni la server și emitere a evenimentelor
de tip newGame si joinGame
Aici jucătorii vor putea să-și schimbe tancul cu care vor juca sau să-și schimbe statusul
când sunt pregătiți să înceapă jocul. La fiecare schimbare se vor emite diferite evenimente către
1 Este o cheie sau un șir unic de cuvinte și litere.
26
server, care la rândul lui va emite către ceilalți clienți din aceeași cameră sau lobby și datele sunt
actualizate automat în interfață.
După ce toți jucătorii au intrat și sunt pregătiți să înceapă jocul, proprietarul lobby-ului
apasă pe butonul PLAY care va emite un eveniment de începere a jocului, ce va fi transmis tuturor
jucătorilor din cameră și vor fi trimiși spre interfața de joc.
2.1.5 Interfața de joc
Figura 13: Interfața de joc
Aici este interfața de joc numită și starea game unde se joacă jocul propriu-zis. Structura
este asemănătoare cu cea din Secțiune de cod: 4.
Pentru început este apelată funcția init() în care atribuim unor variabile publice parametrii
primiți de la interfața precedentă, tipul tancului, harta și dacă este modul de joc multiplayer se mai
trimite și obiectul cu conexiunea la websocket ca să nu mai creem conexiunea încă odată. Apoi
este apelată funcția preload(), unde sunt încărcate de pe server sau din cache2 toate imaginile,
sprite-urile3 și fișierele JSON de configurație a tancurilor și a hărților.
2 Este o colecție de date ce sunt o copie la indigo a valorilor originale, unde operația de aducere este mai costisitoare. 3 Este o colecție de imagini puse într-o singură imagine.
27
După ce toate fișierele au fost încărcate în funcția preload(), este apelată funcția create(),
unde ne folosim de datele și imaginile încărcate pentru a desena harta și tancurile.
Mai jos am creat o instanță pentru unul din cele trei sisteme de fizică, furnizate de către
biblioteca Phaser, fiecare cu propria lui utilizare, în care avem avantaje și dezavantaje:
game.physics.startSystem(Phaser.Physics.ARCADE);
Sistemul de fizică Arcade (Phaser.Physics.ARCADE) este probabil cel mai folosit de către
dezvoltatorii de jocuri pentru că se găsesc cele mai multe tutoriale și este cel mai simplu și mai
rapid sistem în ceea ce privește performanța. Este un sistem care permite accelerația, viteza,
coliziunea și multe altele, dar la un nivel rudimentar deoarece totul este văzut ca un dreptunghi.
Sistemul de fizică Ninja (Phaser.Physics.NINJA) este un pic mai complex decât sistemul
de fizică Arcade. Cea mai mare diferență între cele două sisteme este că permite coliziunea cu
pante. Folosind acest sistem se poate crea o platformă cu dealuri și rampe, pe când sistemul Arcade
este unul destul de pătrățos.
Sistemul de fizică P2 (Phaser.Physics.P2JS) este cel mai complex și mai realistic, dar
consumă și cele mai multe resurse. Folosind acest sistem se pot crea arcuri, pendule și altele.
Un obiect din joc, de exemplu un sprite, poate aparține doar unui sistem de fizică, dar pot
exista obiecte cu sisteme diferite într-un joc. De exemplu se poate avea sistemul de fizică P2 pentru
gestionarea unui teren construit cu ajutorul poligoanelor, peste care trece un vehicul, în timp ce
trage cu gloanțe ce se folosesc de sistemul de fizică mult mai rapid Arcade.
Pentru desenarea hărții în funcția create() trebuie să creem o instanța a obiectului Map din
/src/prefabs/map.js, iar în constructorul lui se declară proprietatea mapData cu JSON-ul de
configurare a hărților și se setează limitele lumii jocului.
28
{
"name": "Default",
"backgroundColor": "#2d2d2d",
"backgroundImage": null,
"width" : 1024,
"height": 608,
"data" : [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,
16, 16, 0, 0],
[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,
16, 16, 0, 0],
[0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 8, 0,
0, 0],
[0, 0, 0, 8, 0, 0, 0, 1, 1, 1, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 1, 1, 1, 0, 0, 0, 8, 0,
0, 0],
[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,
0, 0],
[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,
0, 0],
[16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 0,
0, 0, 0, 0, 16],
[16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 0,
0, 0, 0, 0, 16],
[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,
0, 0],
[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,
0, 0],
[0, 0, 0, 8, 0, 0, 0, 1, 1, 1, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 1, 1, 1, 0, 0, 0, 8, 0,
0, 0],
[0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 8, 0,
0, 0],
[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,
16, 16, 0, 0],
[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,
16, 16, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0,
0, 0]
],
"health": {
"obstacle1" : 100,
"obstacle8" : 1000,
"obstacle16" : 250
},
"enemiesNr": 5
}
Secțiune de cod 6: JSON de configurare a hărții
WarTank este un joc bazat pe plăci (en. Tile-based video game), asta înseamnă că aria unui
joc este formată din mici forme dreptunghiulare, pătrate care sunt asemănătoare unei piese de
puzzle ce formează o imagine mult mai mare.
În acest joc, harta este formată din plăci pătrate cu dimensiune de 32x32 pixeli. Pentru
așezarea obstacolelor pe hartă am folosit o matrice bidimensională; fiind un joc 2D, sistemul de
coordonate este unul bidimensional format din două axe x și y.
29
Așadar în JSON-ul de configurare a hărții, în data avem matricea bidimensională ce este
plină cu numere întregi, care dacă sunt diferite de 0 indică că acolo trebuie poziționat un obstacol
cu id-ul lui.
Map.prototype.createObstacles = function () {
var obstacles = this.game.add.physicsGroup(Phaser.Physics.ARCADE);
for (var x = 0; x < this.mapData.data.length; x++) {
for (var y = 0; y < this.mapData.data[x].length; y++) {
if (this.mapData.data[x][y]) {
var c = obstacles.create(y * this.blockSize, x * this.blockSize,
'obstacle', this.mapData.data[x][y]);
c.maxHealth = c.health = this.mapData.health['obstacle' +
this.mapData.data[x][y]];
c.body.immovable = true;
//c.anchor.setTo(0.5, 0.5);
}
}
}
return obstacles;
}
Secțiune de cod 7: Așezarea obstacolelor pe hartă
Atribuim variabilei obstacles un obiect al jocului de tip Group care este un container pentru
obiectele de afișare și îi setăm un sistem fizic de tipul Arcade. Apoi iterăm matricea bidimensională
și când întâlnim un număr întreg diferit de 0 creăm un obiect de tipul Sprite care așează obstacolul
pe poziția lui, apoi îi setăm proprietățile health, maxHealth și immovable. Fiecare tip de obstacol
se distruge mai ușor sau mai greu, în funcție de ce valoare are la viață și nu trebuie să se miște când
este atins de un jucător.
La crearea obiectului de tipul Sprite pentru poziționarea obstacolului sunt trimiși următorii
parametri:
x – poziția pe axa x în pixeli (numărul plăcii pe axa x * dimensiunea plăcii);
y – poziția pe axa y în pixeli;
key – indexul pus la încărcarea imaginii sau a sprite-ului în funcția preload();
frame – în cazul în care a fost încărcat un sprite, aici este indexul imaginii dorite.
După ce am desenat harta jocului, tot în funcția create() trebuie să adăugăm tancul
jucătorului și cel al inamicilor, prin creare unei instanțe a obiectului Tank. Se generează aleatoriu
o direcție și o poziție, pe care să nu existe un obstacol ca să poată fi poziționat tancul, iar pentru
asta ne folosim de o metodă din obiectul map.
30
Map.prototype.spawnTankLocation = function () {
var location = {x: 0, y: 0};
do {
location.y = this.game.rnd.between(0, this.mapData.data.length - 1);
location.x = this.game.rnd.between(0, this.mapData.data[0].length - 1);
}
while (this.mapData.data[location.y][location.x] !== 0);
return {
x: this.blockSize * location.x + 18,
y: this.blockSize * location.y + 18,
direction: this.game.rnd.pick(['right', 'left', 'up', 'down'])
};
}
Secțiune de cod 8: Căutarea unei poziții fără obstacol în matricea bidimensională
În obiectul Tank se declară o proprietate care conține toate setările tancului ce sunt calculate
din JSON-ul de configurare tancuri. În JSON avem valorile maxime admise pentru viața, rata de
foc, viteza, viteza proiectil și puterea de distrugere, iar pentru fiecare tip de tanc avem un procent
din valoarea maximă.
Mișcarea tancului se face prin apăsarea tastelor de navigare (cele cu săgeți) de pe tastatură
și totul este controlat în funcția update(), care este apelată de 60 de ori pe secundă. Aici se verifică
de fiecare dată dacă o tastă este apăsată și i se oferă tancului accelerație în direcția dorită.
this.tank.body.velocity.x = 0;
this.tank.body.velocity.y = 0;
if (cursors.left.isDown) {
this.tank.body.velocity.x = -this.tankSettings.speed;
this.tankDirection = 'left';
this.tank.angle = 180;
}
else if (cursors.right.isDown) {
this.tank.body.velocity.x = this.tankSettings.speed;
this.tankDirection = 'right';
this.tank.angle = 0;
}
else if (cursors.up.isDown) {
this.tank.body.velocity.y = -this.tankSettings.speed;
this.tankDirection = 'up';
this.tank.angle = 270;
}
else if (cursors.down.isDown) {
this.tank.body.velocity.y = this.tankSettings.speed;
this.tankDirection = 'down';
this.tank.angle = 90;
}
Secțiune de cod 9: Codul care se ocupă de mișcarea și direcția tancului
31
Pentru detectarea coliziunii dintre tanc și obstacol am folosit funcția collide() din sistemul
de fizică Arcade al jocului, care se poate folosi atât între simple instanțe de obiecte ale jocului, cât
și între o instanță de obiect și un grup de obiecte.
Sistemul de fizică face totul prea ușor, trebuie să adăugăm doar o simplă linie de cod în
funcția update().
this.game.physics.arcade.collide(this.tank, this.map.obstacles, this.tankHitObstacle,
null, this);
Secțiune de cod 10: Sistemul de detectare al coliziuni între tanc și obstacole
Primii doi parametri sunt obiectele jocului care trebuie verificate pentru detectarea
coliziunii, iar al treilea este funcția apelată când coliziunea este detectată.
2.2 Modul singleplayer și aplicarea tehnicilor de inteligență artificială
Într-un sens larg, cele mai multe jocuri încorporează o anumită formă de inteligență
artificială (IA), adică nu este acea formă academică, ci mai degrabă un amestec de tehnici care în
principal sunt preocupate de crearea iluziei de inteligență artificială.
De exemplu, dezvoltatorii s-au folosit de inteligența artificială ani de zile, pentru a da
aparent inteligență nenumăratelor caractere de joc, de la fantomele din clasicul joc Pac-Man până
la boții4 din jocul de acțiune cu împușcături Counter-Strike.
Unii dezvoltatori consideră că sarcinile cum ar fi căutarea unui traseu aparțin de inteligența
artificială a jocului. Steven Woodcok a raportat în “2003 Game Developer’s Conference AI
Roundtable Moderator’s Report” că unii dezvolatori cred că și detectarea de coliziune face parte
din inteligența artificială a unui joc, ceea ce arată că sunt o mulțime de interpretări exagerate.
Vom merge pe ideea că inteligența artificială în jocuri include totul, de la simpla urmărire
și evadare, la modelul de mișcare și multe altele.
4 Este un jucător controlat de calculator folosind inteligența artificială.
32
Ciclu de decizii al caracterului non-jucător (en: non-player character) execută trei pași în
mod constant: (10)
percepe - acceptă informații despre mediul înconjurător, hartă, inamic;
gândește - evaluează informațiile percepute și plănuiește conform regulilor;
acționează – execută planul;
Alții ar putea argumenta că această abordare este mult prea simplă și prin urmare ar putea
fi nepotrivită pentru crearea unei experiențe de joc plăcută, dar nu este așa. De fapt, jocurile video
nu au nevoie de caractere non-jucător care sunt la fel de sofisticate și capabile ca și jucătorii umani
care joacă. Jocurile sunt menite să fie distractive, iar sistemul de inteligență artificială nu ar trebui
să fie prea bun, asta ar face jocul imposibil de câștigat. În schimb, ar trebui să permită jucătorului
să câștige jocul în moduri interesante și provocatoare.
În general tehnicile de comportament în inteligența artificială pentru jocuri sunt de două
feluri: deterministe și nedeterministe. (11)
Determinist
Comportamentul determinist este specific și previzibil. Nu există nici o incertitudine. Un
exemplu simplu de comportament determinist este un algoritm simplu de urmărire. Se poate
programa un caracter non-jucător să se îndrepte spre un anumit punct țintă prin avansarea de-a
lungul axelor x și y până când coordonatele caracterului x și y coincid cu locația țintă.
Nedeterminist
Comportamentul nedeterminist este opusul comportamentului determinist, deoarece are un
grad de incertitudine și este oarecum imprevizibil. Un exemplu de comportament nedeterminist
este un caracter non-jucător care învață să se adapteze la tactică de luptă a unui jucător. O astfel de
învățare ar putea folosi o rețea neuronală, tehnică Bayesion sau un algoritm genetic.
În inteligența artificială tehnicile deterministe sunt cele mai folosite în devoltarea unui joc.
Aceste tehnici sunt previzibile, ușor și rapid de implementat, înțeles și testat. Deși sunt foarte
utilizate, metodele deterministe plasează sarcina de a anticipa și de a programa toate scenariile
explicite pe umerii dezvoltatorilor.
33
Metodele deterministe nu facilitează învățarea sau evoluția și după ce joci puțin, aceste
comportamente tind să devină previzibile. Acest lucru limitează perioada în care un joc este
distractiv și începe să devină plictisitor.
Probabil cea mai utilizată tehnică ce este folosită la scară largă în inteligența artificială este
trișarea. De exemplu, într-un joc ca al meu, calculatorul are acces la toate informațiile despre tancul
meu, de la poziția lui, tipul tancului, cât mai am din viață și până la direcția în care mă îndrept și
cu ce viteză. Trișarea în acest mod îl ajută pe caracterul non-jucător să aibă un avantaj în fața
inteligenței jucătorului uman, dar aceasta poate fi și ceva rău. Dacă este prea evidentă pentru
jucătorul uman această trișare, atunci el va crede că eforturile lui sunt zadarnice și își va pierde
interesul. Așa cum am explicat și mai sus, sistemul de inteligență artificială nu trebuie să fie prea
bun și acest mod de trișare pentru caracterele non-jucător trebuie să fie echilibrat destul cât să
creeze o suficientă provocare pentru jucător, dar îndeajuns cât să păstreze jocul interesant și
distractiv.
Clasicul joc Pac-Man îl face pe jucător să creadă că inamicii îl vânează, că fantomele sunt
un grup inteligent. De fapt, această percepție de grup inteligent este doar o iluzie. Pentru a fi siguri
că fantomele nu urmează toate același drum prin labirint și pentru a le oferi o personalitate
individuală, fiecăreia îi este oferită o variație ușor diferită a aceluiași algoritm, care este o selecție
foarte simplă de direcție de fiecare data când fantomele ajung la o intersecție în labirint.
În cazul în care se ajunge la o intersecție, fantoma trebuie să decidă dacă ar trebui să
schimbe direcția sau nu. Uneori fantoma își schimbă direcția ca să se miște spre jucătorul uman,
altădată alege o direcție aleatorie. Pentru a realiza acest comportament ușor diferit pentru fiecare
dintre fantomele create este folosit un sistem aleatoriu ce utilizează factori de ponderare unici
pentru fiecare dintre ele.
Astfel o fantomă se poate deplasa într-o direcție aleatorie 75% din timp și în direcția
jucătorului în celelalte 25% din cazuri, atunci când aceasta ajunge la o intersecție. O altă fantomă
ar avea alegerea aleatorie a direcției cu o pondere de 50% din timp și așa mai departe.
Rezultatul acestei metode simple este o personificare a fiecărei fantome de către jucător
(prin proiecție de subconștient) și va percepe comportamentul unei fantome ca cel al unui jucător
inteligent.
34
Diferența dintre mișcarea fantomelor în jocul Pac-Man nu este foarte mare față de tancurile
non-jucător din WarTank și am încercat deasemenea să le ofer câte o personalitate diferită pentru
a creea iluzia de inteligență.
Deoarece hărțile nu sunt sub forma unui labirint și tancurile au spațiu de mișcare mai mare,
direcția nu se poate schimba doar la întâlnirea unei intersecții, așa că am decis ca fiecare tanc să
aleagă o perioadă de timp aleatorie în care să verifice dacă să schimbe direcția sau nu. Așadar, la
fiecare schimbare de direcție, tancul își alege în mod aleatoriu un nou interval de timp din cele 4
intervale predefinite: 1000, 1500, 2000 și 2500 de milisecunde.
La alegerea direcției de mers am folosit un algoritm simplu de urmărire. Utilizând tehnica
de trișare, în care calculatorul știe poziția mea, am programat tancurile inamice să se îndrepte spre
mine prin avansarea de-a lungul axelor x și y până când una dintre axe este egală cu una din axele
mele. În acel moment tancul non-jucător se va opri, își va schimba direcția spre tancul cu jucător
uman și va continua să tragă până când tancul jucător se va mișca și nici una dintre axe x și y nu va
mai coincide.
Totuși am preferat că tehnica de trișare prin care calculatorul știe poziția mea să nu fie prea
evidentă, așa că în funcție de cadranul în care se află tancul jucătorului am decis să aleagă una din
cele două direcții și nu pe cea mai rapidă.
Figura 14: Cele patru cadrane ale unui sistem de coordonate carteziene
De fiecare dată când se alege o nouă direcție se verifică să nu fie blocată de un obstacol, iar
dacă este blocată, se verifică altă direcție până când se găsește una care nu este blocată de nici un
obstacol.
35
Se mai verifică de fiecare dată când este apelată funcția update(), adica de aproape 60 de
ori pe secundă, dacă tancul non-jucător este pe aceeași axă x sau y cu tancul jucătorului uman,
indiferent dacă timpul aleatoriu nu a trecut pentru o reverificare a direcției, tancul se oprește, se
îndreaptă spre tancul jucătorului uman și trage.
Tank.prototype.updateAIEnemyTank = function (playerTank) {
var directions, tankSpeed = this.tankSettings.speed;
this.game.physics.arcade.collide(this.tank, this.map.obstacles, this.tankHitObstacle, null,
this);
this.game.physics.arcade.overlap(this.projectiles, this.map.obstacles,
this.projectileHitObstacle, null, this);
this.tankMapLocation = this.map.spriteLocationOnMap(this.tank);
if(playerTank) {
directions = this.map.directionFromOneTankToAnother(this.tankMapLocation,
playerTank.tankMapLocation)
} else {
directions = [this.game.rnd.pick(['right', 'left', 'up', 'down']),
this.game.rnd.pick(['right', 'left', 'up', 'down'])];
}
if (this.game.time.now > this.enemyData.nextCheckDirection) {
this.enemyData.nextCheckDirection = this.game.time.now + this.game.rnd.pick([1000, 1500,
2000, 2500]);
this.tankDirection = this.map.checkAndGetDirections(directions, this.tankMapLocation);
this.tank.angle = this.tankAngleByDirection[this.tankDirection];
} else {
if(directions.length == 1){
this.tankDirection = directions[0];
this.tank.angle = this.tankAngleByDirection[this.tankDirection];
tankSpeed = 0;
}
}
this.tank.body.velocity.x = 0;
this.tank.body.velocity.y = 0;
if (this.tankDirection == 'left') {
this.tank.body.velocity.x = -tankSpeed;
}
else if (this.tankDirection == 'right') {
this.tank.body.velocity.x = tankSpeed;
}
else if (this.tankDirection == 'up') {
this.tank.body.velocity.y = -tankSpeed;
}
else if (this.tankDirection == 'down') {
this.tank.body.velocity.y = tankSpeed;
}
if(this.tank.alive){
this.fireProjectile();
}
}
Secțiune de cod 11: Codul pentru mișcarea tancurilor controlate de calculator
36
2.3 Modul multiplayer și comunicarea în timp real dintre client și server
Jocurile sunt destul de dificil de realizat. Senzația jocului trebuie să fie una plăcută, fizica
jocului să fie una lină, coliziunea să fie corectă și controlul să fie precis, iar toate aceste lucruri
necesită deja o cantitate foarte mare de muncă. Adăugarea unei componente multiplayer face acest
lucru mult mai complex, deoarece acum jucătorul trebuie să fie informat cu privire la acțiunile altor
jucători.
Modul în care un grup de jucători vor putea juca împreună este unul destul de simplu. Cum
am explicat și în subcapitolul 2.1.4 Interfața de așteptare a jucătorilor, jucătorul care accesează
jocul de pe un link care are un token de joc unic se alătură jocului creat de jucătorul care a generat
și a trimis link-ul la ceilalți jucători. Tot acest sistem funcționează ca un simplu sistem de lobby ce
este ilustrat mai jos.
Figura 15: Un sistem simplu de lobby și de comunicare client-server
37
La crearea conexiunii websocket în lobby, fiecare client va trimite token-ul sau cheia de
acces al user-ului pentru a demonstra că acea conexiune a fost făcută de un user autentificat.
Logica jocului pornește când toți jucătorii din lobby sunt pregătiți să înceapă și proprietarul
lobby-ului apasă pe butonul PLAY care va emite un eveniment de începere. După încărcarea
jocului, fiecare jucător va emite un eveniment gameLoaded, adică jocul a fost încărcat și sunt
pregătiți să înceapă; după ce toți jucătorii vor trimite acest eveniment, ei vor asculta evenimentul
startGame în care se vor primi toate pozițiile pe hartă a tancurilor și vor putea începe jocul.
În timpul jocului, clientul va emite următoarele evenimente prin websocket:
respawn – după ce tancul este distrus, la apăsarea tastei SPACE se va emite acest
eveniment în care clientul va cere să aducă la viață tancul distrus și să îl
repoziționeze pe hartă, iar server-ul va trimite această informație tuturor clienților
din camera jocului;
fire – acest eveniment se va emite tot la apăsarea tastei SPACE, dar când tancul este
în viață; evenimentul de tragere, care trimite la server poziția, direcția, accelerația
și cât din viață are tancul, va trimite mai departe la restul clienților această
informație;
movement – acest eveniment se va emite de fiecare dată când se va apăsa sau se va
elibera una din tastele de navigare5, și va trimite de fiecare dată aceleași informații,
ca și în cazul evenimentului fire, adică poziția, direcția, accelerația și cât din viață
are tancul.
După ce jocul a fost început, clientul va asculta numai evenimentul updatePlayer, unde vor
veni actualizările de la ceilalți jucători.
Când vine vorba de un joc multiplayer în timp real, logica în sine a jocului trebuie rulată
atât pe server, cât și pe client, deoarece serverul trebuie să fie autoritatea cu privire la starea de joc
în orice moment.
În cazul de față, logica jocului este rulată doar pe client și server-ul primește, verifică și
trimite informațiile celorlalți jucători.
5 Sunt tastele care au desenate pe ele săgeți.
38
Deoarece sunt trimise și primite informații despre tanc doar când se schimbă direcția și
accelerația, trebuie să rulăm animația tancului în care își schimbă poziția pe partea de client, acest
lucru fiind un sistem de prezicere a clientului, iar la următoarea actualizare trebuie reparate foarte
ușor greșelile de predicție, pentru a se păstra pozițiile tancurilor sincronizate, astfel rezolvându-se
problema timpului mare de răspuns în care se pot trimite informațiile.
Ce se poate întămpla dacă nu sunt reparate greșelile de predicție? De exemplu:
Clientul apasă tasta de navigare spre stânga, anunță serverul și în același timp începe
să mute tancul pe hartă;
După 100 de milisecunde informația ajunge pe server, unde este procesată și puțin
mai târziu o trimite celuilalt client;
La celălalt client informația ajunge după 150 de milisecunde, timp în care va începe
să mute tancul jucătorului inamic pe hartă.
Deja există o diferență de 250 de milisecunde care este insesizabilă, dar după câteva minute
de joc, tancurile nu vor mai fi sincronizate.
39
3 Dezvoltarea aplicației server
Aplicația server este formată din două fișiere localizate în folderul server, un fișier în care
se află datele de conectare la baza de date, url-ul aplicației client și url-ul aplicației server numit
config.js și main.js în care se află tot codul specific aplicației server.
Aplicația server se poate porni prin rularea comenzii node main.js în directorul server al
aplicației, dar am preferat să folosesc biblioteca pm2, care este un manager de procese pentru
aplicațiile Node.js. Astfel îmi permite să țin aplicația server tot timpul deschisă, iar în caz de eroare,
îmi salvează în log-uri ce s-a întâmplat și restartează aplicația automat. De asemenea, îmi oferă o
modalitate simplă de monitorizare a resurselor consumate de aplicație la cererea mea.
La pornirea aplicației server, în primele linii de cod, sunt instanțiate toate modulele necesare
explicate în subcapitolul 1.3 Arhitectura aplicației.
Următoarele linii de cod sunt doar câteva necesare pentru pornirea serverului folosind
modulul Express și Socket.IO care este atașat la Express, pentru ca amândouă să asculte pe același
port.
// Create a new Express application
var app = require('express')();
// Create an http server with Node's HTTP module and pass it the Express application
var server = require('http').createServer(app);
// Instantiate Socket.IO hand have it listen on the Express/HTTP server
var io = require('socket.io')(server);
// Start server and listen to port 8081
server.listen(8081, function () {
console.log(‘SERVER STARTED’);
});
Secțiune de cod 12: Codul pentru pornirea server-ului pentru cererile HTTP și WebSocket
După cum am explicat și în capitolul 1.3 Arhitectura aplicației, în această configurație
Express este utilizat pentru a trata cererile HTTP care vin în aplicația Node.js. Modulul Socket.IO
se atașează de Express și ascultă pe același port pentru conexiunele WebSocket de intrare.
Express are în componența lui diferite tehnici pe care le putem folosi în momentul în care
o cerere către o anumită pagină web sau o anumită informație este dorită spre vizualizare în
browser. Una dintre aceste tehnici se numește "routing" și are ca principal scop traseul cererii ce
40
vine de la client și care mai apoi este gestionată cu ajutorul diferitelor funcții sau metode, cu scopul
de a-i oferi un răspuns cu un anume conținut browser client. (12)
3.1 Cererile HTTP
Cererea de autentificare /login
La autentificarea clientului prin introducerea numelui de utilizator și a parolei în formular,
pe partea de client se face o cerere la server pentru validarea celor două.
Server-ul verifică dacă numele de utilizator și parola criptată folosind metoda MD5 există
în baza de date. În cazul în care există, dacă contul are peste o săptămâna vechime, verifică dacă
adresă de email este activată, apoi generează un șir unic format din numere și litere numit token,
pe care îl inserează apoi în baza de date și îl trimite clientului împreună cu informațiile lui într-un
JSON.
Dacă contul nu este valid sau adresa de email nu este activată, clientul primește un JSON
cu mesajul de eroare și statusul 0.
Cererea de înregistrare /register
Dacă clientul nu are cont va trebuie să se înregistreze, completând numai un formular ce
conține numele de utilizator, adresa de email și parola. Aceste date sunt apoi trimise server-ului
pentru validare.
Pentru început server-ul validează cele trei date necesare folosind modulul express-
validator:
adresa de email trebuie să fie validă;
numele de utilizator trebuie să conțină numai litere și cifre și să aibă între 3 și 10
caractere lungime;
parola trebuie să conțină și ea numai litere și cifre și să aibă între 4 și 10 caractere
lungime.
Dacă toate aceste date sunt valide, se verifică în baza de date să nu mai existe un utilizator
cu aceeași adresă de email sau același nume. Dacă nu există, se generează două token-uri unice,
41
unul pentru autentificarea utilizatorului și celălalt pentru validarea adresei de email, care se trimite
prin email, apoi se inserează datele în baza de date.
Ulterior, ca la autentificare, se trimit datele utilizatorului dacă totul este în regulă sau
mesajul de eroare, în formatul JSON.
Cererea pentru verificarea token-ului /check-login
La autentificare sau după înregistrare se generează un token unic care este introdus în baza
de date a utilizatorului pentru care a fost generat și apoi îi este trimis.
Aplicația client îl salvează într-un cookie și atâta timp cât accesează aplicația de pe același
calculator și același browser acesta nu mai trebuie să se autentifice de fiecare dată folosind user-ul
și parola. Aplicația verifică dacă în cookie există un token pe care îl trimite apoi serverului pentru
a vedea dacă corespunde cu cel din baza de date.
Cererea pentru activare email /activate/:token
Față de celelalte cereri care au folosit metoda de solicitare de tip POST, aceasta folosește
GET, adică cererea poate fi accesată pur și simplu prin introducerea link-ului în browser.
La înregistrarea utilizatorului pe adresa email, acesta primește un link în care se află un
token unic. La accesarea link-ului, serverul verifică dacă tokenul din link corespunde cu cel al unui
utilizator în baza de date; caz în care i se actualizează în baza de date adresa de email verificată și
este redirecționat către aplicația client.
3.2 WebSockets
WebSocket asigură comunicarea în timp real în două sensuri între un client și un server, și
prin urmare, este extrem de utilă în dezvoltarea jocurilor moderne de tip multiplayer în browser,
care se pot baza pe o conexiune activă tot timpul, cu o latență redusă, permițând transmiterea rapidă
a informațiilor despre jucător și joc.
42
Când un client se conectează la server folosind Socket.IO, este trimis și token-ul pe care l-
a primit la autentificarea pentru validarea conexiunii folosind o funcție intermediară, numită și
middleware.
După autentificare, clientul va emite un eveniment newGame de creare a jocului sau de
alăturare la un joc deja creat joinGame.
La emiterea evenimentului newGame de către client, serverul generează un token, încarcă
fișierul de configurare a hărții, adaugă într-o variabilă de tip obiect datele despre noul joc, creează
o cameră care are ca id token-ul generat și emite clientului un eveniment gameCreated că jocul a
fost creat împreună cu id-ul camerei.
Conceptul de cameră (en: room) este construit în Socket.IO. O cameră va grupa conexiunile
clienților pentru a permite evenimentelor să fie emise numai clienților din cameră. Ele sunt perfecte
pentru crearea de jocuri individuale, folosind un singur server. Fără camere, ar fi mult mai dificil
să ne dăm seama care jucători și gazde ar trebui să fie conectate între ele, în cazul în care mai multe
persoane încearcă să inițieze mai multe jocuri de pe același server.
socket.on('newGame', function(data){
var gameRoomToken = crypto.randomBytes(5).toString('hex');
socket.join(gameRoomToken);
fs.readFile('../html/assets/data/maps/'+data.map+'.json', function(err,
mapData){
if(err){
return socket.emit('alert', {message: 'Map data not loaded!',
redirect: true});
}
gameRooms[gameRoomToken] = {players: [data.user], map: data.map,
mapData: JSON.parse(mapData), started: false, host: data.user.id};
socket.emit('gameCreated', gameRoomToken);
socketsInfo[socket.id] = {
gameRoomToken: gameRoomToken,
user: data.user
};
});
});
Secțiune de cod 13: Funcția evenimentului de creare joc
Pe partea de server, la emiterea evenimentului joinGame de către client împreună cu token-
ul jocului, obiectul jocului este actualizat cu un nou jucător și este plasat în camera de joc, apoi
este emis evenimentul updatePlayers cu o listă actualizată cu toți jucători la toate conexiunile din
cameră.
43
Pentru că sintaxa este puțin confuză în Socket.IO, mai jos avem o listă despre cum pot fi
emise evenimentele către clienți:
socket.emit('hello', msg); - evenimentul va fi emis doar către expeditor;
io.to('my room').emit('hello', msg); - se va emite evenimentul către toți clienții din
camera 'my room', inclusiv expeditorului;
socket.broadcast.to('my room').emit('hello', msg); - se va emite către toți clienții
din camera ‘my room’, în afară de expeditor;
io.emit('hello', msg); - se va emite tuturor clienților conectați, indiferent de
cameră;
socket.broadcast.to(otherSocket.id).emit('hello', msg); - mesajul va ajunge doar la
clientul cu id-ul specificat, indiferent de cameră.
3.3 Validarea informațiilor primite prin WebSocket de la jucător
Dezvoltarea jocurilor este o provocare în sine, dar jocurile multiplayer adaugă un set
complet nou de probleme destul de interesante care trebuie tratate. Problemele de bază sunt de
natură umană și fizică.
Cea mai mare problemă pentru jocurile multiplayer este încercarea jucătorilor de a trișa. Ca
dezvoltator, de obicei nu te interesează prea mult dacă jucătorul trișează în modul singleplayer,
acțiunile lui nu îl afectează decât pe el. În acest caz, experiența jocului nu va mai fi așa cum a fost
plănuită, dar din moment ce este jocul lor, au dreptul să-l joace cum vor.
Jocurile multiplayer sunt diferite, totuși. În orice joc competitiv, un jucător care înșeală face
ca experiența jocului să fie una proastă nu doar pentru el, dar și pentru ceilalați jucători. În calitate
de dezvoltator, probabil că doriți să evitați această problemă, din moment ce îi face pe ceilalți
jucători să părăsească jocul.
Există multe lucruri care se pot face pentru a preveni înșelăciunea, dar cel mai important și
probabil singurul cu adevărat semnificativ este simplu, să nu ai încredere în jucător și să crezi tot
timpul că va încerca să trișeze.
44
Una dintre soluții este să rulezi motorul jocului pe server și tot ceea ce se întâmplă sub
controlul său și să-i faci pe clienți să fie doar niște simpli spectatori privilegiați ai jocului. Cu alte
cuvinte, clientul trimite comenzi la server, serverul rulează jocul și va trimite înapoi clienților
rezultatele. Această soluție se numește folosirea server-ului autoritar, pentru că server-ul este
singura autoritate cu privire la ceea ce se întâmplă în joc.
Bineînțeles nici soluția aceasta nu te scapă cu totul de probleme, dar te scapă de majoritatea
lor, care sunt cele mai simple și mai grave. De exemplu, nu aveți încredere în client cu starea de
sănătate a caracterului, poate el va modifica jocul și va spune server-ului că are 1000% la viață, dar
folosind un server autoritat el va ști că mai are doar 10% din viață. De asemenea să nu aveți
încredere în poziția jucătorului pe hartă, pentru că va încerca să se teleporteze sau să se miște mai
repede decât are voie, trimițând coordonate false server-ului.
Server-ul jocului WarTank nu este un server autoritar, pentru că nu rulează motorul jocului.
El se ocupă de primirea datelor de la jucători și trimiterea lor mai departe și de aceea încerc să
protejez jocul de trișori prin verificarea acestor date.
În secțiunea de cod de mai jos este prezentată verificarea datelor la emiterea evenimentelor
de tragere și de mișcare.
Prima dată sunt verificate în amândouă evenimentele dacă viață și viteza tancului trimisă
de client nu depășește valorile maxime admise pentru tipul de tanc selectat.
Apoi la primirea evenimentelor de tragere se verifică rata de tragere, adică timpul scurs
între emiterea evenimentelor nu trebuie să fie mai mică decât cea setată.
Ultima verificare este pentru evenimentul de mișcare, unde sunt comparate datele primite
acum cu cele de la evenimentul anterior. De exemplu dacă tancul se pune în mișcare, este verificată
poziția unde s-a oprit ultima oară să fie egală cu poziția de unde a pornit cu o marjă de eroare de
10 pixeli, apoi la schimbarea direcției de mers se verifică distanță de unde a plecat și unde își
schimbă direcția să nu fie mai mare decât cea calculată în funcție de timpul trecut și viteza tancului.
45
if(userInfo.lastEmmitedData){
// informatiile primite de la client despre viata si acceleratia tancului sa nu le
depaseasca pe cele setate
if(newData.health > tankSettings.health || newData.speed > tankSettings.speed){
cheatDetected = 1;
}
// verificare viteza de tragere
if(type == 'fire'){
if(userInfo.lastEmmitedData.nextFire){
if(userInfo.lastEmmitedData.nextFire > currentTime){
cheatDetected = 2;
}
userInfo.lastEmmitedData.nextFire = currentTime + tankSettings.fireRate;
} else {
userInfo.lastEmmitedData.nextFire = currentTime + tankSettings.fireRate;
}
}
//verificare schimbare de pozitie
if(type == 'movement'){
if(userInfo.lastEmmitedData.speed == 0){
if(Math.abs(userInfo.lastEmmitedData.location.x - newData.location.x) > 10
||
Math.abs(userInfo.lastEmmitedData.location.y != newData.location.y) >
10){
cheatDetected = 3;
}
} else {
var timeElapsed = currentTime -
userInfo.lastEmmitedData.lastMovementUpdate;
var maxPosition = timeElapsed * newData.speed / 1000;
var difference = 0;
if(newData.velocity.x != 0){
difference = _.max([newData.location.x,
userInfo.lastEmmitedData.location.x]) - _.min([newData.location.x,
userInfo.lastEmmitedData.location.x]);
if(difference > maxPosition ||
Math.abs(userInfo.lastEmmitedData.location.y != newData.location.y) > 10){
cheatDetected = 4;
}
} else if(newData.velocity.y != 0){
difference = _.max([newData.location.y,
userInfo.lastEmmitedData.location.y]) - _.min([newData.location.y,
userInfo.lastEmmitedData.location.y]);
if(difference > maxPosition ||
Math.abs(userInfo.lastEmmitedData.location.x != newData.location.x) > 10){
cheatDetected = 4;
}
}
}
userInfo.lastEmmitedData.lastMovementUpdate = currentTime;
}
}
Secțiune de cod 14: Verificarea pe server a datelor venite de la client
46
Concluzii generale
Scopul acestei lucrări a fost acela de a satisface unele curiozități și identificarea
provocărilor întâlnite în dezvoltarea unui joc în care sunt aplicate tehnicile de inteligență artificială,
comunicarea în timp real și de a găsi soluții optime în dezvoltarea lor.
Implementarea clientului a fost mai dificilă decât cea a server-ului, deoarece aici este
motorul jocului unde am folosit biblioteca Phaser, special aleasă pentru multitudinea de exemple
și pentru popularitate.
În dezvoltarea clientului am folosit tehnici deterministe pentru inteligența artificială în
modul singleplayer și comunicarea în timp real prin websocket în modul multiplayer.
Pe partea de singleplayer cea mai mare provocarea a fost să ofer o personalitate diferită
fiecărui tanc non-jucător pentru a crea iluzia de inteligentă și să găsesc un echilibru al sistemului
de inteligență artificială care nu trebuie să fie prea bun, dar îndeajuns cât jocul să fie câștigat într-
un mod provocator și distractiv.
Pe partea de multiplayer provocarea a fost rezolvarea problemelor de latență și de trimitere
a datelor pentru a păstra jocul sincronizat pe toți clienții.
În dezvoltarea server-ului cea mai mare provocare a fost protejarea jocului de trișori prin
verificarea informațiilor primite.
Pe viitor jocul ar putea fi îmbunătățit prin dezvoltarea a unui sistem de inteligență artificială
cu un comportament nedeterminist care se adaptează la tactica de luptă a unui jucător, prin
îmbunătățirea sistemului de protejare a jocului de trișori, prin adăugarea unor servere publice cu
hărți generate automat în funcție de numărul jucătorilor sau apariția pe hartă a unor abilități pentru
a face jocul mult mai distractiv.
47
Bibliografie
1. Battle City (video game). Wikipedia. [Online]
https://en.wikipedia.org/wiki/Battle_City_(video_game). 1.
2. U.S. social gaming development industry from 2012 to 2017. statista.com. [Online]
https://www.statista.com/statistics/242691/revenue-of-the-us-social-network-game-development-
industry/. 2.
3. JS: The Right Way. [Online] http://jstherightway.org/.
4. Node.js. Wikipedia. [Online] https://en.wikipedia.org/wiki/Node.js.
5. Which HTML5 Game Engine is right for you? HTML5 Game Engines. [Online]
https://html5gameengine.com/.
6. Socket.IO. Wikipedia. [Online] https://en.wikipedia.org/wiki/Socket.IO.
7. About Page. MySQL. [Online] https://www.mysql.com/about/.
8. Client- server. Wikipedia. [Online] https://ro.wikipedia.org/wiki/Client-server.
9. Canvas Element. Wikipedia. [Online] https://en.wikipedia.org/wiki/Canvas_element.
10. Andersn, Eike F. Playing Smart - Artificial Intelligence in Computer Games. [Online]
http://eprints.bournemouth.ac.uk/20461/2/zfxCON03_EAndersonText.pdf.
11. AI for Game Developers. [book auth.] David M Bourg Glenn Seemann. s.l. : O'Reilly Media, Inc., July
2004.
12. Expressjs - Notiuni generale. [Online] http://www.arta-web.ro/nodejs/expressjs/.
top related