În cadrul temei 2, veți avea de implementat, la alegere, unul dintr-o serie de mini jocuri cu o dronă: race, deliver the package, shooter. Puteți vedea un demo al unui astfel de joc aici:
Demo-ul este pentru jocul de racing, însă nu implementează complet toate functionalitatile, modul de control este ACRO (cu gamepad), viewport-ul din dreapta nu face parte din enunț.
Drona
Drona controlată de jucător va fi formată astfel: un corp format din două paralelipipede așezate în formă de X. În fiecare capăt este atașat un cub unde se găsește câte o elice în formă de paralelipiped. Drona trebuie desenată cu cel puțin două culori distincte: o nuanță de gri pentru corp și negru pentru elice.
Elicele se vor roti cu o viteză constantă în jurul propriului centru și vor rămâne mereu atașate de corpul dronei.
Controlul dronei
Fie animația următoare care ilustrează toate mișcările posibile ale dronei.
În cadrul animației am ilustrat sistemul de coordonate local dronei cu originea în poziția dronei și vectorii de bază formați din săgețile colorate. Ox este săgeata roșie, Oy este săgeata verde iar Oz este săgeata albastră.
Astfel, jucătorul poate controla drona folosind tastele și/sau mouse-ul astfel:
Camera poate fi implementată:
La jocul de livrări este necesară folosirea unei camere third-person pentru a putea observa pachetele atașate de dronă. La celelalte două jocuri, puteți alege varianta preferată.
Scena
Scena va conține ca elemente de bază:
Obstacolele pot fi de diferite mărimi, pentru a evita un aspect uniform al scenei.
La fiecare pornire a jocului se vor amplasa aleator cel puțin 5 obstacole astfel încât acestea să nu se suprapună.
Teren
Pentru teren se va genera un mesh cu formă de grid regulat, având rezoluția rezoluția mn. Grid-ul trebuie să fie format din dreptunghiuri (alcătuite din 2 triunghiuri) și să nu aibă vertecși duplicați.
Exemplu de grid de 3×3:
Acest mesh va fi generat în C++ folosind o listă de vertecși și una de indici, precum în laboratorul 2. Toți vertecșii vor fi generați având componenta y a poziției egală cu 0, obținându-se un grid drept. Culorile și normalele atribuite în crearea mesh-ului nu au importanță, puteți alege valori constante pentru acestea.
Pentru a-i oferi terenului o formă și o aparență plăcută, în timpul randării, se va modifica înălțimea vertecșilor acestuia (componenta y a poziției în Object Space) în Vertex Shader. În Fragment Shader se vor atribui și culori diferite vertecșilor pentru a obține un efect de textură.
O modalitate simplă pentru a face această modificare a înălțimilor este folosirea de noise: https://thebookofshaders.com/11/ .
Se apelează o funcție de noise care primește ca argument o variabilă de tip vec2 care reprezintă poziția în spațiu pentru care se face sampling la noise. Această valoare poate fi poziția (x, z) a vertexului în Object Space, eventual înmulțită cu o constantă (frecvența).
Noise-ul returnează o valoare cuprinsă între 0 și 1 (sau între -1 și 1 în funcție de implementare), valoare care poate fi folosită pentru a modifica înălțimea vertexului.
Această valoare poate fi folosită și pentru alegerea culorii unui fragment.
final_color = mix(culoare1, culoare2, valoare_noise);
Drona nu va putea trece prin obstacole și nici prin sol. Astfel, pentru a implementa această funcționalitate va fi nevoie să realizați coliziunea dronă - sol și dronă - obstacol. Mai mult, în funcție de jocul implementat, va fi nevoie să implementați coliziunea dronă - checkpoint, dronă - pachet sau dronă - proiectil.
Pentru coliziuni, puteți aproxima obiectele astfel:
Pentru cerințele avansate, puteți opta să implementați un mod de simulare pentru controlul realist al dronei.
În realitate, toate motoarele unei drone sunt paralele, ceea ce înseamnă că drona poate genera accelerație directă doar pe axa locală Oy. Totuși, prin ajustarea puterii motoarelor, drona poate să se rotească în jurul oricăreia dintre cele trei axe locale.
Pentru controlul avansat, va fi necesar să implementați:
Controlul Avansat: Input-ul Utilizatorului
Utilizatorul va controla drona folosind următorii parametri:
Recomandări pentru Taste:
Alte recomandari pentru tastele / input-urile folosite pot fi: * Shift/Z W/S Q/E A/D (Thrust Pitch Yaw Roll), care ar permite si utilizarea mouse-ului pentru o alta functionalitate (ex tragerea in alta directie decat centrul ecranului) * Utilizarea mouse-ului pentru pitch si yaw sau roll (pentru mai multa precizie).
Metode de Traducere a Input-ului Utilizatorului în Unghiurile Dronei
Există mai multe moduri de a traduce input-ul utilizatorului pentru a controla unghiul dronei:
Cele două moduri sunt ilustrate în 2D în această demonstrație:
Dacă observați ca menținerea altitudinii este dificilă in acest mod, puteți opta (ca funcționalitate bonus) pentru implementarea unui controller pid pentru altitudine.
if (glfwJoystickPresent(0)) { int count; const float* axes = glfwGetJoystickAxes(0, &count); }
Însă este necesar să permiteți și input din taste, iar suportul pentru gamepad-uri nu se consideră ca fiind functionalitate bonus/nu se vor primi puncte în plus. (Cu toate acestea, poate face jocul mai distractiv 🙂)
Implementarea Minimap-ului
Pentru implementarea unui minimap, cea mai simplă variantă este ca, după ce ați desenat restul scenei, să curățați bufferul de adâncime (depth buffer) și să randati din nou scena folosind o cameră ortografică poziționată deasupra dronei, mutând viewport-ul într-un colț al ecranului. Pentru a face mai vizibile anumite elemente (drona, checkpoint-urile, inamicii, pachetele, destinația, indicatoarele), le puteți randati diferit: de exemplu, drona ca o săgeată, iar checkpoint-urile ca linii cu o grosime mai mare, setate prin glLineWidth.
Implementarea Split Screen
Pentru a implementa split screen, va trebui să păstrați datele pentru două sau mai multe drone. Perspectiva fiecărui jucător va fi randată folosind un viewport dedicat: pentru primul jucător, viewport-ul va fi plasat în jumătatea stângă a ecranului, iar pentru cel de-al doilea jucător, se va folosi o altă cameră și viewport-ul va fi setat în jumătatea dreaptă a ecranului. Fiecare jucător va avea taste de control diferite.
În funcție de tipul de joc:
out_color = mix(culoare1, culoare2, clamp(altitutine_player / altitudine_maxima, 0, 1));
Folosind această implementare rezultatul ar trebui să fie următorul:
În acest joc obiectivul dronei este să se deplaseze cât mai repede printr-o serie de porți. Porțile trebuie să fie parcurse într-o ordine stabilită la începutul jocului. Porțile trebuie generate aleator similar cu obstacolele (fără ca acestea să se intersecteze cu alte porți sau cu obstacolele). Traseul se va stabili ca o permutare aleatoare sau prestabilita a porților.
Fiecare poartă va fi reprezentată printr-un model 3D. În timpul jocului se va desena cu o culoare distinctă poarta care este următoarea din traseu care trebuie parcursă. O poartă se consideră parcursă dacă drona trece prin interiorul ei.
În interfața jucătorului va fi afișată o săgeată care indică direcția unde se află următoarea poartă ce trebuie parcursă.
Există două moduri de joc:
Scopul acestui joc va fi acela de a prelua pachete, pe care trebuie să le duceți la o destinație de pe hartă. Pachetul va fi reprezentat de o cutie, care se va instanția la o poziție aleatoare pe hartă. Drona va prelua pachetul când se află suficient de aproape de pachet (intră în coliziune cu pachetul), iar apoi destinația va deveni vizibilă pe hartă. Destinația se va genera la o poziție aleatoare și va randată sub forma unui cerc pe sol.
Drona va duce pachetul către destinație, pachetul fiind în permanență plasat sub dronă de-a lungul traseului. Pentru a putea observa pachetul plasat sub dronă, acest joc va fi third person.
Odată ajunsă la destinație (drona se află în interiorul cercului destinație, dar nu neapărat pe sol; deci nu neapărat la aceiași înălțime cu cercul destinație), pachetul se consideră livrat, desprins de dronă și se va instanția un pachet la o nouă destinație.
Pentru a ști unde trebuie livrat pachetul tocmai colectat, jucătorul va avea la dispoziție un minimap, unde va putea vedea toată harta pe unde se poate plimba. Mai mult, pe sol va fi afișată în permanență o săgeată cu direcția în care se află destinația.
Scopul jocului va fi adunarea cât mai multor pachete. În acest sens, jucătorul va putea vedea în permanență, fie pe ecran, fie în consolă, câte pachete a colectat.
În acest tip de joc există mai mulți inamici amplasați în scenă, de care jucătorul trebuie să se ferească și pe care acesta trebuie să-i elimine.
Mesh-urile inamicilor trebuie să fie generate din cod, având formă de dronă.
Inamicii se vor spawna deasupra marginilor terenului și se vor deplasa cu o viteză constantă spre extremitățile opuse ale hărții. Când ajung în capetele opuse ale hărții, aceștia vor fi despawnați, iar alți inamici se vor spawna în scenă în locul lor.
Inamicii trebuie întotdeauna să fie orientați cu fața către jucător. Acest lucru se poate realiza folosind o rotație în jurul axei OY folosind unghiul de rotație dat de către funcția atan2() care primește ca argument vectorul în planul XZ dintre inamic și jucător (rotit cu 90 de grade pentru ca forward-ul acestuia diferă de poziția (cos(0), sin(0)) de pe cercul trigonometric și inversată componenta z deoarece folosim un sistem de coordonate right-handed). Această operație se numește billboarding.
Este suficient să implementați rotația pe OY, nu este nevoie să-i rotiți și în jurul axei lor right pentru a urmări jucătorul și în sus/ jos.
O dată la un interval de timp vor lansa un proiectil sferic pe direcția jucătorului de care jucătorul trebuie să se ferească. Dacă proiectilul atinge jucătorul (sphere vs player collision detection), se va decrementa viața acestuia (puteți afișa în consolă viața rămasă a jucătorului). Când viața jucătorului ajunge la 0, acesta nu se mai poate deplasa în scenă.
Jucătorul poate ataca inamicii la rândul său. Este la latitudinea voastră dacă sistemul de atac al jucătorului este folosind proiectile sferice precum sistemul inamicilor, sau folosind raycast-uri (intersecții între raze duse pornind de la poziția jucătorului pe direcția de vizualizare a jucătorului și bounding box-urile inamicilor).
Când jucătorul distruge un inamic, se va porni o scurtă animație simplă a inamicului (de exemplu: începe să se rotească și să devină din ce în ce mai mic până dispare complet), iar scorul jucătorului este incrementat (scorul poate fi afișat în consolă).
Tipuri de Jocuri (Acestea sunt doar recomandări și pot fi combinate/adaptate după preferință.)
Alte functionalitati avansate (doar in cazul ca nu au fost folosite în joculeț)
Exemplu 1: daca implementati complet jocul shooter, si adaugati si un mod de joc tip capture the flag care implementează și toate functionalitatile de la jocul de livrari, veți avea punctajul maxim (75 p / 3 = 25p bonus)
Exemplu 2: implementand jocul de curse fără split-screen dar cu cele două moduri de control avansate (- 20p + 15p + 20p) veți obține 15p/3 = 5p bonus.
Pentru întrebări vom folosi forumurile de pe moodle. Orice nu este menționat în temă este la latitudinea fiecărui student!
Baremul este orientativ. Fiecare asistent are o anumită libertate în evaluarea temelor (de exemplu, să dea punctaj parțial pentru implementarea incompletă a unei funcționalități sau să scadă pentru hard coding). Același lucru este valabil atât pentru funcționalitățile obligatorii, cât și pentru bonusuri.
Tema va fi implementată în OpenGL și C++. Este indicat să folosiți framework-ul și Visual Studio.