0 MINISTERUL EDUCAȚIEI ȘI CERCETĂRII UNIVERSITATEA „OVIDIUS” DIN CONSTANȚA FACULTATEA DE MATEMATICĂ ȘI INFORMATICĂ SPECIALIZAREA INFORMATICĂ Lucrare… [628865]
0 MINISTERUL EDUCAȚIEI ȘI CERCETĂRII UNIVERSITATEA
"OVIDIUS" DIN CONSTANȚA FACULTATEA DE MATEMATICĂ ȘI
INFORMATICĂ SPECIALIZAREA INFORMATICĂ
Lucrare de Licență
DEZVOLTAREA UNUI JOC 3D SUB PLATFORMA
UNREAL ENGINE
Coordonator științific
Conf. dr. , Sburlan Dragoș -Florin
Absolvent: [anonimizat] 2020
0
Abstract
În copilărie, am fost mereu curios de cum funcționează lucrurile și care au fost forțele motrice
din spatele lor în lumea jocurilor online . Această curiozitate m -a îndrumat până la urmă spre
studiul Informati cii. Unul dintre singurele câmpuri cu potențial nelimitat pentru a crea tot ceea ce
poți c oncepe vreodată.
În realitate, abilitatea de a -ți crea propria l ume și de a fi capabil să comanzi și să controlezi
mediul virtual , dar și mediul fizic este ceea ce doream să fac. Cum să construi esc și să control ez
caractere virtuale , cum să fac calculatoa rele să gândească, cum să cre ez o simulare și așa mai
departe. Pe scurt, iată cum a început călătoria mea .
0
CUPRINS
Cuprins 0
Lista Figurilo r 1
Introducere 2
1. Tehnologii folosite 3
2. Blender 4
2.1. Creearea obiectelor în Blender 5
2.2. Creeare texturii f olosite 10
2.3. Adăugarea texturilor 11
3. Unreal Engine platforma care permite unui utilizator să creeze un joc 13
3.1. Detectarea unui bloc atunci când jucătorul se uită la el 21
3.2. Sistemul de inventar al jucătorului 22
3.3. Echiparea și aruncarea obiectelor 27
3.4. Creeare și distrugerea blocurilor de către jucător 37
3.5. Animația în Unreal Engine 40
4. Concluzii 41
5. Bibliografia 42
1
LISTA FIGURILOR
Fig. 1: Grafic care afișează veniturile în funcție de an 3
Fig. 2: Meniul principal atunci când Blender este deschis 6
Fig. 3: Pașii pentru a începe creearea târnăcopului 6
Fig. 4: Rezultatul obținut după adăugarea ”Array -ului” și pașii făcuți 7
Fig. 5: Forma de început a târnăcopului 7
Fig. 6: Rezultatul obți nut după urmărirea pașilor de mai sus 8
Fig. 7: O formă asemănătoare fără textură a unui târnăcop 9
Fig. 8: Rezultatul obținut după unificarea tuturor cuburilor 10
Fig. 9: Notificare de la Blender în care îmi spune că a u fost eliminate 334 de noduri 10
Fig. 10: Meniul de start atunci când realizezi un nou desen pe www.pixilart.com 11
Fig. 11: Câteva din texturile pe care le -am realizat pe www.pixilart.com 12
Fig. 12: Selectarea imaginii pentru ap licarea texturii târnăcopului 13
Fig. 13: Textura pusă, dar nearanjată 13
Fig. 14: Rezultatul final după aplicarea corectă a texturii 14
Fig. 15: Interfața de selectare a șablonului pentru un nou pro iect 16
Fig. 16: Interfața simplă de tip ”Demo” pentru crearea unui joc de tip ”First Pe rson” 16
Fig. 17: Interfața simplă cu caracterul principal și un cub pe care acesta să stea 17
Fig. 18: Fișierul ”Block.cpp” și ” Block.h” din editorul de cod 18
Fig. 19: Cubul format cu textura ”M_Grass_Block” aplicată 20
Fig. 20: Blueprint -ul ”Bp_Block_Grass” în forma sa finalizată 20
Fig. 21: Model de teren finalizat 21
Fig. 22: Model de platformă pe care jucătorul se va mișca 21
Fig. 23: Testarea funcționalității codului 23
Fig. 24: Setez conexiunile ”Widget” -urilor pentru joc 26
Fig. 25: Legăturile create pentru a distinge ce inventar este curent selectat 26
Fig. 26: Legăturile create pentru a naviga prin inventar 27
Fig. 27: Setarea intrărilor pentru inventar 27
2 Fig. 28: Inventarul jucătorului 28
Fig. 29: Setarea intrărilor pentru inventar 37
Fig. 30: Obiectul care plutește în aer 37
Fig. 31: Obiectul luat de jucător și pus în inventar, de asemenea acesta îl ține în m ână 38
Fig. 32: Funcția obiectului atunci când textura sa se schimbă 41
3 Introducere
Jocurile video sau conceptul de jocuri video încep încă de la începutul anilor 1950,
respectiv sfârșitul anilor 1940, unde Thomas T. Goldsmith Jr. și Estle Ray Mann au creat primul
joc video de tip “Arcade ” folosind un CRT (Cathode -ray- tube[1]). Jocul acesta consta într -un
punct dintr -o rachetă în care utilizatorul putea să îl controleze și să tragă la o țintă aflată pe ecran.
Câțiva ani mai târziu, cel mai mare succes comercial a fost realizat de compania ”Atari”
care a dezvoltat un joc numit ”Po ng” în anul 1975, unde acesta putea fi jucat de acasă. Acest
lucru a determinat multe alte companii să își creeze propriile jocuri și console, începând astfel
secolul industriei jocurilor video.
În ziua de azi, industria jocurilor video este una globală, e a fiind una dintre marile puteri
economice ale lumii. Un studiu realizat de ”Wolf -Street” arată veniturile care provin din această
industrie din anul 2000 până în anul 2019 ( Fig. 1 ). Acesta a fost realizat pentru mai multe
platforme (Arcade, Console, Porta bile, PC/Computer, Mobile și Realitate Virtuală). Acest studiu
arată cât de mult valorează jocurile video și cât de mult afectează lumea cotidiană.
Fig. 1: Grafic care afișează veniturile în funcție de an .
4 1. Tehnologii Folosite
Ansamblul de elemente implicat e în prelucrarea și transmitere a datelor pe cale electronica
formează un sistem informatic. Sistemul informatic poate cuprinde: sisteme de transmisie a
datelor, calculatoare, software -ul, alte componente hardware, perso nalul ce exploatează tehnica
de calcul, datele prelucrate, teoriile ce au la baza algoritmii de prelucrare, etc. Funcționarea unui
sistem informațional -decizional presupune desfășurarea următoarelor activități:
– culegerea datelor despre starea și funcțio nalitatea sistemului ;
– transmiterea datelor în vederea prelucrării dar și prelucrarea acestor date pentru asigurarea
necesarului de informații;
– adoptarea unor decizii , transmiterea acestora spre executanți și urmărirea modului de
implementare .
Jocurile fără programare ar fi la fel de distractive! Programarea este ceea ce aduce totul la
viață într -un joc, de aceea este foarte important să învățăm și să înțelegem conceptele d e
programare și cum să le aplicăm la jocurile pe care intenționăm să le pr oiectăm și să le
dezvoltăm . Programarea orientată pe obiecte ( POO ) este o proiectare filozofică , un model de
programare în care programele sunt organizate în jurul obiectelor și datelor, în loc de acțiune și
logică, totul din POO este grupat ca auto -durabil. Programarea orientată pe obiect permite
descompunerea unei probleme într -un număr de entități numite obiecte care mai apoi construiesc
date și metode în jurul acestor obiecte1.
Conceptul de obiecte software a apărut din nevoia de a proiecta lumea reală în simulări
computerizate. Un obiect poate fi considerat ca reprezentare a unui articol fizic sau a unei idei
care poate efect ua un set de activități conexe în viața reală . Ansamblul de activități pe care le
realizează obiectul definește compo rtamentul obiectului.
De exemplu, în jocul meu veți avea mai multe obiecte care alcătuiesc jocul. Să presupunem
că luăm în considerare modul în care putem reprezenta un târnăcop în jocul nostru . Târnăcopul,
care este un obiect , poate preze nta o varie tate de elemente , cu care putem realiza un număr de
acțiuni . De asemenea, trebuie în același timp să păstreze informaț ii despre caracteristicile sale.
Pentru a reprezenta târnăcopul ca obiect, ar trebuii să programăm comportamentele sale ca
metode și ați declara variabile pentru a conține informații despre caracteristicile sale . În timpul
jocului, obiectul v a efectua diferite acțiuni după cum este necesar pentru a reflecta efectul
acțiunilor sale (va ajuta la spargerea unor blocuri de piatră de exe mplu) . Conceptul unui obiect
este simplu, dar puternic. Obiectele creează module software ideale deoarece pot fi definite și
întreținute independent unul de celălalt, fiecare obiect formând un univers separat .
1 V. Karamian , Introduction to Game Programming: Using C# and Unity 3D , Noorcon Inc. , 2016, p39
5 2. Blender
Blender este un software open source disponibil pentru oricin e dorește să folosească și să
creeze conținut 3D, dar nu a fost întotdeauna așa. Când a fost creat Blender, software -ul a fost o
platformă proprie dezvoltată de un studio olandez numit NeoGeo (care nu a re legătură cu consola
de jocuri N eoGeo) și o companie numită Not a Number (NaN). Creatorul principal și
dezvoltatorul Blender este Ton Roosendaal. A cesta a fost implicat în dezvoltarea tehnică a
Blender la NeoGeo și în marketingul NaN2.
Până în 2002, in vestitorii din spatele NaN au decis să pună capăt tuturor operațiunilor
companiei, inclusiv dezvoltarea Blender 3D. În același an, Ton Roosendaal a creat Fundația
Blender pentru a promova utilizarea și dezvoltarea Blender ca un proiect open source, folosin d
licența publică GNU (GPL). Cu campania Free Blender la nivel mon dial, fundația a fost capabilă
să adune cei 100.000 EUR necesari pentru a cumpăra codu l sursă de la NaN și să ofere Blender
lumii . Astăzi, Ton Roosendaal conduce Fun dația Blender și nou l institut Blender Institute care
organizează dezvoltarea și promovarea Blender3.
Lumea Blender nu este o lume animată așa cum se vede în filme precum Big Buck Bunny
sau Sintel care a fost realizat în Blender. Este o comunitate uimitoare de oameni din întreaga
lume care folosesc Blender. Artiști, programatori, profesioniști, amatori, adolescenți și
pensionari folosesc toate versiunile de Blender4.
Un lucru care face remarcabilă această comunitate este conceptul că Blender este gratuit,
îl plătiți doar dacă doriți ajutarea comunității Blender. Puteți rec omanda Blender prietenilor
dumneavoastră doar pentru a vă distra împreună ajutând alți utilizatori Blender pe site -uri web,
cum ar fi www.blenderartists.org, critic ându -le lucrările sau trecându -vă pr ivirea pe câteva
sfaturi de folos5.
După cum a fost explicat mai sus, a plicația Blender este o aplicație cu sursă deschisă, fapt
ce mă ajută să creez diferite obiecte după bunul meu plac, iar apoi să le exportez în Unreal
Engine pentru a le folosi mai dep arte. Atunci când se deschide interfața principală, utilizatorul
este întâmpinat cu un meniu de a alege dacă dorește să facă un model nou sau să îl continuie pe
cel precedent ( Fig. 2).
Blender reprezintă și o aplicație în care pot să animez diferite obiecte sau lucruri ca apoi
să le utilizez în alte proiecte pe diferite aplicații. A m ales să creez doar obiect ele propriu -zise, ca
apoi să îi pun textura , iar animațiile nu le -am realizat, deoarece acestea apar mai târziu pe
platforma unde adaug aceste obiecte create .
2 A. Brito , Blender 3D 2.49 Incredible Machines , Packt Publishing, 2009, p35
3 Ibid
4 G. C. Fisher , Blender 3D Basics Beginner's Guide , Packt Publishing, 2012, p61
5 G. C. Fisher, Blender 3D Basics Beginner's Guide, Packt Publishing, 2012, p61
6
Fig. 2: Meniul principal atunci când Blender este deschis .
Pentru început am ales primul lucru din meniu, și anume ”General” . O dată selectat, un
cub va apărea în mijloc, simplu și fără vreo textură anume , o lumină va apărea deasupra acestuia,
împreună cu camera care arată poziția obiectului în momentul creări acestuia . Acest cub este
lăsat utilizatorului ca acesta să poată avea un punct de orientare și de start. Vo i folosi acest cub
pentru a crea, în general, mai multe modele de unelte, unul dintre acestea fiind târnăcopul.
2.1. Creearea obiectelor în Blender
Ca să pot începe, am nevoie să adaug mult mai multe cuburi pentru ca apoi să le ordonez,
așa că voi merge în butonul ”Modifier Properties”, selectez opțiunea ”Add Modifier” , selectez
”Array” , iar după în ”Relative Offset” voi seta coordonata ”Z” de la valoarea 0.000 la valoarea
1.000, așa cum am făcut în Fig. 3.
Fig. 3: Pașii pentru a începe creeare a târnăcopului .
7
După ce am făcut acest lucru, Blender ne va afișa pe ecran două cuburi cu unghiuri
diferite. ( Fig. 4)
Fig. 4: Rezultatul obținut după adăugarea ”Array -ului” și pașii făcuți .
Din moment ce am putut crea aceste două cuburi, voi adăuga mai multe cuburi,
schimbând numărul de cuburi de la 2 la 12 în opțiunea ”Count”, iar apoi voi schimba perspectiva
de viziune ortografică prin apăsarea butonului ”1” ca să văd doar vederea din față a cuburilor, și
butonul ”5” pentru a mă seta pe viziunea ortografică a cuburilor , astfel pot naviga mai ușor pe
ecran. De asemenea, voi folosi rotița mouse -ului împreună cu tasta ”Shift” ca să pot să mă
deplasez mai ușor pe ecran ( Fig. 5).
Fig. 5: Forma de început a târnăcopului .
8
Acum că am un start, voi începe prin a creea părțile laterale ale târnăcopului. Prima dată
voi selecta toate cuburile cu combinația de taste ”Ctrl+A” și voi duplica aceste cuburi cu
combinația de taste ”Shift+D”. Totodată, după ce le duplic și le am selectate, voi schimba
”Array -ul” de la valoarea 1 2 la valoarea 11. După ce le voi duplica, voi ține apăsat tasta ”Ctrl”
pentru a le poziționa mai ușor pe axa Z și le voi așeza doar cu o valoare de 2 mai mare. Acum că
am partea superioară creeată, voi face același lucru și pentru partea inferioară, difere nța fiind că
le voi poziționa cu o valoare de 2 mai mare față de poziția originală pe axa X. (Fig. 6)
Fig. 6: Rezultatul obținut după urmărirea pașilor de mai sus.
Următorul pas pe care l -am făcut a fost să duplic partea inferioară , iar apoi primul cub de
la baza creeată o să îl mut în partea de sus părții inferioare, mai precis îl voi muta cu o valoare de
20 mai mare pe axa X și cu o valoare de 18 mai mare pe axa Z. Voi obține o linie continuă ca
partea inferioară a trunchiului târnăcopului, așa că voi schimba asta prin a seta axa X la valoarea
0.000 și valoarea Z la valoarea -1.000 în opțiunea ”Relative Offset”. Voi seta după aceea la
opțiunea ”Count” valoarea 7, p entru că altfel ar fi prea lung târnăcopul.
După ce am realizat acești pași voi duplica rezultatul obținut , iar ținând apăsată tasta
”Ctrl” îl voi poziționa pe axa X și Z cu 2 poziții mai mici, iar apoi voi seta opțiunea ”Count” cu
valoarea 5. După ce obți n rezultatul dorit, îl voi duplica și îl voi așeza cu o poziție de 4 mai mare
pe axa X. Voi face același lucru pentru partea superioară, numai că voi schimba de data asta la
opțiunea ”Relative Offset” valoarea lui X în -1.000 și valoarea lui Z în 0.000 pen tru a fi orientat
pe poziția corectă. În final vom avea o formă asemănătoare a unui târnăcop făcut din cuburi.
(Fig. 7)
9
Fig. 7: O formă asemănătoare fără textură a unui târnăcop.
Acum, după cum arată în Fig. 7, am creat multe obiecte de tip cub pe parcursul
procesului, dar ca rezultat avem un singur obiect, și anume târnăcopul. Acest lucru poate
îngreuna procesul de creare a jocului, deoarece îl încarcă foarte mult, iar ca să facem un singur
obiect final, vom u nii toate cuburile într -unul singur.
Pentru început vom aplica modificările făcute la fiecare cub prin a le selecta pe fiecare în
parte din meniul din dreapta sus și a le aplica opțiunea ”Apply” în meniul din dreapta jos. Apoi,
o să le selectăm pe toa te apăsând tasta ”B” și selectând toate cuburile sau selectând cuburile de la
meniul din dreapta sus, unul câte unul. O dată ce am făcut asta vom aplica opțiune ”Join”
apăsând combinația de taste ”Ctrl+J”. Se va observa după aceea că programul va avea un singur
obiect, și anume ”Cube” pe care îl vom redenumi în ”Pickaxe” în ”Object Proprietes”. ( Fig. 8)
10
Fig. 8: Rezultatul obținut du pă unificarea tuturor cuburilor .
Pentru a optimiza mai mult obiectul voi folosi comanda ”Merge by Distance” pentru a
curăța mai multe noduri create în interior, care nu sunt necesare din moment ce vom avea nevoie
mereu mai mult de exterior. Pentru a realiza acest lucru, am selectat comanda ”Mesh” din meniul
de sus în stânga, apoi ”Clean Up” și într -un final ”Merge by Dis tance”. După un scurt timp voi
primi o notificare de la Blender despre ștergerea celor din interior ca în Fig. 9.
Fig. 9: Notificare de la Blender în care îmi spune că au fost eliminate 334 de noduri.
În Fig.7 se observă un punct galben în mijlocul obiectului, acel punct reprezintă punctul
de origine, unde utilizatorul poate schimba diferite lucruri la obiectul său, unul dintre acestea
fiind dimensiunea pe X,Y, Z. Ca să îl modific, am setat locația ”3D Cursor -ului” la poziția 0, 0, 0
apăsând tasta ”N” și din meniul care a apărut am selectat opțiunea ”View”, iar după la setările
”Location” din meniul ”3D Cursor”. După ce am setat acest lucru, voi selecta opțiunea ”Object”
din meniul de sus, după care ”Set Origin”, iar la final ”Origin to 3D Cursor”.
11 Acum că am obținut obiectul dorit, dimensiunea destul de mare, așa că îl voi micșora la o
dimensiune de 2 pe scala X, Y, Z. De asemenea, voi seta camera să fie orientată cu fața spre
obiectul respectiv apăsând combinaț ia de taste ”Ctrl+Alt+0”. De asemenea, după ce am terminat
totul, vom aplica ”Smart UV Project” ceea ce mă va ajuta să aplic textura mai ușor atunci când o
voi aplica. Pentru a face acest lucru, am selectat opțiunea ”UV” din meniul de sus, apoi ”Smart
UV P roject”. După doar câteva secunde, Blender va afișa un meniu în stânga jos a ecranului care
afișează alte opțiuni pentru ceea ce am făcut, dar îl vom lăsa în statusul ”Default”.
În concluzie, am reușit să obțin un obiect de tip târnăcop, de la un singur cu b pătratic.
Acum va urma să îi pun textura și la final să îl exportăm într -un fișier de tip ”.fbx”.
2.2 Creare texturii folosite
Înainte să aplic vreo textură pe obiectul meu, am nevoie la început să creez textura folosită, iar
pentru a realiza acest lucru, voi folosi un site numit www.pixilart.com . Acest site îmi oferă o
platformă gratuită în care pot să îmi creez singur textura, după bunul plac. Pentru a începe al
folosi, apăs pe butonul ”Start Drawning”, unde mă va redirecționa pe platforma de lucru. Acolo,
mă va întreba dimensiunea despre desenul pe care doresc să îl realizez, iar pentru târnăcop voi
selecta opțiunea de 100×120 pixeli. ( Fig. 10)
Fig. 10: Meniul de start atunci când realizezi un nou desen pe www.pixilart.com
După ce am selectat acest lucru, site -ul mă întâmpină cu diferite opțiuni de a face un
desen, de la alegerea culorilor până la ce dimensiune doresc să desenez .În funcție de jocul pe
12 care doresc să îl fac, realizez mai multe texturi, de la un târnăcop până la ”Thumbnail -ul” pentru
inventarul din joc.( Fig. 11)
Fig. 11: Câteva din texturile pe care le -am realizat pe www.pixilart.com
Într-un final, ca să pot salva texturile la care am lucrat, voi accesa butonul de
”Download ” al paginii respective, iar apoi voi selecta dimensiunea imaginii în momentul
descărcării. Prestabilit rămâne dimensiunea curentă a texturii, aceasta fiind opțiunea pe care o
utilizez. Imaginea pe care am descărcat -o o voi utiliza apoi în Blender pentru s etarea texturii
pentru diferite obiecte, dar și în Unreal Engine atunci c ând fac, de exemplu, inventarul .
2.3. Adăugarea texturilor
Acum, am reușit să creez târnăcopul și textura, acum ramâne întrebarea cum le putem
unii între ele? Pentru asta, vom începe prin a adăuga imaginea dorită (în cazul nostru,
”MC_DiamondPickaxe.png”) în ”Base Color” -ul obiectului din meniul ”Base Color”. Din
meniul ”Base Color”, vom selecta după ”Image Texture”. ( Fig. 12)
13
Fig. 1 2: Selectarea imaginii pentru aplicarea texturii târnăcopului.
Un meniu ne va apărea în care ne pune să selectăm ce imagine dorim să selectăm. O dată
selectată imaginea, va trebui să vedem obiectul din punct de vedere al ” Viewport Shade -ului” din
doar noduri unite în material. Pentru a face acest lucru vom selecta din meniul de sus dreapta cea
de-a treia opțiune. ( Fig. 1 3)
Fig. 1 3: Textura pusă, dar nearanjată.
14 Ca și rezultat, se poate observa că textura are o formă ciuda tă, iar pentru asta trebuie ca
eu să le pun fiecare în parte după cum cred eu că se potrivesc. Pentru asta, va trebui să fac o
pagină nouă în Blender și schimbând din ”Layout” în ”UV Editing” din meniul de sus. Procesul
este destul de lung, dar pentru că am dat mai devreme ”Smart UV Project” când am creat
obiectul, Blender a reușit puțin să aranjeze nodurile laterale pentru textură, dar cele din interior
sunt nearanjate și trebuie să le aranjez eu cum trebuie. ( Fig. 1 4)
Fig. 14: Rezultatul final după apli carea corectă a texturii.
3. UnrealEngine , platforma care permite unui utilizator să
creeze un joc
Când un dezvoltator dorește să dezvolt e un concept sau joc, unul dintre primii pași de
urmat este construcția unui prototip prestabilit care să fie creat pe baza conceptului dorit. Din
fericire, Unreal Engine face mai uș or ca niciodată acest proces.
Înainte de a putea începe configurarea elementelor de joc, dezvoltatorul trebuie să cre eze
proiectul jocului. Pentru a accesa Unreal Engine 4 și pentru a începe configurarea proiectului,
trebuie să deschid ă mai întâi Epic Games Launcher, care poate fi descărcat de pe site -ul Unreal
Engine 4 (https://www.unrealengine.com/).
Tab-ul Library este locația în care se pot accesa versiunile motorului instalat și proiectele care
au fost construite .
15 Odată ce platforma este lansată , aceasta va prezenta browser -ul de proiecte Unreal Engine. În
mod implicit, acest lucru va trimite utilizatorul la tab -ul ”Projects ”, care va arăta o imagine în
miniatură a tuturor proiectelor care au fost create , precum și a tuturor proiectelor care sunt
valabile . În cazul acesta, dorim să începem un proiect nou, așa că lansarea se face printr -un clic
pe fila cu eticheta New Project.
Inginerii de la Epic Games au construit un motor care să -i ajute să creeze primul joc
”Unreal”. De -a lungul anilor, odată cu dezvoltarea fiecărei generații, seria jocurilor Unrea l s-a
diversificat cât mai mult, unde tot mai multe funcționalități au fost adăugate motorului pentru a
ajuta la dezvoltarea unui joc video. Acest lucru, la rândul său, a crescut capacitățile platformei
Unreal Engine și a îmbunătățit foarte repede motorul jocului de -a lungul anilor.
În 1998, prima versiune Unreal Engine a fost lansată și a făcut posibilă creearea unui prim joc
de tip ”Shooter”. Astfel, echipa Unreal a pus la dispoziție acest mic joculeț atunci când
utilizatorul crea un joc video. Acesta se poate modifica și se pot înlocui diferite funcții după cum
dorește utilizatorul. Mai târziu, funcțiile ”multiplayer online” au fost adăugate în Unreal Engine
prin dezvoltarea primei sale aplicați, și anume ”Unreal Tournament”, care este un joc online.
Acest joc a adăugat și consola PlayStation 2 pe lista de platforme compatibile, pe lângă PC și
Mac6.
Până în 2002, Unreal Engine s -a îmbunătățit într -un mod alert, făcând -o una dintre primele
platforme utilizabile în următoarea generație odată cu dezvoltarea unui sistem de particule (un
sistem care să genereze efecte precum ceața și fumul), instrumente de plasă statică (instrumente
pentru manipularea obiectelor), mo torul fizicii (permite interacțiunea dintre obiecte, cum ar fi
coliziunile . Această îmbunătățire a avut ca rezultat dezvoltarea Campionatului Unreal și Unreal
Tournament în anul 2003. Lansarea Unreal Championship a adăugat de asemenea consola de
jocuri Xbo x pe lista de platforme compatibile, aceasta având funcții multiplayer în Xbox Live.
Toate aceste avansări și capacități tehnologice au făcut ca noi versiuni să devină accesibile. Un
exemplu ar fi că Unreal Engine 3 a fost cea mai populară versiunea Unreal Engine. Unreal
Engine 3 a dominat piața timp de 8 ani până când Unreal Engine 4 a fost lansat. Unreal Engine 4
a fost lansat în 2014 și a introdus cea mai mare schimbare înlocuind ”Kismet” cu noul concept de
”Blueprint”7.
Am ales Unreal Engine, deoarece este o platforma ușoară din punct de vedere al utilizării.
De asemenea, o prefer și pentru simplul fapt că nu trebuie să intru în a face doar cod, dar pot crea
singur animații, implementa texturi sau modele și să le adaug în joc. Totodată, cel mai bun luc ru
care mi se pare la această platformă ar fi modelarea și grafica sa, pentru că această platformă
conține totul de la scriptare vizuală (C++) pana la editoare deja incluse pentru absolut orice
(începând de la peisaje până la animații sau creația cinematic ă), dar și că utilizatorul poate să
aleagă ce tip de joc dorește acesta (aventură/strategie/acțiune etc.), unde jucătorul poate fi din
perspectiva de ”First Person”/”Third Person”/Virtual Reality ș.a.m.d. ( Fig. 15).
6 N Misra Unreal Engine: Game Development from A to Z, Packt Publishing, 2016, p49
7 N Misra Unreal Engine: Game Development from A to Z, Packt Publishing, 2016, p50
16
Fig. 15: Interfața de selectare a șablonului pentru un nou proiect.
Ca și tehnologie, aș putea spune că Unreal Engine folosește o tehnologie care consumă o
scală destul de largă de resurse (RAM, GPU, Memoria Procesorului etc.) pentru a obține o
calitate grafică cât mai acurată și mai grafică pentru joc, iar asta îl pot considera ca fiind singurul
dezavantaj în ceea ce privește această platformă față de altele.
Am ales sa utilizez această platformă pentru a crea un joc de tipul Open -World. Acest joc
este unul de tip aventură, în care utilizatorul poate să exploreze o lume simplă cu copaci în jurul
său, având diferite unelte disponibile .
Pentru început, Unreal Engine îți pune la dispoziție un set de tip ”Demonstrație” pentru
ca utilizatorul să poată testa funcționalitatea șablonului . (Fig. 16)
Fig. 16: Interfața simplă de tip ”Demo” pentru crearea unui joc de tip ”First Person”.
17 Se pot observa clasele/fișierele generate automat de către Unreal Engine în partea de jos,
stânga, al interfeței Unreal Engine.
Deși acesta reprezintă un șablon destul de bun pentru început, nu am nevoie de această
platformă pentru că este irelevantă pent ru jocul care vreau să îl creez și să îl dezvolt, deci am
șters-o și am lăsat doar caracterul principal din joc, adică cel pe care îl controlează jucătorul ( Fig.
17).
Fig. 17: Interfața simplă cu caracterul principal și un cub pe care acesta să stea
Următorul pas este să creez platforma pe care jucătorul se va afla . Pentru a realiza acest
lucru, mai întâi trebuie să am într -o clasă C++ un actor cu denumirea ”Block”. Ca să fac acest
lucru , o să accesez în meniul ”Content Browser” fișierul ”C++ Classes” , iar în clasa ”Mc”, cu
click dreapta, voi creea o nouă clasă C++. Mi se va deschide un nou meniu numit ”Choose
Parent Class”, iar de acolo voi selecta clasa ”Actor” . La finalul acestui proces, voi salva clasa
sub denumirea ”Block”.
Dupa ce s -a creat clasa respectivă, editorul de cod (în cazul meu ”Visual Studio 2019”) se
va deschide și va afișa câteva funcții generate automat. ( Fig. 1 8)
18
Fig. 18: Fișierul ”Block.cpp” și ”Block.h” din editorul de cod.
În acesta, voi șterge funcția ” void ABlock::Tick(float DeltaTime )” .Totodată, în fișierul
”Block.h” vom șterge și funcția ” virtual void Tick(float DeltaTime) override; ” și vom adăuga în
locul ei un ” ”UStaticMeshComponent*” denumit ”SM_Block”. Acum că am făcut acest lucru,
voi șterge linia de cod din ”ABlock::ABlock()” , iar în locul acesteia voi declara variabila
”SM_Block”. Într -un final, vom avea în fișierul ”Block.h” următorul cod:
#include "Block.h"
// Sets default values
ABlock::ABlock()
{
SM_Block = CreateDefaultSubobject< UStaticMeshComponent >(TEXT("BlockMesh" ));
}
// Called when the game s tarts or when spawned
void ABlock::BeginPlay()
{
Super::BeginPlay();
}
19 Iar în fișierul”Block.cpp” vom avea următoarea secvență de cod:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Block.generated.h"
UCLASS()
class MYPROJECT_API ABlock : public AActor
{
GENERATED_BODY ()
public:
// Sets default values for this actor's properties
ABlock();
protected :
// Called when the game starts or when spawned
virtual void BeginPlay() override ;
public:
// Called every frame
UPROPERTY (EditDefaultsOnly)
UStaticMeshComponent * SM_Block;
};
După ce am făcut acest lucru, voi compila rezultatele obținute în Unreal Engine apăsând butonul
”Compile ”, iar după un timp, Unreal Engine va afișa o notificare cu mesajul ”Compile
Complete!”.
Voi creea un fișier nou în folderul ”Content, numit ”Assets”, înauntrul acestuia voi face
alte fișiere numit e ”Blueprints”, ”Meshes”, ”Textures” și ”Materials” . În fișierul ”Meshes” voi
adăuga alt fișier numit ”Blocks”, apoi voi muta din fișierul ”Geometry”, respectiv ”Meshes” ,
”Static Mesh” -ul denumit ”1M_Cube_Chamfer” în fișierul ”Meshes”, respectiv ”Blocks”. De
asemnea, îi voi schimba denumirea în ” Cube”.
În următorul pas, voi avea nevoie de textura cubului pe care am realizat -o precedent.
Pentru asta, pur și simplu voi adăuga în fișierul Textures fila cu textura respectivă. Acum că am
textura, denumită ”T_Grass_Block”, și ”T_Break”. Voi da apoi cl ick dreapta pe
”T_Grass_Block” și voi alege opțiunea ”Create Material”. Se va creea un nou material pe care îl
voi muta în fișierul ”Materials”.
Într-un final, revin înapoi în fișierul ”Blocks”, unde voi duplica ”Static Mesh” -ul ”Cube”
și îl voi denumi pe al doilea ”GrassBlock”. Voi da dublu click, iar la opțiunea ”Material Slots”
voi selecta materialul denumit ”M_Grass_Block” și mi se va aplica textura pe cubul respectiv.
(Fig. 17 )
20
Fig. 19: Cubul format cu textura ”M_Grass_Block” aplicată.
Acum că avem cubul, mai trebuie să îi creez ”BluePrint” -ul pentru ca acesta să își poată
lua valori din codul creat precedent. Pentru asta, voi merge înapoi în fișierul ”MC” din ”C++
Classes” , iar cu click dreapta pe clasa ”Block” voi selecta opțiunea ”Create Blue print class based
on Block”, o vom denumi ”BP_Block_Grass” și o vom salva în ”Assets”, respectiv ”Blueprints”.
Acolo voi crea un nou fișier numit ”Blocks” și voi muta ”BP_Block_Grass” în acesta.
După ce am făcut acest lucru, voi intra în ”Bp_Block_Grass”, iar la opțiunea ”Static
Mesh” voi alege ”GrassBlock”. ( Fig. 18 )
Fig. 20: Blueprint -ul ”Bp_Block_Grass” în forma sa finalizată.
21 Pentru următorul pas , deși am acest bloc, pentru a fi mai ușor să pun fac map, am duplicat
acest ”BluePrint” cu numele de ” Bp_Block_Grass_Floor”. În acesta, voi duplica blocul de mai
multe ori, până voi avea o formă asemănătoare a unui mic teren. (Fig. 1 9)
Fig. 21: Model de t eren finalizat.
Într-un final, am șters cubul inițial pe care stătea jucătorul și am pus câteva platforme în
jurul jucătorului pentru a vedea rezultatul final. ( Fig. 20)
Fig. 22: Model de p latform ă pe care jucătorul se va mișca .
22 3.1 Detectarea unui bloc atunci când jucătorul se uită la el
Mai târziu, vom dori să punem, respectiv să spargem blocurile după bunul plac, dar
pentru a face acest lucru, mai întâi trebuie ca jocul să știe că jucătorul se uită la un bloc. Pentru a
face asta , voi deschide ”McCharacter” din fișierul ”C++ Classes” .
Voi adăuga funcția de ”CheckForBlocks()”, de asemnea vo i include și ”Block.h” pentru
că voi ave a nevoie de el atunci când apelez variabila ”ABlock* CurrentBlock ;”. De asemnea, voi
adăuga și o variab ilă de tip ”float” numită ”Reach”, pentru a afla distanța dintre caracter și blocul
respectiv.
Ca să pot verifica dacă jucătorul se uită sau nu la un bloc anume, trebuie să iau poziția în lume a
caracterului și să văd dacă obiectul la care se uită este sa u nu un bloc.
void AMcCharacter ::CheckForBlocks()
{
FHitResult LinetraceHit;
FVector StartTrace = FirstPersonCameraComponent ->GetComponentLocation();
FVector EndTrace = (FirstPersonCameraComponent ->GetForwardVector() * Reach) +
StartTrace;
FCollisionQueryParams CQP;
CQP.AddI gnoredActor( this);
GetWorld() ->LineTraceSingleByChannel(LinetraceHit, StartTrace, EndTrace,
ECollisionChannel ::ECC_WorldDynamic , CQP);
ABlock* PotentialBlock = Cast< ABlock>(LinetraceHit.GetActor());
if (PotentialBlock == NULL)
{
CurrentBlock = nullptr;
return;
}
else
{
CurrentBlock = PotentialBlock;
GEngine->AddOnScreenDebugMessage( -1, 5.f, FColor::Red, *CurrentBlock –
>GetName());
}
}
Pentru a verifica funcționalitatea aplicației și a funcției declarate anterior am adăugat un
”AddOnScreenDebugMessage” pentru a -mi spune blocul la care se uită jucătorul. Odată ce
pornesc jocul, atunci când jucătorul se uită la un bloc, voi observa un mesaj constant cu numele
entității la care se uită . (Fig. 2 1)
23
Fig. 23: Testarea funcționalității codului.
Odată ce am testat acest lucru, voi șterge linia de cod ce afișează acel mesaj, pentru că nu doresc
ca jucătorul să vadă acest mesaj în timpul rulării programului.
GEngine->AddOnScreenDebugMessage( -1, 5.f, FColor::Red, *CurrentBlock ->GetName());
3.2 Sistemul de inventar al jucătorului
Acum că am blocurile, aș dori să creez un sistem de inventar pentru jucător, pentru ca
atunci când jucătorul va dori să spargă un bloc, acesta să poată să spargă cu o unealtă.
Pentru asta am creat un folder numit ”HUD” și am adăugat în el, cu click dreapta, un
”Widget Blueprint” din opțiunea ”User Interface”. Dupa ce l-am creat, am accesat ”Blueprint” -ul
respectiv și am adăugat 10 butoane, unde fiecare buton va avea o textură. Am setat primul slot de
inventar să fie cel activ, setându -i opacitatea mai închisă.
Pentru a trece în a crea legătura dintre joc și jucător prin ”Blueprint” trebuie mai întâi să
realizez un cod în spate în care să îi spun despre acest sistem , astfel voi edita fișierul
”McGameMode ” pentru asta .
La final, fișierul meu ”McGameMode.h” și ”McGameMode.cpp” vor arăta astfel:
24
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "McGameMode.generated.h"
UCLASS(minimalapi)
class AMcGameMode : public AGameModeBase
{
GENERATED_BODY ()
virtual void BeginPlay() override ;
public:
AMcGameMode();
enum EHUDState : uint8
{
HS_InGame ,
HS_Inventory
};
/*Verifica starea HUD -ului si apeleaza applyhud ca sa afiseze ce hud afisez pe
moment*/
void ApplyHUDChanges();
/*Ia starea HUD -ului*/
uint8 GetHUDState();
/*Seteaza o functie pentru statusul HUD -ului, aplica noul stat us si apoi apeleaza
applyhudchanges*/
UFUNCTION (BluePrintCallable, Category = "HUD Functions" )
void ChangeHUDState( uint8 NewState );
/*O functie ce aplica HUD -ul ecranului, returneaza true daca are succes si false
daca nu are*/
bool ApplyHUD( TSubclassOf <class UUserWidget > WidgetToApply , bool ShowMouseCursor ,
bool EnableClickEvents );
protected :
/* HUDSTATE Curent, unde 1 -> In joc,2 -> Inventar, 3 -> Craft Menu*/
uint8 HUDState;
/*Ce HUD doresc in Joc*/
UPROPERTY (EditDefaultsOnly, BlueprintReadWrite, Category = "Blueprint Widgets" ,
Meta = (BlueprinProtected = "true"))
TSubclassOf <class UUserWidget > InGameHUDClass;
/*Ce HUD doresc in Inventar*/
UPROPERTY (EditDefaultsOnly, BlueprintReadWrite, Category = "Blueprint Widgets" ,
Meta = (BlueprinProtected = "true"))
TSubclassOf <class UUserWidget > InventoryHUDClass;
/*Ce HUD e vizibil pe ecran pe moment*/
class UUserWidget* CurrentWidget;
};
25
#include "McGameMode.h"
#include "McHUD.h"
#include "McCharacter.h"
#include "UObject/ConstructorHelpers.h"
#include "Blueprint/UserWidget.h"
#include <EngineMinimal.h>
void AMcGameMode ::BeginPlay()
{
Super::BeginPlay();
ApplyHUDChanges();
}
AMcGameMode ::AMcGameMode()
: Super()
{
// set default pawn class to our Blueprinted character
static ConstructorHelpers ::FClassFinder <APawn>
PlayerPawnClassFinder( TEXT("/Game/FirstPersonCPP/Blueprints/FirstPersonCharacter" ));
DefaultPawnClass = PlayerPawnClassFinder.Class;
// use our custom HUD class
HUDClass = AMcHUD::StaticClass();
HUDState = EHUDState ::HS_InGame ;
}
void AMcGameMode ::ApplyHUDChanges()
{
/*Sterge HUD -ul precendent din moment ce adaugam pe cea noua.*/
if (CurrentWidget != nullptr)
{
CurrentWidget ->RemoveFromParent();
}
/*Verifica starea HUD -ului si aplica HUD -ul corespondent catre orice HUD este
disponibil*/
switch (HUDState)
{
case EHUDState ::HS_InGame :
{
ApplyHUD(InGameHUDClass, false, false);
}
case EHUDState ::HS_Inventory :
{
ApplyHUD(InventoryHUDCla ss, true, true);
}
default:
{
ApplyHUD(InGameHUDClass, false, false);
}
}
}
26 După ce compilez codul făcut, voi creea un ”BluePrint” după fișierul ”McGameMode” pe
care l -am editat. Îl voi denumi ”MyMcGameMode” și îl voi muta în folderul ”HUD”, iar apoi îl
desch id și variabilele de tip ”Widget” pe care le -am creat în cod le voi specifica ce HUD doresc
să utilizez. ( Fig. 2 4)
.
După ce am terminat acest lucru, voi accesa ”BluePrint” -ul creat precedent și îi voi crea
conexiunile necesare. Odată fac evenimentul p entru a lumina și pentru a distinge item -ul curent
în joc ( Fig. 2 3) și odată pentru a putea naviga printre inventar. ( Fig. 2 4)
Fig. 2 5: Legăturile create pentru a distinge ce inventar este curent selectat.
27
Fig. 2 6: Legăturile create pentru a naviga prin inventar .
Într-un final, pentru a ca jucătorul să poată schimba inventarul cu rotița mouse -ului, am
accesat opțiunea ”Project Settings” din Unreal Engine, iar la ”Input” am adăugat variabilele
”InventoryUp” și ”In ventoryDown”, declarate precedent în cod, și le -am specificat rotița mouse –
ului ca fiind intrările pe care le face jucătorul. ( Fig. 2 5)
Fig. 2 7: Setarea intrărilor pentru inventar.
28 La final, odată ce pornesc jocul, voi putea observa inventarul jucătorului pe ecran. ( Fig. 2 6)
Fig. 2 8: Inventarul jucătorului.
3.3 Echiparea și aruncarea obiectelor
Din moment ce am reușit să creez un inventar, vreau să îi dau jucătorul ui unelte în
inventar și să navigheze printre acestea. Pentru a realiza acest lucru, va trebui să realizez
uneltele.
Voi începe prin a crea un folder denumit ”Wildables”. În acest folder, îmi voi importa din
Blender uneltele create. De asemenea, în folderul ”C++ Cl asses” voi crea un nou actor numit
”Wildable” , apoi îl voi deschide pentru editat. Am decis ca am nevoie de două clase în special,
una fiind cea care se ocupă cu rotirea obiectul de luat pe care doresc să îl iau, aceasta
denumindu -se ”Tick”, și cealaltă fi ind cea care se ocupa cu funcția ca atunci când jucătorul
merge peste acel obiect, jucătorul să îl poată corecta, aceasta fiind denumită
”OnBoxBeginOverlap”. Totodată, am adăugat și funcția ”Hide” pentru ca atunci când este
apelată, obiectul pe care l -a luat jucătorul să dispară de pe mapă. Codul pentru ”Wildable.h” și
”Wildable.cpp” fiind următorul:
29
// Wildable.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/ShapeComponent.h"
#include "Wildable.generated.h"
UCLASS()
class MC_API AWildable : public AActor
{
GENERATED_BODY ()
public:
// Sets default values for this actor's properties
AWildable();
// Called when the game starts or when spawned
virtual void BeginPlay() override ;
// Called every frame
virtual void Tick(float DeltaTime ) override ;
enum ETool : uint8
{
Unarmed,
Pickaxe,
Axe,
Shovel
};
enum EMaterial : uint8
{
None = 1,
Wooden = 2,
Stone = 4,
Iron = 6,
Diamond = 8
};
//class UBoxComponent* Box;
UFUNCTION ()
void OnBoxBeginOverlap( UPrimitiveComponent * OverlappedComp , AActor*
OtherActor , UPrimitiveComponent * OtherComp , int32 OtherBodyIndex , bool bFromSweep , const
FHitResult & SweepResult );
UPROPERTY (EditAnywhere)
uint8 ToolType;
UPROPERTY (EditAnywhere)
uint8 MaterialType;
UPROPERTY (EditAnywhere)
USkeletalMeshComponent * WieldableMesh;
30
UPROPERTY (EditAnywhere)
UShapeComponent * PickupTrigger;
UPROPERTY (EditDefaultsOnly)
UTexture2D * PickupThumbnail;
bool bisActive;
void Hide(bool bVis);
void OnUsed();
// Wildable.cpp
#include "Wildable.h"
#include "Components/BoxComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/PrimitiveComponent.h"
#include <Mc\McCharacter.h>
#include <Mc\Mc.h>
#include "Components/ActorComponent.h"
#include <EngineMinimal.h>
// Sets default values
AWildable ::AWildable()
{
bisActive = true;
// Set this actor to call Tick() every frame. You can turn this off to improve
performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
WieldableMesh =
CreateDefaultSubobject< USkeletalMeshComponent >(TEXT("WieledableMesh" ));
//PickupTrigger = CreateDefaultSubobject<UShapeComponent>(TEXT("PickupTrigger"));
PickupTrigger = CreateDefaultSubobject< UBoxComponent >(TEXT("PickupTrigger" ));
PickupTrigger ->SetGenerateOverlapEvents( true);
PickupTrigger ->OnComponentBeginOverlap. AddDynamic (this,
&AWildable ::OnBoxBeginOverlap);
PickupTrigger ->AttachTo(WieldableMesh);
MaterialType = EMaterial ::None;
ToolType = ETool::Unarmed;
//Box->OnComponentBeginOverlap.AddDynamic(this, &AWildable: :OnBoxBeginOverlap);
}
// Called when the game starts or when spawned
void AWildable ::BeginPlay()
{
Super::BeginPlay();
bisActive = true;
}
// Called every frame
void AWildable ::Tick(float DeltaTime )
{
Super::Tick(DeltaTime );
bisActive = true;
FRotator rotation = WieldableMesh -> GetComponentRotation();
rotation.Yaw += 1.f;
WieldableMesh ->SetRelativeRotation(rotation);
31
}
void AWildable ::OnBoxBeginOverlap( UPrimitiveComponent * OverlappedComp , AActor*
OtherActor , UPrimitiveComponent * OtherComp , int32 OtherBodyIndex , bool bFromSweep , const
FHitResult & SweepResult )
{
/*AMcCharacter* Character =
Cast<AMcCharacter>(UGameplayStatics::GetPlayerCharacter(this, 0));
Character ->FP_WieldedItem ->SetSkeletalMesh(WieldableMesh ->SkeletalMesh);
Character ->MaterialType = MaterialType;
Character ->ToolType = ToolType;
Destroy();*/
if (bisActive)
{
AMcCharacter * Character =
Cast<AMcCharacter >(UGameplayStatics ::GetPlayerCharacter( this, 0));
Character ->FP_WieldedItem ->SetSkeletalMesh(WieldableMesh ->SkeletalMesh);
Character ->AddItemToInventory( this);
Hide(true);
}
}
void AWildable ::Hide(bool bVis)
{
WieldableMesh ->SetVisibility(! bVis);
bisActive = ! bVis;
}
void AWildable ::OnUsed()
{
Destroy();
}
Acuma, voi avea nevoie să lucrez puțin și la funcțiile jucătorului, pentru ca acesta să
poată interacționa cu obiectul respectiv, iar pentru asta a trebuit să modific clasa ”McCharacter”.
În general, în această clasă voi adăuga funcțiile care fac legătura dintre jucător și inventar, cum
ar fi să adăugăm sau să aruncăm obiectul respectiv din inventar, dar și alte funcții cum ar fi
actualizarea inventarului atunci când jucătorul face diverse acțiuni. De asemenea, adaug și
funcția ca atunci când jucătorul apasă pe o tastă sau folosește rotița mouse -ului să se schimbe
inventarul. Funcțiile noi declarate în ”McCharacter.h” sunt:
// McCharacter. h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include <Mc\Wildable.h>
#include "Block.h"
#include "McCharacter.generated.h"
32
protected :
virtual void BeginPlay();
virtual void Tick(float DeltaTime ) override ;
public:
/*Obtine inventarul curent pentru HUD*/
UFUNCTION (BlueprintPure, Category = HUD)
int32 GetCurrentInventorySlot();
/*Adauga un item in inventarul nostru*/
UFUNCTION (BlueprintCallable, Category = Inventory)
bool AddItemToInventory( AWildable * Item);
/*Obtine thumbnail=ul pentru un anumit item (i.e. pickaxe)*/
UFUNCTION (BlueprintPure, Category = Inventory)
UTexture2D * GetThumbnailAtInventorySlot( uint8 Slot);
/*Tipul de unelte pe care le folosim*/
uint8 ToolType;
uint8 MaterialType;
private:
/*Da update la itemul pe care il tii*/
void UpdateWieldedItem();
/*Ia itemul curent selectat*/
AWildable * GetCurrentlyWieldedItem();
/*Cate Iteme sunt in inventar*/
const int32 NUM_OF_INVENTORY_SLOTS = 10;
/*Numarul curent de iteme*/
int32 CurrentInventorySlot;
/*Schimba inventarul cu mouse -ul*/
void MoveUpInventorySlot();
void MoveDownInventorySlot();
33
/*Arunca item -ul pe care il tine jucatorul*/
void Throw();
/*Daca sparge blocuri, e true, daca nu, e false*/
bool bIsBreaking;
/*Apelat atunci cand lovesti cu o unealta*/
void OnHit();
void EndHit();
/*Incepe animatia de spargere*/
void PlayHitAnim();
/*Verifica daca sunt blocuri in fata jucatorului*/
void CheckForBlocks();
/*Apelat atunci cand vrem sa distrugem un block*/
void BreakBlock();
/*Tine minte blocul la care jucatorul se uita pe moment*/
ABlock* CurrentBlock;
/*Distanta de pus blocuri*/
float Reach;
/*Time Handler*/
FTimerHandle BlockBreakingHandle;
FTimerHandle HitAnimHandle;
UPROPERTY (EditAnywhere)
TArray<AWildable *> Inventory;
Iar funcțiile în ”McCharacter.cpp” am adăugat următoarele funcții :
void AMcCharacter ::Tick(float DeltaTime )
{
Super::Tick(DeltaTime );
CheckForBlocks();
}
34
int32 AMcCharacter ::GetCurrentInventorySlot()
{
return CurrentInventorySlot;
}
bool AMcCharacter ::AddItemToInventory( AWildable * Item)
{
if (Item != NULL)
{
const int32 AvailableSlot = Inventory.Find( nullptr);
const int32 IsTheSameItem = Inventory.Find( Item);
if (IsTheSameItem != INDEX_NONE )
{
UpdateWieldedItem();
return false;
}
if (AvailableSlot != INDEX_NONE )
{
Inventory [AvailableSlot ] = Item;
UpdateWieldedItem();
return true;
}
else
{
UpdateWieldedItem();
return false;
}
}
else
{
UpdateWieldedItem();
return false;
}
}
UTexture2D * AMcCharacter ::GetThumbnailAtInventorySlot( uint8 Slot)
{
if (Inventory [Slot] != NULL)
{
return Inventory [Slot]->PickupThumbnail;
}
else return nullptr;
}
oid AMcCharacter ::UpdateWieldedItem()
{
Inventory [CurrentInventorySlot ] != NULL ? FP_WieldedItem ->SetSkeletalMesh(Inven-
tory[CurrentInventorySlot ]->WieldableMesh ->SkeletalMesh) : FP_WieldedItem ->SetSkele-
talMesh( NULL); //Inlocuiesc if/else -ul
}
AWildable * AMcCharacter ::GetCurrentlyWieldedItem()
{
return Inventory [CurrentInventorySlot ] != NULL ? Inventory [CurrentInventorySlot ] :
nullptr;
}
35
oid AMcCharacter ::MoveUpInventorySlot()
{
CurrentInventorySlot = FMath::Abs((CurrentInventorySlot + 1) %
NUM_OF_INVENTORY_SLOTS);
UpdateWieldedItem();
}
void AMcCharacter ::MoveDownInventorySlot()
{
if (CurrentInventorySlot == 0)
{
CurrentInventorySlot = 9;
UpdateWieldedItem();
return;
}
CurrentInventorySlot = FMath::Abs((CurrentInventorySlot – 1) %
NUM_OF_INVENTORY_SLOTS);
UpdateWieldedItem();
}
void AMcCharacter ::Throw()
{
//Obtine item -ul ep care il tine jucatorul in acest moment
AWildable * ItemToThrow = GetCurrentlyWieldedItem();
//Vezi unde arunca item -ul respectiv
FHitResult LinetraceHit;
FVector StartTrace = FirstPersonCameraComponent ->GetComponentLocation();
FVector EndTrace = (FirstPersonCameraComponent ->GetForwardVector() * Reach) +
StartTrace;
FCollisionQueryParams CQP;
CQP.AddIgnoredActor( this);
GetWorld() ->LineTraceSingleByChannel(LinetraceHit, StartTrace, EndTrace,
ECollisionChannel ::ECC_WorldDynamic , CQP);
FVector DropLocation = EndTrace;
if (LinetraceHit.GetAc tor() != NULL)
{
DropLocation = (LinetraceHit.ImpactPoint + 20.f);
}
if (ItemToThrow != NULL)
{
UWorld* const World = GetWorld();
if (World != NULL)
{
ItemToThrow ->SetActorLocationAndRotation(DropLocation,
FRotator ::ZeroRotator);
ItemToThrow->Hide(false);
Inventory [CurrentInventorySlot ] = NULL;
}
}
UpdateWieldedItem();
36
}
void AMcCharacter ::OnHit()
{
PlayHitAnim();
if (CurrentBlock != nullptr)
{
bIsBreaking = true;
float TimeBetweenBreaks = ((CurrentBlock ->Resistance) / 100.f) / 2;
GetWorld() ->GetTimerManager().SetTimer(BlockBreakingHandle, this,
&AMcCharacter ::BreakBlock, TimeBetweenBreaks, true);
GetWorld() ->GetTimerManager().SetTimer(HitAnimHandle, this,
&AMcChara cter::PlayHitAnim, 0.4f, true);
}
}
void AMcCharacter ::EndHit()
{
GetWorld() ->GetTimerManager().ClearTimer(BlockBreakingHandle);
GetWorld() ->GetTimerManager().ClearTimer(HitAnimHandle);
bIsBreaking = true;
if (CurrentBlock != nullptr)
{
CurrentBlock ->ResetBlock();
}
}
După ce am terminat acest lucru voi avea nevoie o clasă ”Wieldable” pe care am creat -o
în cod. Aceasta o vom face în folderul ”Wieldables” sub denumirea ”Dia mond Pickaxe_Pickup”
iar în acea clasă, o dată ce o deschidem, voi seta ”Skeletal Mesh” -ul acelui obiect să fie
”DiamondPickaxe”. ( Fig. 2 7)
37
Fig. 2 9: Setarea intrărilor pentru inventar.
La final, o să adaug și textura pentru ca atunci când jucătorul ia acel obiect, în cazul
acesta târnăcopul de diamant, acesta să îi apară în inventar cu o anumită interfață. Pentru a
realiza acest lucru, voi selecta opțiunea ”DiamondPickaxe_PickUp(self)” ș i voi seta la opțiunea
”Pickup Thumbnail” textura pentru inventar, în cazul acesta ”ThumbnailPickaxe_Diamond”. O
dată ce am făcut acest lucru, voi pune obiectul creat în joc ( Fig. 2 8), iar când jucătorul va trece
peste el, acesta va dispărea și îi va apăre a atât în inventar cât și în mână ( Fig. 2 9).
Fig. 30: Obiectul care plutește în aer.
38
Fig. 31: Obiectul luat de jucător și pus în inventar, de asemenea acesta îl ține în mână.
Acum că jucătorul poate obține și echipa obiecte, voi dori ca acesta să poată, de
asemenea, arunca obiectele pe care le are. Pentru a face acest lucru , vom intra la ”Project
Settings” din interfața Unreal Engine și voi adăuga o intrare numită ”Throw” în care jucătorul o
apelează atunci când apasă tasta ”G”.
3.4. Creeare și di strugerea blocurilor de către jucător
Din moment ce jucătorul poate să dețină obiecte în inventar, doresc ca acesta să poată
pune și să distrugă blocuri după bunul plac. Voi începe, prima dată, prin a sparge blocurile de pe
planșă. Pentru început voi intra în clasa ”MinecraftCharacter” și voi adăuga următoarele funcții
și parametrii noi:
bool bIsBreaking;
void OnHit();
void EndHit();
void PlayHitAnim();
void BreakBlock();
FTimerHandle BlockBreakingHandle;
FTimerHandle HitAnimHandle;
Aceste funcții le voi folosi pentru interacțiunea juc ătorului cu blocul respectiv atunci
când începe să îl spargă.
39
void AMcCharacter ::OnHit()
{
PlayHitAnim();
if (CurrentBlock != nullptr)
{
bIsBreaking = true;
float TimeBetweenBreaks = ((CurrentBlock ->Resistance) / 100.f) / 2;
GetWorld() ->GetTimerManager().SetTimer(BlockBreakingHandle, this,
&AMcCharacter ::BreakBlock, TimeBetweenBreaks, true);
GetWorld() ->GetTimerManager().SetTimer(HitAnimHandle, this,
&AMcCharacter ::PlayHitAnim, 0.4f, true);
}
}
void AMcCharacter ::EndHit()
{
GetWorld() ->GetTimerManager().ClearTimer(BlockBreakingHandle);
GetWorld() ->GetTimerManager().ClearTimer(HitAnimHandle);
bIsBreaking = true;
if (CurrentBlock != nullptr)
{
CurrentBlock ->ResetBlock();
}
}
void AMcCharacter ::PlayHitAnim()
{
// try and play a firing animation if specified
if (FireAnimation != NULL)
{
// Get the animation object for the arms mesh
UAnimInstance * AnimInstance = Mesh1P ->GetAnimInstance();
if (AnimInstance != NULL)
{
AnimInstance ->Montage_Play(FireAnimation, 1.f);
}
}
}
void AMcCharacter ::BreakBlock()
{
if (bIsBreaking && CurrentBlock != nullptr && !CurrentBlock ->IsPendingKill())
{
/* AMcCharacter* Character =
Cast<AMcCharacter>(UGameplayStatics::GetPlayerChara cter(this, 0));
Character ->FP_WieldedItem ->GetSkeletalMeshRenderData();*/
CurrentBlock ->Break();
}
}
40 De asemenea voi modifica funcția ”CheckForBlocks” pentru ca atunci când jucătorul își schimbă
privirea de pe bloc, acesta să revină la starea inițială.
void AMcCharacter ::CheckForBlocks()
{
FHitResult LinetraceHit;
FVector StartTrace = FirstPersonCameraComponent ->GetComponentLocation();
FVector EndTrace = (FirstPersonCameraComponent ->GetForwardVector() * Reach) +
StartTrace;
FCollisionQueryParams CQP;
CQP.AddIgnoredActor( this);
GetWorld() ->LineTraceSingleByChannel(LinetraceHit, StartTrace, EndTrace,
ECollisionChannel ::ECC_WorldDynamic , CQP);
ABlock* PotentionalBlock = Cast< ABlock>(LinetraceHit.GetActor());
if (Potentio nalBlock != CurrentBlock && CurrentBlock != nullptr)
{
CurrentBlock ->ResetBlock();
}
if (PotentionalBlock == NULL)
{
CurrentBlock = nullptr;
return;
}
else
{
if (CurrentBlock != nullptr && !bIsBreaking)
{
CurrentBlock ->ResetBlock();
}
CurrentBlock = PotentionalBlock;
}
Pentru ca jucătorul să apeleze funcțiile ”OnHit()” și ”EndHit()”, voi scrie în funcția
”SetupPlayerInputComponent” următoarea secvență de cod:
PlayerInputComponent ->BindAction( "Interact" , IE_Pressed , this, &AMcCharacter ::OnHit);
PlayerInputComponent ->BindAction( "Interact" , IE_Released , this, &AMcCharacter ::EndHit);
Într-un final, voi face ca un bloc să fie destructibil. Pentru asta voi modifica materialul
unui bloc, în cazul acest ”M_Grass_Block” pentru ca acesta să aibă o animație în timpul
spargerii. ( Fig. 30)
41
Fig. 32: Funcția obiectului atunci când textura sa se schimbă.
Pentru asta, am luat textura ”T_Break”, am adăugat o expresie denumită ”Linear
Interpolate” care amestecă două valori date cu o a treia mascată. Aici voi folosi valorile 1 și 0 ca
atunci când parametrul ”CrackingValue” este apelat din cod, să adauge textura ”T_Break” peste
textura originală a obiec tului. Inițial are valoarea 1 unde materialul obiectului este intact , iar
atunci când valoarea ajunge la 0, texturile se vor uni.
3.5. Anima ția în Unreal Engine
Când o persoană se joacă un joc, probabil acea persoană se întreabă ce presupune crearea
unui joc? Într-un simplu exemplu a unui joc de tip ”Shooting” , când utilizatorul apasă butonul
din stânga al mouse -ului, arma se declanșează. Se pot vedea gloanțe care zboară, auzi sunetul
armei și efectul ca re s-a întâmplat după asupra jocului . Dacă s-a lovit ceva, de exemplu, un
perete, ținta primește o formă de deteriorare.8
Din perspectiva unui creator de joc uri video , trebuie să învăț defalcarea a ceea ce vede
într-un joc pentru a și da seama de ce a re nevoie pentru crearea acestuia și animația din cadrul
jocului. O explica ție simplă, fără a intra în detalii prea mari: unirea click-ului de la mouse la
8 N Misra , Unreal Engine: Game Development from A to Z , Packt Publishing, 2016 , p44
42 tragerea gloanțelor, reda rea unui fișier sonor, afișa rea scântei lor (denumite efect de particule) în
apropierea armei și ținta arată unele pagube vizibile.9
Ținând cont de acest exemplu, încercați s e poate vizualiza și descompune orice joc în
componentele sale fundamentale. Acest lucru ajut ă foarte mult în proiectarea și crearea unui
nivel de joc sau pur și simplu a unui joc .
Tehnologia Unreal Engine pune la dispoziție căi care au fost folosite la crearea a sute de
jocuri, iar mii de persoane și -au construit cariere și companii în jurul competențelor dezvoltate
folosind acest motor. Unre al Engine oferă instrumente și capacități de dezvoltare a unui joc
foarte șlefuite, care permit cantități vaste de personalizare pentru aproape orice joc la care o
persoană poate visa. Cursul de Dezvoltare a Jocurilor folosind Unreal Engine oferă o perspec tivă
asupra funcționalităților bogate pentru a crea jocuri 2D și 3D pe mai multe platforme.
Pentru proiectul meu, am nevoie ca atunci când caracterul lovește un cub, să aibă o
animație. Pentru asta, am modificat animația generată de ”Unreal Engine” pentru caracater într –
una care să simuleze lovirea unui bloc cu un târnăcop. Ca să realizez acest lucru, am intrat în
animația ”FirstPerson_Fire” și am modificat -o corespunzător.
4. Concluzii
Dezvoltarea acestui joc a necesitat utilizarea cunoștințelor practice, dar și teoretice
dobândite în timpul facultății, cum ar fi: Arhitectura sistemelor de calcul, Grafica pe calculator și
Programare Orientată pe Obiecte. În plus, față de materiile pe car e le-am învățat, a fost necesar
să acumulez și alte cunoștințe pe parcursul creări jocului, precum: Blueprints Visual Scripting,
Unreal Engine Features și Blender 2.83 Reference Manual.
Cea mai mare provocare în dezvoltarea jocului a fost să creez și să m odific clasele C++
astfel încât funcțiile din cod să se unească cu ” BluePrint ”-urile din Unreal Engine. Am găsit
această provocare destul de bună, pentru că am reușit să învăț diferite aspect ale realizării unui
joc și faptul că acesta poate fi dezvoltat p rin mai multe moduri, nu doar prin cod.
De asemenea, pot spune că a fost o experiență foarte bună, pentru că ma ajutat să învăț
foarte multe lucruri și aspecte legate de crearea și dezvoltarea unui joc video, ele pornind de la
simpla mișcare a utilizator ului pe o mapă până la dezvoltarea unui sistem de meniu și
interacțiunea jucătorului cu obiectele din jur.
Pe viitor, jocul poate să fie dezvoltat mult mai complex, pentru a permite facilități noi. În
primul rând, jocul ar avea nevoie de o mapă care să se genereze automat pentru ca jucătorul
atunci când intră să aibă o nouă experiență a lumii. În prezent mapa nu este generată, dar creată
anterior de către programator, neavând o anumită complexitate.
Alte îmbunătățiri care ar putea fi: funcții de utilizare a a sunetului, un sistem mai dezvoltat
de inventar, mai multe obiecte pe care jucătorul să le obțină, dar și un ” Graphical User
Interface ” mai modern pentru jucător.
9 N Misra , Unreal Engine: Game Development from A t o Z, Packt Publishing, 2016, p45
43
5. Bibliografia
Andrew Sanders, An Introduction to Unreal Engine 4, CRC Press, 2017
Nitish Misra , Unreal Engine: Game Development from A to Z , Packt Publishing ,2016
Gordon C. Fisher, Blender 3D Basics Beginner's Guide, Packt Publishing, 2012
Allan Brito, Blender 3D 2.49 Incredible Machines, Packt Publishing, 2009
Vahe Karamian, Introduction to Game Programming: Using C# and Unity 3D, Noorcon Inc.,
2016
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: 0 MINISTERUL EDUCAȚIEI ȘI CERCETĂRII UNIVERSITATEA „OVIDIUS” DIN CONSTANȚA FACULTATEA DE MATEMATICĂ ȘI INFORMATICĂ SPECIALIZAREA INFORMATICĂ Lucrare… [628865] (ID: 628865)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
