• Rezultati Niso Bili Najdeni

IZDELAVA IZOBRAŽEVALNIH 3D IGER V PROGRAMU UNITY

N/A
N/A
Protected

Academic year: 2022

Share "IZDELAVA IZOBRAŽEVALNIH 3D IGER V PROGRAMU UNITY "

Copied!
53
0
0

Celotno besedilo

(1)

UNIVERZA V LJUBLJANI PEDAGOŠKA FAKULTETA

TUGO-TIJAŠ ŠTRBENC

IZDELAVA IZOBRAŽEVALNIH 3D IGER V PROGRAMU UNITY

DIPLOMSKO DELO

LJUBLJANA, 2021

(2)
(3)

UNIVERZA V LJUBLJANI PEDAGOŠKA FAKULTETA

DVOPREDMETNI UČITELJ RAČUNALNIŠTVO - TEHNIKA

Tugo-Tijaš Štrbenc

Mentor: prof. dr. Jože Rugelj Somentor: asist. Matej Zapušek

IZDELAVA IZOBRAŽEVALNIH 3D IGER V PROGRAMU UNITY

DIPLOMSKO DELO

LJUBLJANA, 2021

(4)

Zahvala

Najprej bi se zahvalil mentorju, profesorju dr. Jožetu Ruglju, za odobritev izbrane teme, prevzem vloge mentorja in napotke pri izdelavi diplomskega dela. Hvala tudi asistentu Mateju Zapušku za vso strokovno pomoč, usmeritve, napotke ter spodbudo pri izbiri teme in izdelavi naloge.

Za pomoč pri lektoriranju se zahvaljujem Eli Brglez.

Velika zahvala gre moji mami, Janji Lamovšek, za vso moralno, psihično in finančno podporo, ki mi jo nudi na moji poti do študijskih dosežkov.

In na koncu, hvala vsem, ki ste mi stali ob strani in pripomogli k nastanku diplomskega dela, ki je pred vami.

(5)

POVZETEK

Igranje videoiger je vsako leto bolj priljubljeno (Newzoo, 2018, Statista, 2021), otroci, ki odraščajo ob moderni tehnologiji, pa virtualni svet dojemajo kot del svoje okolice (Prensky, 2001a).

Raziskave (Backlund in Hendrix, 2013; Chatzeparaskevaidou in drugi, 2017; Harrington, 2012, Prensky, 2001a, Prensky, 2001b) so pokazale, da izobraževalne igre pripomorejo k motivaciji in dobremu počutju pri učenju.

Na Pedagoški fakulteti se študentje 4. letnikov, smeri dvopredmetni učitelj, vezava računalništvo, preko aktivnih oblik dela seznanijo s snovanjem in izdelavo izobraževalnih iger (angleško Game Design-Based Learning). Pod mentorstvom profesorja dr. Jožeta Ruglja ter somentorstvom asistenta Mateja Zapuška, izdelajo izobraževalne igre, ki so tudi uporabljene in preizkušene v okviru pedagoške prakse na osnovnih šolah (Zapušek in Rugelj, 2014).

Na fakulteti se zadnjih nekaj let izobraževalne igre razvija v razvojnem okolju Unity, ki omogoča izgradnjo iger za vrsto platform. Okolje Unity skupaj s C# knjižnicami omogoča ogromno funkcionalnosti (Unity User Manual 2020.3, 2020). Večina študentov Pedagoške Fakultete se z razvojnimi okolji in skriptnimi jeziki sreča prvič, pri čemer razsežnost okolja Unity pomeni prednost na kreativnem področju, slabost pa na področju učenja uporabe in razvoja. Da si olajšamo delo, si je smiselno pomagati s kvalitetno Unity dokumentacijo, brezplačnimi spletnimi video vodiči, prav tako pa so nam lahko v veliko pomoč diplomska in magistrska dela, v katerih so opisane in pojasnjene funkcionalnosti okolja Unity ter skriptnega jezika C#.

Brglez (2017) v svojem diplomskem delu opiše osnove programa Unity, Abram (2019) pa predstavi nekatere naprednejše funkcionalnosti programiranja in uporabe okolja Unity. Za razliko od omenjenih del se v pričujoči diplomski nalogi osredotočimo na predstavitev izdelave dobrih osnov za 3D videoigro – pojasnimo izdelavo skript za premikanje v prvi in tretji osebi, uporabo in skriptno implementacijo animacij glavnega lika, nadziranje kamere ter zaznavanje trkov kamere (camera- collision). Predstavljena izdelava skript s pojasnili predstavlja dobro osnovo za izdelavo izobraževalne 3D videoigre s kontrolami, ki omogočajo prijetno uporabniško izkušnjo.

Primere sem izdelal samostojno s pomočjo Unity dokumentacije, stremel pa sem k uporabniški izkušnji, ki jo nudijo popularni naslovi, kot sta igri The Witcher 3 ter The Elder Scrolls Skyrim.

Ključne besede: izobraževanje, poučevanje, videoigre, računalniške igre, učno gradivo, tridimenzionalno, 3D, IKT, Unity, razvojno okolje, interakcija kamere z objekti

(6)

ABSTRACT

Video games are gaining popularity each year (Newzoo, 2018, Statista, 2021), while kids growing up with modern technology consider virtual worlds as part of their everyday reality (Prensky, 2001a). Research (Backlund in Hendrix, 2013, Chatzeparaskevaidou and others, 2017, Harrington, 2012, Prensky, 2001a and Prensky, 2001b) has shown that educational video games help to maintain motivation and well-being throughout the learning process.

At the Faculty of Education, 4th year students, majoring in the two-subject teacher programs, studying computer science, get acquainted with the game-design based learning. Under the mentorship of professor dr. Jože Rugelj and co-mentorship of assistant Matej Zapušek, students get to make educational games, which are practically tested in the context of pedagogical practice in primary schools (Zapušek and Rugelj, 2014).

In the last few years, at the Faculty of Education, educational games are developed in the Unity game engine. The Unity environment along with the C # libraries provide tremendous

functionality (Unity User Manual 2020.3, 2020). Majority of Faculty of Education students encounter game development tools and scripting languages for the first time, thus the vastness of the Unity game engine appears to be an advantage in the creative area and a weakness in learning how to use and develop. To make our work easier, it is useful to use high-quality Unity

documentation and free online video guides, as well as diploma and master's thesis that describe and explain the functionalities of the Unity environment and the scripting language C # in detail.

In his work Brglez (2017) describes the basics of the Unity program, while Abram (2019) presents some more advanced functionalities of programming and use of the Unity environment. In contrast to their works, the present thesis focuses on how to create good foundations for 3D video games – by explaining how scripts for first- and third-person character control are created, the use and script implementation of main character animations, camera tracking, and the implementation of a smooth camera collision. The explanatory scripts presented provide a good foundation for creating a 3D video game with controls that provide a pleasant user experience.

I created examples of my own using the Unity documentation, basing them on the user experience of popular titles, such as e.g. The Witcher 3 and The Elder Scrolls Skyrim.

Keywords: education, teaching, videogames, computer games, teaching material, three- dimensional, 3D, ICT, Unity, game engine, camera-collision

(7)

Kazalo vsebine

1 Uporaba videoiger v izobraževanju ... 1

2 Unity (okolje za razvoj videoiger) ... 3

3 Osnovni elementi 3D videoiger ... 3

4 Pojasnjeni primeri ... 4

4.1 Kamera v prvi osebi ... 4

4.1.1 Skripta za krmiljenje - CharControll.cs ... 7

4.2 Kamera v tretji osebi ... 10

4.2.1 Skripta za vrtenje kamere - CameraOrbit.cs ... 12

4.2.2 Skripta za interakcijo kamere z objekti - CameraCollision.cs ... 14

4.3 Premikanje v prvi osebi ... 20

4.3.1 Nadgrajena skripta za krmiljenje - CharControll.cs ... 20

4.4 Premikanje v tretji osebi in animacije ... 21

4.4.1 Animator Controller ... 21

4.4.2 Deklaracija spremenljivk v skripti PlayerMovement.cs ... 23

4.4.3 Metode v skripti PlayerMovement.cs ... 24

4.4.4 Korutine v skripti PlayerMovement.cs ... 27

4.4.5 Metoda FixedUpdate() v skripti PlayerMovement.cs ... 33

5 Pojasnitev zahtevnejših elementov ... 34

5.1 Povezava med Unity komponento in C# razredi ... 34

5.2 Razred Physics ... 36

5.2.1 Metoda Physics.Raycast(…) ... 36

5.2.2 Metoda Physics.Linecast(…) ... 36

5.2.3 Podatkovni tip RaycastHit ... 37

5.2.4 Metoda Physics.CheckCapsule(…) ... 37

5.3 Razred Time ... 37

5.3.1 Metoda Time.deltaTime ... 37

5.4 Razred Transform ... 38

5.4.1 Metoda transform.localPosition ... 38

5.4.2 Metoda transform.localPosition.normalized ... 38

5.4.3 Metoda transform.localPosition.magnitude ... 38

5.4.4 Metoda transform.parent ... 38

(8)

5.4.5 Metoda transform.parent.position ... 38

5.4.6 Metoda .parent.localPosition ... 39

5.4.7 Metoda .parent.TransformPoint(…) ... 39

5.5 Razred Input ... 39

5.5.1 Metoda Input.GetAxis(…) ... 39

5.5.2 Metoda Input.GetButton(…) ... 39

5.5.3 Metoda Input.GetKey(…) ... 39

5.5.4 Metoda Input.GetKeyDown(…) ... 39

5.6 Razred Mathf ... 40

5.6.1 Metoda Mathf.Clamp() ... 40

5.7 Razred Vector3 ... 40

5.7.1 Vector3.Lerp(…) ... 40

5.7.2 Vector3.Slerp() ... 40

5.8 Razred Quaternion ... 41

5.8.1 Metoda Quaternion.eulerAngles(…) ... 41

6 Zaključek ... 42

7 Viri in literatura ... 43

(9)

Kazalo slik

Slika 1: Prikaz postopka drag and drop. ... 4

Slika 2: Dodajanje novega objekta Main Camera. ... 4

Slika 3: Prikaz Transform komponente kamere. ... 5

Slika 4: Prikaz možnosti spreminjanja pozicije kamere v oknu Scene. ... 5

Slika 5: Dodajanje skripte preko Add Component. ... 6

Slika 6: Dodajanje skripte s postopkom drag and drop. ... 6

Slika 7: Spremenljivke in metoda Start v skripti CharControll.cs ... 7

Slika 8: Metoda void Update skripte CharControll.cs. ... 8

Slika 9: Postopek preimenovanja objektov. ... 10

Slika 10: Prikaz Transform komponente objekta CamFollow. ... 11

Slika 11: Deklaracija spremenljivk v skripti CameraOrbit.cs. ... 12

Slika 12: Metoda void Update() skripte CameraOrbit.cs. ... 13

Slika 13: Deklaracija spremenljivk v skripti CameraCollision.cs. ... 14

Slika 14: Metoda Update v skripti CameraCollision.cs. ... 16

Slika 15: Metoda LateUpdate v skripti CameraCollision.cs. ... 17

Slika 16: Prikaz Debug.DrawRay() žarkov. ... 18

Slika 17: Nadgrajena skripta CharControll.cs z dodanim premikanjem. ... 20

Slika 18: Primer Animator Controllerja. ... 22

Slika 19: Primer prehoda med animacijami. ... 22

Slika 20: Primer vrednosti boolean spremenljivk za prehod v animacijo Run. ... 22

Slika 21: Deklaracija spremenljivk in sklici do komponent v skripti PlayerMovement.cs. ... 23

Slika 22: Metodi MovePlayer() in MovePlayerLR(). ... 24

Slika 23: Metoda RotateInCamDir(). ... 25

Slika 24: Metoda PlayerJump(). ... 25

Slika 25: Metoda CapsuleCheck(). ... 26

Slika 26: Prikaz kapsule za testiranje in CapsuleCollider. ... 27

Slika 27: Korutina EnGroundCheck(). ... 28

Slika 28: Korutina EnLandingCheck(). ... 29

Slika 29: Korutina EnRun(). ... 30

Slika 30: Korutini EnLCtrl...() ... 30

Slika 31: Korutini EnLShiftDown() in EnLShiftUp(). ... 31

Slika 32: Korutina EnJump(). ... 32

Slika 33: Metoda FixedUpdate(). ... 33

Slika 34: Pojasnilo razredov in objektov. ... 35

(10)

1

1 Uporaba videoiger v izobraževanju

Iz statističnih podatkov je jasno razvidno, da videoigre, tako mobilne kot na drugih platformah, postajajo iz leta v leto bolj priljubljene. To dokazujejo podatki letnih prihodkov industrije videoiger ter podatki vrednosti tržišča videoiger (Newzoo, 2018, Statista, 2021), pa tudi dejstvo, da so že leta 2001 računalniške igre postale bolj dobičkonosne od filmske industrije (Prensky, 2001a). V istem letu so ameriški najstniki v povprečju dnevno namenili 3 ure televiziji ter 1,5 ure računalniškim igram (Prensky, 2001b). Prensky (2001a) meni, da bo učenje v 21. stoletju postalo zabavno in prilagojeno vsakemu posamezniku. Avtorja Backlund in Hendrix (2013) v svoji študiji ugotavljata, da je uporaba videoiger v izobraževanju smiselna in ima velik potencial, saj je večina raziskav pokazala, da videoigre prispevajo k učnemu napredku pri sodelujočih. Ameriška vojska je bila ena prvih, ki je izkoristila moč računalniških iger in simulacij za izobraževanje in urjenje vojakov.

Prensky (2001a) našteje dosežke videoiger v izobraževanju – vojska se uri preko realističnih videoiger, piloti se urijo preko letalskih simulacij, inženirji se učijo uporabe CAD programov preko zabavne 3D igre (produkt podjetja think3) itd. Prensky (prav tam) poudari, da učenje s tem postaja tudi bolj efektivno. K temu pripomore predvsem motivacija (učenec se ne zaveda, da se uči) v kombinaciji z dobro metodologijo, ki ne spominja na tradicionalen pouk. Izpostavlja tudi, da videoigre predstavljajo odlično platformo za reševanje nalog brez omejitev, preko njih pa učenci lažje dosežejo učne cilje (prav tam).

Prensky poudarja, da otroci 21. stoletja videoiger ne dojemajo kot tehnologijo, saj so bile te izumljene pred njihovim rojstvom, oni pa z njimi odraščajo. Sodobni otroci ne delajo več ostrih razlik med naravo in virtualnim svetom, temveč oboje dojemajo kot del svojega okolja (Prensky, 2001b).

Harrington (2012) pravi, da so otroci po naravi vedoželjni in zainteresirani za raziskovanje svojega okolja, to pa je mogoče izkoristiti pri uporabi videoiger v izobraževanju. Računalniške igre predstavljajo dobro podlago za različne tipe učenja, ki ob primerni izvedbi za učence predstavlja aktivno, zabavno in prijetno učno izkušnjo (prav tam). Implementacija videoiger se lahko močno razlikuje, tako po kontrolah, uporabi različnih vmesnikov (npr. virtualna resničnost), kot tudi po vizualni "kakovosti" (prav tam).

Harrington v svoji raziskavi (prav tam) preučuje vplive vizualne kakovosti igre ter vplive navigacijske prostosti po virtualnem svetu na usvojeno znanje učencev. Z besedo "vizualna kakovost" opisuje, v kakšni meri se izgled igre približa resničnemu življenju (od zelo enostavne proti foto-realistični grafiki). Z besedami "navigacijska prostost" pa opisuje možnosti igralca za prosto gibanje po virtualnem svetu (od omejitve na začrtano pot do neomejenega gibanja po virtualnem svetu) (prav tam). Za izvedbo raziskav uporablja 3D igro, ki je bila izdelana in prilagojena za potrebe raziskave. V igri se igralec giblje po rezervatu divjih rastlin s ciljem opazovanja rastlinja in izobraževanja po lastni volji. Igra ne uporablja točkovanja ali drugih načinov motiviranja igralca, temveč je osnovana na prikazu dejstev, nasvetov, zanimivosti ter predvajanju informativnih zvočnih posnetkov (prav tam). Igra omogoča nastavitev vizualne kakovosti ter nastavitev prostosti gibanja. Vizualna kakovost omogoča izbiro nizke (nivo risank)

(11)

2

ali visoke kakovosti (fotorealistična), nastavitev navigacijske prostosti pa izbiro visoke omejenosti (omejitev na prej določeno pot) ali nizke omejenosti (prosta hoja po rezervatu) (prav tam).

Harrington omeni, da je v videoigrah, pred vsem v tridimenzionalnih, pomembnejše informacije dobro podajati v obliki mejnikov (poudarjeno mesto, ki pritegne igralčevo pozornost). Izpostavlja še dve pomembni ugotovitvi. Za zagotavljanje visokega nivoja prostosti gibanja je 3D virtualno okolje predstavljalo zelo jasno prednost. Do podobne ugotovitve so prišli ob primerjavi vpeljave rastlinja iz resničnega sveta v virtualni svet. Kvaliteten 3D model s poudarjenimi ključnimi elementi iz resničnega sveta je mnogo lažje prepoznaven kot ploščat 2D model kot je npr. slika na ploskvi (prav tam).

Po primerjavi igre v različnih kombinacijah nastavitev, so rezultati pokazali, da je različica igre z veliko svobodo gibanja ter nizko stopnjo vizualne kakovosti prinesla približno 20 % boljše učne rezultate. Enako velja za različico igre z nizko stopnjo svobode gibanja, a visoko vizualno kakovostjo. Najvišje rezultate so dosegali učenci, ki so igrali igro z visoko stopnjo svobode gibanja ter visoko kakovostjo grafike. Ti so v povprečju dosegali 37,4 % boljše rezultate, kar pomeni, da sta vizualna kakovost igre ter svoboda gibanja komutativna in torej oba pripomoreta k izboljšanju učnih dosežkov (prav tam).

Chatzeparaskevaidou, Karaoli, in Zaharias (2017) podobno ugotavljajo splošne koristi uporabe videoiger v izobraževanju - tako 2D kot tudi 3D iger. Za potrebe primerjave učinkovitosti 2D in 3D iger so izdelali dve računalniški igri z enako tematiko in nalogami. Dvodimenzionalno različico so izdelali v aplikaciji Scratch.mit, 3D različico pa v programu Kodu Game Lab. V svoji raziskavi so odkrili, da so skupine pri igranju 2D različice videoigre, v primerjavi s skupinami, ki so igrale 3D različico, dosegale višje izobraževalne cilje. Avtorji ugotavljajo, da so učenci pri 3D različici igre namenili preveč časa občudovanju in raziskovanju virtualnega sveta ter s tem pozabili na cilje igre. Hkrati so skupine, ki so igrale 3D različico igre, izrazile višjo mero motiviranosti za učenje ter ocenile uporabniško izkušnjo kot kvalitetnejšo (prav tam). Avtorji izpostavijo tudi, da je iz vprašalnikov razvidno, da je večina otrok že izkusila 2D računalniške igre, zato jim 2D okolje ni predstavljalo težav pri igranju in navigaciji. To pa ne velja za uporabo 3D igre. Ta je nekaterim učencem prinesla težave in frustracije pri navigaciji po prostoru, saj je zaradi sočasne uporabe tipkovnice in miške od njih zahtevala večji mentalni napor (prav tam). Avtorji izpostavijo še eno zanimivo ugotovitev. Raziskava je pokazala, da imajo učenci do 3D iger zelo visoka pričakovanja (statistično pomembno višja od 2D), saj so bile te v času raziskave za mnoge učence svojevrstna novost oz. posebnost (prav tam).

Chatzeparaskevaidou, Karaoli, in Zaharias (prav tam) poudarijo, da morajo izdelovalci iger svoje zamisli zasnovati na igrah, ki so namenjene zabavi in v njih prikrito integrirati izobraževalne elemente. Izkazalo se je namreč, da igra, ki je zasnovana kot učno gradivo, negativno vpliva na učenčevo motivacijo. Z drugimi besedami, videoigra ne sme delovati le kot delovni list, ki posnema zabavno igro. Naš cilj je, da se učenec med igranjem ne zaveda učnega procesa. Prav tako izpostavljajo, da morajo izdelovalci iger veliko časa posvetiti dobrim kontrolam v 3D igrah, saj slaba izvedba zniža motivacijo in užitek pri igranju (prav tam). Poseben premislek terja tudi velikost igre, ki lahko predstavlja pomemben faktor pri njeni uporabnosti na območjih s počasnejšo internetno povezavo (prav tam). Iz raziskav lahko povzamemo, da je za izobraževalno videoigro

(12)

3

bistvena izbira primerne prostorske razsežnosti, saj različni avtorji izpostavljajo tako prednosti kot tudi slabosti 3D iger v primerjavi z 2D izobraževalnimi igrami (Chatzeparaskevaidou, Karaoli, in Zaharias, 2017, Harrington, 2012). Po ugotovitvah Chatzeparaskevaidouja idr. (2017) ter Harringtona (2012) so se 3D igre izkazale za učinkovitejše na področju učenčevega samostojnega raziskovanja virtualnega sveta, v 2D igrah pa lažje usmerimo fokus učenca na izbrane elemente.

Razsežnost virtualnega sveta je zato smiselno izbrati na podlagi učne snovi. Obe raziskavi tudi poudarjata velik pomen dobro izdelanih kontrol (prav tam).

2 Unity (okolje za razvoj videoiger)

Unity Technologies je dansko-ameriško podjetje, ki se ukvarja z izdelavo orodij za izdelavo videoiger. Unity okolje za razvoj videoiger (angleško Unity game engine) je izšlo leta 2005 na Apple Inc. svetovni konferenci razvijalcev (Unity Technologies, 2020). Sprva je bilo okolje zasnovano ekskluzivno za MAC OS X operacijski sistem, od leta 2018 pa je bila podpora za okolje razširjena na več kot 25 platform (med drugim tudi MS Windows ter Android). Okolje lahko uporabimo za razvoj dvodimenzionalnih (2D), tridimenzionalnih (3D), virtualno resničnostnih (Virtual Reality) ter razširjeno resničnostnih (Augmented Reality) iger, kot tudi simulacij, animacij in drugih projektov (Unity game engine, 2020). Unity nudi podporo za primarni skriptni jezik C#, pred tem pa je okolje nudilo podporo za skriptni jezik Boo ter prirejeno različico JavaScript (prav tam).

3 Osnovni elementi 3D videoiger

Pri izdelavi 3D računalniških iger običajno začnemo s programiranjem glavnega lika, ki ga bo igralec krmilil in z njim raziskoval virtualni svet. Na način igranja iger močno vpliva izbrani način kamere ter način krmiljenja glavnega lika. Kamera v igri definira perspektivo, iz katere bo igralec opazoval virtualni svet. Nekateri značilni tipi kamer so: kamera v prvi osebi, kamera v tretji osebi, kamera v tretji osebi visoko nad glavnim likom itd. Nekatere igre omogočajo tudi preklapljanje med kamero v prvi osebi in kamero v tretji osebi. Način krmiljenja pri igrah definira, na kakšen način se bo igralec po prostoru premikal in vršil interakcijo z elementi igre. Načine krmiljenja glavnega lika lahko delimo v dve večji kategoriji: krmiljenje z miško (miškin klik določi, kam naj se glavni lik premakne ali kaj naj stori) in krmiljenje z miško ter tipkovnico (igralec ločeno pritiska tipke za premikanje, z miško pa usmerja pogled). Prvi način je bolj priljubljen pri igrah s kamero v tretji osebi visoko nad glavnim likom, drugi pa pri igrah v prvi ali tretji osebi. V nadaljevanju je podrobneje opisana in pojasnjena realizacija kamere v prvi osebi, kamere v tretji osebi, možnosti preklopa med prvo in tretjo osebo, odbijanja kamere od objektov (angleško camera-collision), krmiljenja glavnega lika z miško in tipkovnico ter animiranja glavnega lika v tretji osebi.

(13)

4

4 Pojasnjeni primeri

4.1 Kamera v prvi osebi

Za kamero v prvi osebi je značilno, da igralec virtualni svet opazuje skozi oči glavnega lika. Če vemo, da glavnega lika ne bo nihče videl, ga lahko nadomestimo, na primer s praznim objektom (GameObject/ Create Empty) ali s kapsulo (GameObject/ 3D Object/ Capsule). V izdelanem primeru smo glavni lik nadomestili s kapsulo (angleško capsule). Kapsula je smiselna izbira, saj s pomočjo enostavnega matematičnega telesa zajame celoten telesni prostor človeka. Pokončna kapsula s primerno nastavitvijo višine, človeško telo idealno objame z zožitvami pri glavi in podplatih, z nastavitvijo radija pa jo prilagodimo širini ramen.

Da lahko virtualni svet opazujemo iz prve osebe, moramo kapsuli dodati glavno kamero. Če kamera obstaja, jo v oknu "Hierarchy" z levim miškinim gumbom povlečemo na kapsulo (slika 1).

Slika 1: Prikaz postopka drag and drop.

Če kamera še ne obstaja, v meniju "Hierarchy" kapsuli dodamo novo kamero (desen miškin klik na kapsulo/ Camera). Postopek prikazuje slika 2.

Slika 2: Dodajanje novega objekta Main Camera.

(14)

5

Tako smo kamero spremenili v otroka (child) kapsule. Ker je kamera otrok, se bo v globalnem koordinatnem sistemu pomikala tako kot njen starš, ki je v našem primeru kapsula. Od njega bo namreč podedovala lastnosti o poziciji. Z izbiro kamere v oknu "Hierarchy", lahko v oknu

"Inspector" vidimo njeno "Transform" komponento (slika 3).

Slika 3: Prikaz Transform komponente kamere.

Preverimo, ali je rotacija nastavljena na (0, 0, 0). Tako zagotovimo, da bo kamera obrnjena v isto smer kot kapsula. Z nastavitvijo pozicije določimo zamik kamere glede na njenega starša. Smiselno je nastaviti "X" in "Z" na 0, "Y" pa na višino glave. Slika 3 prikazuje smiselno nastavitev

"Transform" komponente kamere. Višino kamere lahko nastavimo tudi v oknu scene tako, da izberemo kamero in povlečemo prikazane puščice v želeno smer (slika 4).

Slika 4: Prikaz možnosti spreminjanja pozicije kamere v oknu Scene.

(15)

6

Pripravljeni kapsuli, moramo dodati še skripto, ki bo krmilila vedenje kapsule in glavne kamere. V oknu "Project" ustvarimo novo C# skripto (desni klik/ Create/ C# Script) in jo poljubno poimenujemo (npr. CharControll.cs). Skripto nato dodamo kot komponento kapsule (v meniju

"Inspector" izberemo Add Component/ v iskalnik vpišemo ime skripte/ Char Controll). Postopek dodajanja skripte prikazuje slika 5. Alternativna možnost je prenos skripte CharControll.cs v okno

"Inspector" z načinom dvig in spust (slika 6).

Slika 5: Dodajanje skripte preko Add Component.

Slika 6: Dodajanje skripte s postopkom drag and drop.

(16)

7

4.1.1 Skripta za krmiljenje - CharControll.cs

Na začetku skripte CharControll.cs (slika 7) deklariramo spremenljivke in sklice do komponent, ki jih bomo v skripti uporabili.

public class CharControll : MonoBehaviour {

[Header("Player body transform")]

private Transform playerBody;

[Header("Player camera transform")]

public Transform playerCam;

[Header("Mouse sensitivity")]

public float mouseSens = 100f;

private float camX = 0f;

void Start() {

playerBody = transform;

playerCam = GetComponentInChildren<Transform>().Find("Main Camera");

}

Slika 7: Spremenljivke in metoda Start v skripti CharControll.cs

V vrstici "private Transform playerBody;" deklariramo privatni "Transform" imenovan

"playerBody".

V metodi "void Start()" dodamo vrstico "playerBody = transform;", s čemer spremenljivki

"playerBody" priredimo transform komponento objekta, na katerem je skripta pripeta (v tem primeru je to kapsula).

V naslednji vrstici "public Transform playerCam;" deklariramo javni "Transform", imenovan

"playerCam". Ker je deklariran kot javni, se spremenljivka prikaže v okolju Unity, v oknu

"Inspector", pod lastnostmi komponente "Char Controll". Če v označeno polje povlečemo glavno kamero, bomo pridobili referenco do "Transform" komponente kamere. Alternativna rešitev je dodatna vrstica v metodi "void Start()". Vrstica "playerCam = GetComponentInChildren<Transform>().Find("Main Camera");" spremenljivki "playerCam"

priredi komponento, ki jo pridobi od otroka objekta, na katerem je skripta pripeta. Komponenta mora biti tipa "Transform", poiskati pa jo mora pri otroku z imenom "Main Camera" – torej

".Find("ime otroka)".

Sledi vrstica "public float mouseSens = 100f;", kjer deklariramo javno spremenljivko tipa plavajoče vejice z imenom "mouseSens". Spremenljivko bomo uporabili za določitev odzivnosti miške (kako hitri bodo premiki miške). Spremenljivko deklariramo kot javno, da lahko odzivnost sproti nastavljamo v oknu "Inspector".

(17)

8

Zadnja spremenljivka, ki jo deklariramo je "private float camX = 0f". V spremenljivko bomo shranjevali vrednost rotacije kamere po X koordinati. Potrebujemo jo zato, da ji lahko prištevamo premik miške, nato pa jo omejimo med -90 in 90 stopinj (gor in dol).

Sledi koda, ki zagotovi krmilno funkcijo skripte. Slika 8 prikazuje kodo, ki jo dodamo v metodo

"void Update()".

void Update() {

float mouseX = Input.GetAxis("Mouse X") * mouseSens * Time.deltaTime;

float mouseY = Input.GetAxis("Mouse Y") * mouseSens * Time.deltaTime;

if (mouseX != 0) {

playerBody.rotation *= Quaternion.Euler(0, mouseX, 0);

}

if (mouseY != 0) {

camX += mouseY;

camX = Mathf.Clamp(camX, -90f, 90f);

playerCam.localRotation = Quaternion.Euler(camX, 0, 0);

} }

Slika 8: Metoda void Update skripte CharControll.cs.

V vrstici "float mouseX = Input.GetAxis("Mouse X") * mouseSens * Time.deltaTime"

deklariramo spremenljivko "mouseX" tipa plavajoče vejice, v katero shranimo vrednost, ki jo vrne virtualna os "Mouse X" (premik miške po X osi). Vrednost pomnožimo s spremenljivko

"mouseSens". V izogib težavam zaradi različnega števila sličic na sekundo vrednost pomnožimo še s "Time.deltaTime". Zelo podobno v naslednji vrstici storimo še za "mouseY".

V vrstici "if (mouseX != 0)" s pogojnim stavkom preverimo, ali smo premaknili miško v predpostavljeni smeri X. Znotraj bloka v vrstici "playerBody.rotation *= Quaternion.Euler(0, mouseX, 0)" rotacijski kvaternion objekta "playerBody" zmnožimo s kvaternionom, ki vsebuje vrednost miškine osi X (kvaternion je bolje pojasnjen v poglavju 5. Pojasnitev zahtevnejših elementov). To storimo, da premik miške prevedemo na spremembo orientacije glavnega lika. Da se izognemo neposrednemu računanju s kvaternioni, uporabimo metodo ".Euler()", ki kvaternion prevede v "Vector3". Če povzamemo, bi v kontekstu stopinjskih kotov glavni lik zavrteli s prištetjem delta kota (za koliko stopinj nas premik miške zavrti), v kontekstu kvaternionov pa se ta sprememba izvede z množenjem.

S pogojnim stavkom "if (mouseY != 0)" preverimo, ali se je zgodil premik miške v smeri Y. Znotraj bloka v vrstici "camX += mouseY" najprej vrednosti "camX" prištejemo vrednost "mouseY".

Razlog za dano prištevanje je relacija med Y osjo miške in rotacijskim vektorjem kamere. Če miško

(18)

9

gledamo od zgoraj navzdol in si predstavljamo koordinatni sistem, bo ob premikih miške naprej oz. nazaj prišlo do sprememb na Y osi.

Če si predstavljamo še tridimenzionalni rotacijski vektor kamere, stojimo pravokotno glede na osi X in Y, gledamo v smer Y in smo kolinearni z osjo Z. Sprememba vrednosti "X" rotacijskega vektorja, nas zavrti okoli X osi, kar preusmeri pogled kamere navzdol oz. navzgor. Tako lahko delto "mouseY" prištejemo vrednosti "X" rotacijskega vektorja kamere in s tem dvignemo oz.

spustimo pogled igralca.

V naslednji vrstici "camX = Mathf.Clamp(camX, -90f, 90f)" uporabimo knjižnico "Math" in metodo "Clamp", s katero omejimo vrednost "camX" med vrednosti -90 in 90. S tem preprečimo dvig in spust kamere preko danih vrednosti (navpično gor/dol).

Na koncu še z vrstico "playerCam.localRotation = Quaternion.Euler(camX, 0, 0)" prepišemo kvaternion lokalne rotacije kamere z novim kvaternionom, ki vsebuje posodobljeno vrednost rotacije kamere v smeri "X". Pri tem zopet uporabimo metodo Euler().

Ob pritisku gumba Start lahko s pomočjo miške usmerjamo pogled v vse smeri, kamera pa je omejena v navpični osi za 90° gor in dol (razpon 180°).

Če želimo, da se ob pričetku igre miška zaklene na sredino zaslona, kurzor pa postane neviden, lahko v metodo "void Start()" dodamo še vrstici "Cursor.lockState = CursorLockMode.Locked;"

in "Cursor.visible = false;". S prvo nastavimo zaklenjenost kurzorja na vrednost "Locked"

(zaklenjen), z drugo pa nastavimo vidnost kurzorja na vrednost "false", kar pomeni, da ne bo viden.

Po ponovnem pričetku igre se kurzor skrije in zaklene na sredino zaslona igre, kamera pa deluje tako kot prej.

(19)

10

4.2 Kamera v tretji osebi

Za kamero v tretji osebi je značilno, da igralec glavni lik in virtualni svet okoli njega gleda iz perspektive, ki zajema vsaj del podobe glavnega lika, ki se giblje po prostoru – glavni lik običajno gledamo v hrbet, opazujemo pa virtualni svet pred njim oz. okoli njega. Posledično pri kameri v tretji osebi postane pomembna podoba glavnega lika ter animiranje njegovega premikanja (več o animacijah v poglavju 4.4 Premikanje v tretji osebi…). Prav tako je smiselno izdelati kvaliteten camera-collision, ki prepreči, da bi kamera prehajala skozi objekte s t. i. colliders (podpoglavje 4.2.2 Skripta za interakcijo kamere…).

Če elementov camera-collision ne vključimo v igro, bo naša kamera popolnoma ignorirala dejstvo, da se v okolici lika nahajajo tudi drugi objekti. Kamera predstavlja vidno polje igralca. V vidnem polju se elementi izrisujejo od najbolj oddaljenih proti najmanj oddaljenim. Če oddaljenost kamere nastavimo na npr. vrednost 2, zid za glavnim likom pa je oddaljen za vrednost 1, bo igralec pred seboj videl zgolj zid, saj je glavni lik od kamere oddaljen bolj (se izriše prej), zid pa je manj oz.

je kameri bližje (se izriše kasneje).

V izdelanem primeru bomo glavni lik nadomestili s preprosto 3D kapsulo. Najprej v okolju Unity dodamo novi 3D objekt kapsulo (GameObject/ 3D Object/ Capsule). Kapsuli nato dodamo otroka, ki naj bo prazen objekt (v oknu Hierarchy desni klik na kapsulo/ Create Empty). Prazen objekt preimenujemo v "CamFollow" (slika 9), saj ga bomo uporabili kot bazo, ki ji bo kamera sledila in se okoli nje rotirala.

Slika 9: Postopek preimenovanja objektov.

Prazen objekt premaknemo na višino ramen ali glave (višino nastavimo po želji). S tem dosežemo, da bo kamera okoli glavnega lika krožila na višini glave in ne pri dnu oz. okoli nog. Slika 10 prikazuje nastavitve "Transform" komponente objekta "CamFollow", ki smo jih uporabili v prikazanem primeru.

(20)

11

Slika 10: Prikaz Transform komponente objekta CamFollow.

Podobno bi storili, če bi namesto kamere uporabili neko drugo figuro, npr. pripravljeno figuro iz Asset Store. Figuri bi dodali prazen objekt, ga preimenovali in dvignili bližje glavi.

Sledi priprava baze za kamero in glavne kamere. V okolju Unity dodamo nov prazen objekt (GameObject/ Create Empty) in ga preimenujemo v "CamBase". Objektu dodamo novega otroka - kamero (v oknu Hierarchy desni klik na CamBase/ Camera). Izberemo kamero in ji spremenimo oznako (Tag). V oknu "Inspector" zgoraj zasledimo "Tag", v plavajočem meniju pa izberemo

"MainCamera". Kamero tudi preimenujemo v "Main Camera". "Transform" komponento objekta

"CamBase" bomo uporabili za sledenje in kroženje okoli objekta "CamFollow". Ker smo objektu

"CamBase" dodali otroka "Main Camera" bo ta vedno sledila premikom objekta "CamBase", prav tako pa bo ohranjala njegovo orientacijo. S pomočjo vrtenja in orientiranja "CamBase" bomo upravljali vrtenje in orientiranje glavne kamere. "Transform" komponento objekta "Main Camera"

pa bomo uporabili pri programiranju camera-collision, saj bomo spreminjali transform komponento kamere tako, da se bo ta približevala in oddaljevala od svojega starša "CamBase".

Ustvarimo še dve novi skripti. Prvo poimenujemo CameraOrbit.cs drugo pa CameraCollision.cs. S prvo bomo krmilili rotacijo objekta "CamBase", zato jo pripnemo na objekt "CamBase", z drugo skripto pa bomo krmilili oddaljenost kamere od igralca ter interakcijo kamere s colliders (efekt camera-collision), zato jo pripnemo objektu "Main Camera". Skripte pripnemo s pritiskom na "Add Component" v oknu "Inspector", nato pa izberemo "Script" in želeno skripto. Alternativna rešitev je premik skripte med komponente s postopkom drag and drop.

(21)

12

4.2.1 Skripta za vrtenje kamere - CameraOrbit.cs

Na začetku skripte CameraOrbit.cs deklariramo spremenljivke in sklice do komponent, ki jih bomo kasneje uporabili (slika 11).

public class CameraOrbit : MonoBehaviour {

[Header("Target transform")]

public Transform followTransform;

[Header("Camera properties")]

private Vector3 camAngles;

public float verticalSpeed = 70f;

public float horizontalSpeed = 70f;

void Start() {

camBaseRot = transform.rotation.eulerAngles;

}

Slika 11: Deklaracija spremenljivk v skripti CameraOrbit.cs.

V vrstici "public Transform followTransform;" deklariramo novo spremenljivko tipa "Transform"

z imenom "followTransform". Sem bomo shranili referenco do transform kompomente objekta

"CamFollow" (tistega, ki mu bo "CamBase" sledil). Da spremenljivki določimo vrednost, moramo v oknu "Inspector" v polje spremenljivke spustiti objekt "CamFollow".

V vrstici "private Vector3 camBaseRot;" deklariramo privatno spremenljivko tipa "Vector3" z imenom "camBaseRot". Ta bo hranila rotacijski vektor objekta "CamBase". Pred prvim klicem metode "void Update()" mu moramo določiti začetne vrednosti, zato dodamo v metodo "void Start()" vrstico " camBaseRot = transform.rotation.eulerAngles;". V spremenljivko "camBaseRot"

shranimo rotacijski kvaternion pretvorjen v Eulerjeve kote. Na tak način lahko v izbrani spremenljivki hranimo vektor rotacijskih kotov.

V vrsticah "public float verticalSpeed = 70f;" ter "public float horizontalSpeed = 70f;" deklariramo novi javni spremenljivki tipa plavajoče vejice, s katerimi bomo določali vertikalno oz. horizontalno hitrost miške (oz. hitrost premikanja kamere). Če ne potrebujemo ločenih nastavitev za hitrost v vertikalni in horizontalni smeri, lahko ustvarimo le eno spremenljivko, s katero nadomestimo obstoječi.

(22)

13

V metodo "void Update()" dodamo kodo, ki skripti doda krmilno funkcijo (slika 12).

void Update() {

float mouseX = Input.GetAxis("Mouse X");

float mouseY = Input.GetAxis("Mouse Y");

camBaseRot += new Vector3((mouseY * horizontalSpeed * Time.deltaTime), (mouseX * verticalSpeed * Time.deltaTime), 0);

camBaseRot.x = Mathf.Clamp(camBaseRot.x, -70f, 90f);

transform.rotation = Quaternion.Euler(camBaseRot);

transform.position = followTransform.position;

}

Slika 12: Metoda void Update() skripte CameraOrbit.cs.

Z vrsticama "float mouseX = Input.GetAxis("Mouse X");" ter "float mouseY = Input.GetAxis("Mouse Y");" deklariramo spremenljivke tipa plavajoče vejice. V spremenljivki shranimo vrednosti virtualnih osi miške (v prvo vrednost osi X, v drugo pa vrednost osi Y).

V vrstici "camBaseRot += new Vector3((mouseY * horizontalSpeed * Time.deltaTime), (mouseX

* verticalSpeed * Time.deltaTime), 0);" spremenljivki "camBaseRot" prištejemo novi vektor, ki vsebuje premike miške. Premike sočasno pomnožimo s konstantami ("horizontalSpeed",

"verticalSpeed"), ki vplivajo na končno hitrost premikov miške. Obe vrednosti pomnožimo še s Time.deltaTime, da se izognemo težavam zaradi razlik v številu sličic na sekundo. Zadnja komponenta vektorja ostane 0, saj kamere ne želimo vrteti okoli Z osi.

Dvig in spust kamere za več kot 90° omejimo z vrstico "camBaseRot.x = Mathf.Clamp(camBaseRot.x, -70f, 90f);", v kateri z metodo "Clamp" dodamo zgornjo in spodnjo mejo komponente X, vektorja "camBaseRot" (podobno kot v poglavju 4.1.1). V prikazanem primeru smo kamero omejili na 90° navzgor ter 70° navzdol. Do komponente "X" dostopamo s kodo "camBaseRot.x", ki dostopa do spremenljivke "X" objekta tipa "Vector3" (predstavljamo si lahko Vector3.x).

V vrstici "transform.rotation = Quaternion.Euler(camBaseRot);" spremenljivki "rotation"

priredimo nove vrednosti shranjene v vektorju "camBaseRot". Spremenljivka "rotation" je definirana v "Transform" komponenti objekta, na katerem je skripta pripeta. Spremenljivka

"rotation" je tipa kvaternion, zato v izogib računanju s kvaternioni uporabimo metodo "Euler()".

Metoda sprejme posamične komponente vektorja ali podatkovni tip "Vector3" kot celoto. Ko vrtimo objekt "CamBase", vrtimo tudi glavno kamero, saj je kamera njegov otrok.

Na koncu skripte v vrstici "transform.position = followTrasform.position;" posodobimo še pozicijo objekta "CamBase" tako, da ga premaknemo na mesto objekta "CamFollow".

(23)

14

4.2.2 Skripta za interakcijo kamere z objekti - CameraCollision.cs

Če želimo izdelati kvalitetno kamero v tretji osebi, moramo nujno pomisliti na situacije, ko drugi predmeti kameri prekrižajo pot. Tak dogodek imenujemo "camera-collision", saj govorimo o trku kamere s predmeti v virtualnem svetu. V prikazani situaciji bomo trke kamere zaznali s pomočjo nevidnih vektorjev okoli kamere. Ob trku katerega koli od teh vektorjev z objektom, ki ima dodano collider komponento (preko katere lahko trk zaznamo), bomo ta trk zabeležili v pripravljeno spremenljivko. Zabeležen trk bo hranil podatek o točki trka in razdaljo od izhodišča vektorja do točke trka. Tako lahko kamero približamo na razdaljo točke trka in še nekoliko bližje, da se gotovo izognemo prekrivanju vidnega polja ter objekta (angleško clipping).

Na začetku skripte CameraCollision.cs deklariramo spremenljivke ter sklice do komponent, ki jih bomo kasneje uporabili (slika 13).

public class CameraCollision : MonoBehaviour {

[Header("Cam collision layerMask and radius")]

public LayerMask layers;

[Header("Sensitivity")]

public float scrollSens = 0.25;

public float smoothCam = 6f;

[Header("Cam direction & distance parameters")]

private Vector3 vectDir;

private float vectLen;

private float minDist = 1f;

private float maxDist = 4f;

[Header("Cam collision offsets")]

private Vector3 offsetDown = new Vector3(0, -0.3f, -0.4f);

private Vector3 offsetLeft = new Vector3(-0.3f, 0, -0.4f);

private Vector3 offsetRight = new Vector3(0.3f, 0, -0.4f);

private void Start() {

vectDirection = transform.localPosition.normalized;

vectLenght = transform.localPosition.magnitude;

Cursor.lockState = CursorLockMode.Locked;

Cursor.visible = false;

}

Slika 13: Deklaracija spremenljivk v skripti CameraCollision.cs.

V vrstici "public LayerMask layers;" deklariramo javno spremenljivko tipa "LayerMask"

imenovano "layers". S pomočjo dane spremenljivke bomo v oknu "Inspector" nastavili plasti, ki

(24)

15

jih bo skripta upoštevala pri zaznavanju trkov. To nam omogoča, da določimo, od katerih trkalnikov (colliders) se bo kamera odbijala.

V vrstici "public float scrollSens = 0.25;" deklariramo javno spremenljivko tipa plavajoče vejice imenovano "scrollSens". Z vrednostjo "scrollSens" bomo določili velikosti korakov približevanja (oziroma oddaljevanja) kamere proti glavnemu liku (oziroma stran od njega). Če zavrtimo miškino kolo za en korak, se bo v danem primeru kamera premaknila za 0.25f enote.

S kodo "public float smoothCam = 6f;" v naslednji vrstici, deklariramo javno spremenljivko tipa plavajoče vejice, ki smo jo poimenovali "smoothCam". Z vrednostjo spremenljivke določimo, kako hitro se bo kamera približala razdalji, ki jo določa spremenljivka "maxDist". Če je vrednost

"smoothCam" majhna (npr. 3), se bo kamera počasi in gladko približala oz. oddaljila od glavnega lika, če pa je vrednost večja (npr. 15), se bo kamera premaknila sunkovito.

V vrstici "private Vector3 vectDir;" deklariramo privatno spremenljivko tipa "Vector3" imenovano

"vectDir". Na mesto spremenljivke v metodi "void Start()" shranimo enotski vektor lokalne pozicije transforma objekta, na katerega je skripta pripeta. Lokalna pozicija ima vrednosti določene glede na starša. Te pridobimo v vrstici "vectDir = transform.localPosition.normalized;", kjer najprej poiščemo lokalno pozicijo, nato pa jo z metodo "normalized" pretvorimo v enotski vektor.

Preprosteje povedano, v "vectDir" shranimo vrednost 1 na mestih, kjer vrednost ni bila enaka 0, sicer pa pustimo 0.

V vrstici "private float vectLen;" deklariramo privatno spremenljivko imenovano "vectLen". V metodi "void Start()" v vrstici " vectLenght = transform.localPosition.magnitude;" na mesto spremenljivke shranimo oddaljenost kamere. Oddaljenost kamere pridobimo tako, da najprej poiščemo lokalni pozicijski vektor, nato pa z metodo "magnitude" pridobimo dolžino vektorja.

V vrsticah "private float minDist = 1f;" ter "private float maxDist = 4f;" deklariramo privatni spremenljivki tipa plavajoče vejice. V spremenljivki shranimo najmanjšo ("minDist") oz. največjo ("maxDist") razdaljo oddaljenosti kamere. Vrednosti bosta upoštevani v primeru, ko spreminjamo oddaljenost kamere z miškinim kolescem oz. kadar je zaznan trk kamere in se ta zaradi tega približa.

V vrsticah pod naslovom [Header("Cam collision offsets")] deklariramo privatne spremenljivke tipa "Vector3", v katere shranimo vektorje z vrednostmi odmikov, ki bodo vplivali na žarke, s katerimi bomo preverjali trke kamere. Spremenljivke so deklarirane po ključu kode

"private Vector3 imeOdmika = new Vector3(x, y, z);". Z vrednostjo "x" določimo zamik žarka levo (x < 0) oz. desno (x > 0), z vrednostjo "y" določimo odmik dol (y < 0) oz. gor (y > 0), z vrednostjo "z" pa določimo odmik bližje (z > 0) ali dlje stran (z < 0). Odmike uporabimo v metodi

"camCollisionUpdate()".

Če želimo skriti kurzor ter njegovo pozicijo zakleniti na sredino igralnega polja, dodamo v metodo

"void Start()" vrstici "Cursor.visible = false;" in "Cursor.lockState = CursorLockMode.Locked;".

Prva nastavi vidnost kurzorja na "false", druga pa nastavi zaklenjenost kurzorja na način

"zaklenjen".

(25)

16

Sledi metoda void Update(), v kateri dodamo kodo, ki omogoča približevanje in oddaljevanje kamere s premikom miškinega kolesca (slika 14).

void Update() {

float mouseWheel = Input.GetAxis("Mouse ScrollWheel");

if (mouseWheel != 0) {

maxDistance = Mathf.Clamp(maxDistance + (mouseWheel*scrollSens), 1f, 4f);

} }

Slika 14: Metoda Update v skripti CameraCollision.cs.

V vrstici "float mouseWheel = Input.GetAxis("Mouse ScrollWheel");" najprej deklariramo spremenljivko mouseWheel, v katero shranimo vrednost virtualne osi z imenom "Mouse ScrollWheel" (miškino kolesce).

Nato v pogojnem stavku "if (mouseWheel != 0)" preverimo, ali smo zavrteli miškino kolesce (to drži, kadar vrednost osi ni enaka nič). Kadar ta pogoj velja, se izvede vrstica

"maxDist = Mathf.Clamp(maxDist + (mouseWheel*scrollSens), 1f, 4f);", kjer spremenljivki

"maxDist" določimo novo vrednost. Novo vrednost pridobimo s prištevanjem vrednosti

"mouseWheel" spremenljivki "maxDist". Pri tem vrednost "mouseWheel" pomnožimo še s konstanto "scrollSens", ki vpliva na velikost pomikov kamere. Na ta način bi lahko odmik kamere povečevali v neskončnost, zato oddaljenost kamere omejimo s pomočjo metode "Mathf.Clamp()".

V danem primeru smo uporabili vrednosti 1f in 4f, ki omogočata približevanje kamere do vrednosti 1f in oddaljitev kamere do vrednosti 4f. Seveda je vrednosti smiselno prilagoditi vsaki igri posebej.

(26)

17

Na koncu skripte, znotraj bloka metode "LateUpdate()" (slika 15), izvedemo še preverjanje trkov in premik kamere na določeno razdaljo.

private void LateUpdate() {

RaycastHit hit;

#region directional vectors

Vector3 parentPos = transform.parent.position;

Vector3 desiredDown = transform.parent.TransformPoint(

vectDir * vectLen + offsetDown) - parentPos;

Vector3 desiredLeft = transform.parent.TransformPoint(

vectDir * vectLen + offsetLeft) - parentPos;

Vector3 desiredRight = transform.parent.TransformPoint(

vectDir * vectLen + offsetRight) - parentPos;

#endregion

#region Debug draw ray

Debug.DrawRay(parentPos, desiredDown, Color.green);

Debug.DrawRay(parentPos, desiredLeft, Color.red);

Debug.DrawRay(parentPos, desiredRight, Color.blue);

#endregion

if (Physics.Raycast(parentPos, desiredLeft, out hit, desiredLeft.magnitude, layers)||

Physics.Raycast(parentPos, desiredRight, out hit, desiredRight.magnitude, layers)||

Physics.Raycast(parentPos, desiredDown, out hit, desiredDown.magnitude, layers)) {

vectLen = Mathf.Clamp(hit.distance * 0.99f, minDist, maxDist);

transform.localPosition = Vector3.Lerp(

transform.localPosition, vectDir * vectLen, 20 * Time.deltaTime);

} else {

vectLen = maxDist;

transform.localPosition = Vector3.Lerp(

transform.localPosition, vectDir * vectLen, smoothCam * Time.deltaTime);

} }

Slika 15: Metoda LateUpdate v skripti CameraCollision.cs.

Najprej deklariramo novo spremenljivko "RaycastHit hit;". Za spremenljivko "hit" izberemo podatkovni tip RaycastHit , ki nam omogoča hranjenje podatkov o presečišču (ali več presečiščih) žarka in trkalnikov. V spremenljivko bomo kasneje shranili zaznano presečišče in iz njega prebrali razdaljo.

Znotraj regije "#region directional vectors" deklariramo več spremenljivk tipa "Vector3". Prvo poimenujemo "parentPos", vanjo pa shranimo globalni vektor pozicije transforma starša (starša objekta, na katerem je pripeta skripta – torej pozicija "CamBase"). Sledijo spremenljivke

"desiredDown", "desiredLeft" in "desiredRight", v katere shranimo vektorje, ki kažejo v smer, kamor želimo "izstreliti" žarek, ki bo preveril trk kamere. Vektorje s pravilno usmeritvijo dobimo

(27)

18

po ključu kode "Vector3 imeVektorja = transform.parent.TransformPoint(vectDir * vectLen + offset) - parentPos;". V deklarirani vektor shranimo vektor pozicije starša, na katerem izvedemo metodo "TransformPoint". Kot argument vstavimo vektor sestavljen iz "vectDir" (lokalna usmerjenost pozicije kamere) pomnožen z "vectLen" (magnituda lokalnega pozicijskega vektorja kamere), temu pa prištejemo še odmik (offset). Vse skupaj metoda pretvori v globalne koordiante.

Na koncu pretvorjenemu vektorju odštejemo še vektor "parentPos", da ga usmerimo v pravilno smer.

Znotraj regije "#region Debug draw ray" so dodani t.i. debug žarki, ki so namenjeni odkrivanju napak. Omogočijo nam vizualizacijo vektorjev, s katerimi bomo preverjali trke kamere. Metoda

"Debug.DrawRay(parentPos, desiredDown, Color.green);" sprejme začetno pozicijo žarka ("parentPos") ter usmerjenost z magnitudo izraženo kot vektor (v katero smer in kako daleč). Po želji lahko določimo še barvo žarka. Izris žarkov prikazuje slika 16Napaka! Vira sklicevanja ni bilo mogoče najti., iz katere je razvidno, da žarki izhajajo iz pozicije starša ("parentPos") v smer kamere, zaradi prištetih odmikov (offsets) pa se razpršijo navzven.

Slika 16: Prikaz Debug.DrawRay() žarkov.

V bloku pogojnega stavka preverimo, ali kateri od žarkov trči v trkalnik (collider) iz okolice. Prvi pogoj "Physics.Raycast(parentPos, desiredLeft,out hit, desiredLeft.magnitude, layers" je koda z zamikom v levo. Kot začetno stanje vstavimo pozicijo starša, kot usmeritev vstavimo vektor

"desiredLeft", v spremenljivko "hit" se shranijo podatki o morebitnem trku, dolžino žarka določimo z magnitudo vektorja "desiredLeft", plasti, ki jih bo žarek zaznal, pa določimo s spremenljivko

"layers". Pogojni stavek vsebuje tri pogoje ločene z operatorjem ALI, s čemer zagotovimo, da se

(28)

19

vsebina pogojnega stavka izvede, kadar drži vsaj en od pogojev. V primeru, ko kateri od žarkov (levo, desno, dol) vrne vrednost "true", se podatek o točki trka shrani v spremenljivko "hit", nato pa se izvede blok.

Znotraj bloka, v vrstici "vectLen = Mathf.Clamp(hit.distance * 0.99f, minDist, maxDist);", spremenljivki "vectLen" najprej določimo novo vrednost. Vrednost nastavimo na razdaljo od izhodišča žarka do točke trka, nato razdaljo pomnožimo še s številom 0.99f. To storimo zato, da razdaljo malenkost skrajšamo. S tem dosežemo, da se kamera premakne pred objekt, v katerega je žarek trčil (če trči na razdalji 5, se kamera premakne na razdaljo 4.95, kar je bližje od 5). Razdaljo do točke trka pridobimo iz objekta "hit" s klicem "hit.distance". Z metodo "Mathf.Clamp()"

omejimo novo vrednost med "minDist" in "maxDist", saj ne želimo, da se kamera približa ali oddalji v neskončnost.

Nato moramo kamero še premakniti na novo pridobljeno razdaljo. To naredimo v vrstici

"transform.localPosition = Vector3.Lerp(transform.localPosition, vectDir * vectLen, 20 * Time.d eltaTime);", kjer lokalno pozicijo kamere premaknemo na novo točko z uporabo metode

"Vector3.Lerp(…)". V metodo vstavimo vektor začetne pozicije, vektor želene končne pozicije (to je "vectDir" pomnožen z novo razdaljo "vectDir") ter hitrost premika. Vrednost hitrosti nastavimo relativno visoko, saj želimo, da se kamera hitro odmakne stran od ovir (po želji lahko vrednost znižamo).

Če noben od pogojev v pogojnem stavku ni izpolnjen, se izvede koda v bloku "else{}". Najprej s kodo "vectLen = maxDist;" vrednost spremenljivke "vectLen" nastavimo na maksimalno dopuščeno vrednost – "maxDist". Za tem pozicijo kamere premaknemo s klicem metode "Lerp"

(tako kot v predhodnih opisih). Tokrat za hitrost premika vzamemo vrednost "smoothCam", ki jo lahko nastavimo v oknu "Inspector". Pri testiranju se je vrednost 3f izkazala za najprimernejšo vrednost spremenljivke "smoothCam".

(29)

20

4.3 Premikanje v prvi osebi

Premikanje igralca v prvi osebi je enostavnejše, saj v tem primeru ni potrebno animirati glavnega lika. Poglejmo si zelo enostaven primer premikanja igralca v prvi osebi, kjer se igralec premika naprej, nazaj, levo in desno pri konstantni hitrosti. Kompleksnejši primer premikanja je prikazan v naslednjem poglavju (4.4 Premikanje v tretji osebi in animacije).

4.3.1 Nadgrajena skripta za krmiljenje - CharControll.cs

Za krmiljenje lahko uporabimo in nadgradimo skripto CharControll.cs iz poglavja 4.1 Kamera v prvi osebi. Slika 17 prikazuje kodo, ki jo dodamo skripti CharControll.cs, ko želimo dodati možnost premikanja glavnega lika.

public class CharControll : MonoBehaviour {

[Header("Controlls sensitivity")]

public float moveSpeed = 3f;

}

void Update() {

if (Input.GetButton("Vertical") || Input.GetButton("Horizontal")) {

Float vertAxis = Input.GetAxis("Vertical") * moveSpeed * Time.deltaTime;

Float horAxis = Input.GetAxis("Horizontal") * moveSpeed * Time.deltaTime;

playerBody.Translate(new Vector3(horAxis, 0, vertAxis));

} }

Slika 17: Nadgrajena skripta CharControll.cs z dodanim premikanjem.

V vrstici "public float moveSpeed = 3f;" deklariramo novo spremenljivko "moveSpeed" tipa plavajoče vejice, s katero bomo določili hitrost premikanja objekta.

Sledi koda v bloku metode "void Update()". Pogojni stavek "if (Input.GetButton("Vertical")

|| Input.GetButton("Horizontal"))" preveri, ali je pritisnjena katera od tipk definiranih v virtualnih oseh "Vertical" ali "Horizontal". V primeru, da je, v bloku najprej deklariramo novi spremenljivki

"vertAxis" ter "horAxis" tipa plavajoče vejice. Vanju shranimo podatke virtualnih osi pomnožene s hitrostjo premikanja ("moveSpeed") ter z vrednostjo Time.deltaTime.

Na koncu premaknemo objekt, na katerem je pripeta skripta (tega smo poimenovali "playerBody").

Premaknemo ga z metodo "Translate" v smer novega vektorja z vstavljenimi vrednostmi "horAxis"

ter "vertAxis". Tako se bo glavni lik premaknil s hitrostjo "moveSpeed" v smer pritisnjenih tipk.

Pri tem je upoštevana tudi rotacija glavnega lika (naprej bo vedno v smeri pogleda kamere).

(30)

21

4.4 Premikanje v tretji osebi in animacije

Premikanje glavnega lika v tretji osebi je izvedeno na podoben način kot premikanje v prvi osebi.

Glavna razlika med omenjenima tipoma premikanj je upoštevanje primernih animacij. Za prikazan primer smo uporabili paket animacij iz Unity Asset Store (Basic Motions FREE, 2020).

Lik najprej pripravimo, tako kot je to prikazano v poglavju 4.2. Kamera v tretji osebi, le da namesto kapsule uporabimo pripravljeni model "Basic Motions Dummy.fbx".

4.4.1 Animator Controller

Preden napišemo skripto, s katero aktiviramo animacije, moramo najprej pripraviti Animator Controller s stanji animacij in prehodi med stanji. Animator Controller je orodje, ki poenostavi preklapljanje med animacijami, saj ponuja intuitiven grafični vmesnik, hkrati pa možnost, da njegovo delovanje nadziramo preko skripte. V pripravljenem primeru smo Animator Controller izdelali tako, kot to prikazuje slika 18. V našem primeru smo uporabili zgolj animacije, ki vplivajo na okostje lika (točke zgibov in togih predelov lika), ne pa tudi na njegovo pozicijo v prostoru.

Vključene animacije so mirovanje, tek, hoja, šprint, tek nazaj ter skok, ki je sestavljen iz treh stanj – odskoka, leta in pristanka. Animaciji "Jump" smo dodali ekskluzivni prehod v "MidAir", slednjemu pa ekskluzivni prehod v "Landing" (to pomeni, da si morajo ti koraki vedno slediti).

Animacijama "Jump" in "Landing" smo dodali tudi pogoj, da se ne moreta vračati sami vase, ko se izvedeta do konca (izogib ponovljeni animaciji odskoka ali pa pristanka). Kontrolerju smo dodali še boolean spremenljivke "Jump", "MidAir", "Landing", "Walk", "Run", "Sprint", "RunBack" ter

"Idle", s katerimi smo določili, v katerem stanju se mora glavni lik v določenem trenutku nahajati.

Za vsak prehod med animacijami (slika 19) so določene vrednosti boolean spremenljivk, ki morajo veljati, da kontroler preide v določeno animacijo (slika 20). Pogoje je potrebno nastaviti za vsak prehod v orodju Animator Controller.

Pripravljeni Animator Controller pripnemo komponenti Animator (v oknu Inspector poiščemo komponento Animator/ izdelani Animator Controller povlečemo v polje "Controller"). Če komponenta Animator še ne obstaja, jo moramo dodati (v oknu Inspector s pritiskom na Add Component/ Miscellaneous/ Animator).

(31)

22

Slika 18: Primer Animator Controllerja.

Slika 19: Primer prehoda med animacijami.

Slika 20: Primer vrednosti boolean spremenljivk za prehod v animacijo Run.

(32)

23

4.4.2 Deklaracija spremenljivk v skripti PlayerMovement.cs

Na začetku skripte PlayerMovement.cs deklariramo spremenljivke in ustvarimo sklice do komponent, ki jih bomo v skripti uporabili (slika 21).

public class PlayerMovement : MonoBehaviour {

//spremenljivke za komponente Rigidbody rigbody;

CapsuleCollider capColl;

Animator animator;

[Header("Main Camera Transform")]

public Transform mainCam; //transform glavne kamere

[Header("Player movement and rotation properties")]

public float walkSpeed = 1.5f; //hitrost hoje naprej public float runSpeed = 4f; //hitrost teka

public float sprintSpeed = 6f; //hitrost šprintanja public float backSpeed = 2f; //hitrost teka nazaj public float LRspeed = 2f; //hitrost hoje vstran public int rotSpeed = 110; //hitrost rotacije igralca public int jumpForce = 400; //višina skoka

// Quaternion (0,0,0,1), ki ga bomo uporabili za beleženje rotacije igralca private Quaternion desiredRot = Quaternion.identity;

[Header("Player movement bool")]

public bool isGrounded; //preverimo, če glavni lik stoji na tleh public bool enJumpStarted; //ali je korutina "EnJump()" vklopljena

private void Start() {

rigbody = GetComponent<Rigidbody>(); //sklici na komponente animator = GetComponent<Animator>();

capColl = GetComponent<CapsuleCollider>(); //sklic na skripto StartCoroutine(EnGroundCheck()); //vklopimo korutine

StartCoroutine(EnLShiftDown());

StartCoroutine(EnLCtrlDown());

StartCoroutine(EnJump());

}

Slika 21: Deklaracija spremenljivk in sklici do komponent v skripti PlayerMovement.cs.

Pri vrhu skripte deklariramo spremenljivke (rigbody, capColl, animator), ki se bodo sklicevale na komponente objekta, na katerem je skripta pripeta. Sklice do komponent izvedemo v metodi

"void Start()" po ključu kode "imeSpremenljivke = GetComponent<Komponenta>();".

Sledi vrstica "public Transform mainCam;", v kateri deklariramo novo spremenljivko tipa

"Transform" z imenom "mainCam". Spremenljivka se bo sklicevala na transform komponento

(33)

24

kamere, da to dosežemo, pa moramo v oknu Inspector spremenljivki "mainCam" dodati glavno kamero (način povleci in spusti).

Pod naslovom "Player movement properties" deklariramo več spremenljivk številskega tipa. V komentarjih je pojasnjena njihova vloga.

V vrstici "private Quaternion desiredRot = Quaternion.identity;" deklariramo spremenljivko

"desiredRot" tipa Quaternion. Spremenljivki za začetno vrednost dodelimo identiteto kvaterniona.

Spremenljivko bomo uporabili za beleženje želene rotacije igralca, nato pa bomo igralca postopoma zavrteli v smer, ki bo shranjena v spremenljivki "desiredRot".

Pod naslovom "Player movement bool" deklariramo več boolean spremenljivk, uporabili pa jih bomo za preverjanje stanja gibanja igralca (ali stoji na tleh, hodi, skače ipd.). Spremenljivke bomo uporabili tudi pri vklapljanju animacij.

Pod naslovom "Keyboard inputs" definiramo še spremenljivki, v kateri bomo beležili vrednosti virtualnih osi (vodoravne in navpične).

4.4.3 Metode v skripti PlayerMovement.cs

V skripti uporabimo več metod, s katerimi želimo program skrajšati in ga narediti bolj berljivega.

Slika 22 prikazuje metodi, ki jih uporabimo za premik glavnega lika.

void MovePlayer(float speed) {

transform.Translate(horAxis * LRspeed * Time.deltaTime, 0, vertAxis * speed * Time.deltaTime);

}

void MovePlayerLR() {

transform.Translate(horAxis * LRspeed * Time.deltaTime, 0, 0);

}

Slika 22: Metodi MovePlayer() in MovePlayerLR().

Prva metoda imenovana "MovePlayer(float speed)" sprejme vhodni podatek "speed". Vstavljeni parameter mora imeti podatkovni tip plavajoča vejica, uporabimo pa ga za določitev hitrosti premikanja v navpični smeri. V telesu metode spremenimo pozicijo objekta, na katerem je skripta pripeta. To dosežemo s klicem metode "Translate", v katero vstavimo shranjene vrednosti virtualnih osi ("vertAxis", "horAxis"). Vrednosti pomnožimo s hitrostjo premikanja ter s Time.deltaTime (zaradi razlik v številu izrisanih sličic na sekundo).

Druga metoda imenovana "MovePlayerLR" nima argumentov, deluje pa podobno kot

"MovePlayer". Za razliko od prve, "MovePlayerLR" igralca premakne le levo ali desno, definirali pa smo jo za primere, ko igralec pritisne tipko za pomik v levo ali desno, brez tipke naprej/nazaj.

(34)

25

Slika 23 prikazuje metodo, ki jo uporabimo za postopno vrtenje igralca v smer pogleda kamere.

void RotateInCamDir() {

desiredRot = Quaternion.Euler(

transform.eulerAngles.x, mainCam.eulerAngles.y, transform.eulerAngles.z);

transform.rotation = Quaternion.RotateTowards(

transform.rotation, desiredRot, rotSpeed * Time.deltaTime);

}

Slika 23: Metoda RotateInCamDir().

V telesu metode "RotateInCamDir()" v spremenljivko "desiredRot" shranimo kvaternion izražen z Eulerjevimi koti (preko metode Euler). Kvaternion oblikujemo tako, da Eulerjevi komponenti "x"

in "z" ohranita rotacijo objekta, na katerega je skripta pripeta, za komponento "y" pa vzamemo vrednost "Y" rotacije kamere. Po pripravi kvaterniona "desiredRot", igralca zavrtimo v shranjeno smer. To izvedemo preko metode "RotateTowards", ki sprejme trenutno vrednost rotacije (za koliko je zarotiran objekt s pripeto skripto), ciljno vrednost po rotaciji ("desiredRot") ter hitrost rotacije. Hitrost je določena v stopinjah in jo reguliramo s spremenljivko "rotSpeed", pomnožimo pa jo še s Time.deltaTime, zaradi razlik v številu sličic na sekundo.

Slika 24 prikazuje metodo "PlayerJump()", ki jo pokličemo ob zaznanem pritisku tipke presledek.

void PlayerJump() {

rigbody.AddForce(Vector3.up * jumpForce * Time.deltaTime, ForceMode.Impulse);

}

Slika 24: Metoda PlayerJump().

V telesu metode na komponento togega telesa (referenca do komponente je shranjena v spremenljivki "rigbody") vplivamo s silo v smeri Y. Zapis Vector3.up pomeni enako kot, če bi zapisali Vector3 z vrednostjo (0,1,0). Vektor pomnožimo z vrednostjo "jumpForce" in Time.deltaTime (zopet zaradi izrisanih sličic na sekundo). Način vplivanja sile nastavimo na impulz, s tem pa dosežemo, da se sila doda sunkovito in nemudoma.

Reference

POVEZANI DOKUMENTI

Slika 4: Prikaz območja Ledavskega jezera s primerjavo stanja parcele jezera v zemljiškem katastru (svetlo modra meja) in vodne površine jezera po podatkih ARSO (temno modra

Slika 2: Povprečna masa glav solate gojene v akvaponiki in v zemeljskem substratu 17 Slika 3: Povprečna masa rozet endivije gojene v akvaponiki in v zemeljskem substratu 18 Slika

Slika 19: Prikaz števila lastnosti, po razredih, glede na smer spremembe v primerjavi s kravo Branko, po bikih, v primeru ekonomske situacije.. prireja mleka 27 Slika 20:

Ezért olyan fontos, hogy elegendő rostokban gazdag élelmiszert és folyadékot fogyasszon, valamint hogy eleget mozogjon. Rostokban gazdagok a zöldségek, gyümölcsök,

Tako smo na primer lahko telesno dejavni doma: doma lahko delamo vaje za moč, vaje za gibljivost in vaje za ravnotežje, hodimo po stopnicah, uporabimo sobno kolo. Ne pozabimo, da

Valvasorjevo risbo z drugačno postavitvijo kamere 43 Slika 53: Perspektiva 3D rekonstrukcije ortofoto načrta izseka Lože 44 Slika 54: Fotografija širšega območja izseka Lože

Slika 15 Grafični prikaz rezultatov za kalcij Prav tako program omogoča prenos grafov, ki jih izrišejo aparati v laboratoriju. Slika 16 Grafični prikaz

Slika 22 prikazuje okno za prikaz aktivnih meritev za več bolnikov hkrati in je podobno oknu za meritev posameznega bolnika.