NaGs - Not.a.("Game studie")

Gruppemedlemmer & studienr: Vejleder: Ebbe Vang [[email protected]] ​ Benjamin Jul Westermann [60653] Uddannelsessted: RUC - Roskilde Universitet ​ Niels Georg Vendelø [61218] Periode: 03-09-2018 til 18-12-2018 ​ Lasse Bjerg Christiansen [60632] Mads Zeuch Ethelberg [62608] Antal tegn: 69651 + 10 billeder ​ Normalsider: 29 ​ Sprog: Dansk ​

Github repository: https://github.com/westermann920/TowerDefence.git ​ ​

Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Abstract

The goal of this paper is to detail how to conceptualized and create a videogame playable in a virtual reality setting. We’ve constructed the game with the help of the and written the code in the C# language. The game follows the classical genre, which we have attempted to restructure so it would fit into a new setting and perspective. The paper details our early thoughts on the different elements of the game, as well as our experiences as first timers working with the HTC vive, the unity engine and construction of a group IT projekt using the github desktop app to share our code and our work. The conclusion to this report is that when working on a game, as well as any other IT project, one must consider the possibility that sudden delays can occur. Be they hardware problems that require IT support, mind boggling software problems or simply a lack of planning. In the discussion we elaborate on how we failed to prepare adequately for the design hurdles and data structures, which eventually lead to the final product not being as sound as we would have liked it to be. The lesson from this paper is that planning is a necessity and delays are inevitability in game design, and the proper way of dealing with these are clear and concise plans created through discussions and preparations.

Forord

Dette er vores 5. semesterprojekt på bachelorniveau inden for faget computer science / datalogi på Roskilde Universitet (RUC). Formålet med dette projekt er at erhverve viden, færdigheder og kompetencer inden for softwareudvikling. Herunder viden og færdigheder om programmering, algoritmer, datastrukturer og testing. Kompetencer indenfor planlægning, kravspecificering og styre udvikling af mindre udviklingsprocesser. Samtidig med at de blødere færdigheder også videreudvikles såsom organisering, projektstyring og kommunikation i forhold til et projektarbejde. Dette projekt er primært målrettet andre bachelor studerende inden for de datalogiske fag, som er i slutningen af deres studie, og som ønsker viden inden for udvikling af virtual reality spil. I vores projekt har vi valgt at fordybe os i udviklingen af et virtual reality spil hvor unity er benyttet som engine. Vi vil gerne takke vores vejleder Ebbe Vang, som har giver os ideer, inspiration og vejledning til arbejdet med dette projekt.

Side 2 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Indholdsfortegnelse

Abstract 2 Forord 2 Indholdsfortegnelse 3

Indledning 6 Problemformulering 6 Teori 7 VR(Vive) 7 Git og Github 10 Unity 11

Game Design 13 Tårne 13 Bygmesteren 15 Fjender 15 Overlorden og hans minions 17 Pathing 17 Planlægning 19

Abejdsprocess 21 Implementering af scripts i unity 21 Prefabs 23 Minions prefab 23 Overlord prefab 24 Spawner prefab 25 Towers prefab 25 Projectiles prefab 25 Building obj prefabs 25 Scripts 26 AnimationEvents.cs 26 BaseSpecs.cs 26 ControllerFlameThrower.cs 27 Health.cs 27 Intel.cs 29 MinionWaves.cs 30 Movement.cs 31 Overlord.cs 33 PathPointList.cs 37

Side 3 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

PathFinding.cs 39 Projectile.cs 40 Spawner.cs 41 StatsUpgrade.cs 42 Targeting.cs 44 Tower.cs 45 Scripts for Tower Building 46 TowerCreation.cs 46 TowerTypeUpgrade.cs 46 Upgrade.cs 47 Scripts for Hand Menu 47 BuildingMenu.cs 48 ButtonHandler.cs 49

Diskussion 51

Konklusion 52

Reference list 53

Bilag 54 Bilag 1 : Unity Interface 54 Bilag 2: Unity Prefabs eksempel 55 Bilag: Scripts 56 Bilag: AnimationEvents.cs 56 Bilag: BaseSpecs.cs 57 Bilag: ControllerFlameThrower.cs 58 Bilag: Health.cs 59 Bilag: Intel.cs 61 Bilag: MinionWaves.cs 63 Bilag: Movement.cs 65 Bilag: Overlord.cs 67 Bilag: PathFinding.cs 73 Bilag: PathPointList.cs 75 Bilag: Projectile.cs 77 Bilag: Spawner.cs 79 Bilag: StatsUpgrade.cs 81 Bilag: Targeting.cs 83 Bilag: Tower.cs 84 Bilag: Hand Menu/BuildingMenu.cs 87 Bilag: Hand Menu/ButtonHandler.cs - mangler script 91

Side 4 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Tower Building/TowerCreation.cs - mangler script 92 Bilag: Tower Building/TowerTypeUpgrade.cs - mangler script 93 Bilag: Tower Building/Upgrade.cs - mangler script 95

Side 5 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Indledning

Computerspil er en interesse der i takt med en stigende digitalisering af vores samfund har fundet vej til et hidtil uset antal brugere. Forøget interesse betyder et større og bredere markede.

Et bredere marked har betydet at der har været mulighed for at eksperimentere med adskillige nicher, i en hvis forstand “See what sticks”, en søgen efter en spiloplevelse som kunne starte en hel ny måde at interagere med computerspil

Virtual reality er et af de nicher som gennem computerspils historie har set adskillige iterationer( såsom nintendo virtual boy tilbage i 1995(1) , på trods af flere forsøg så har der ikke været den ​ ​ store efterspørgsel inden for virtual reality oplevelsen, det vil sige indtil nyere tid hvor virtual reality har haft en genopstandelse blandt andet gennem det “crowdfundede” kickstarter projekt iværksat af Oculus VR (2), som producerede VR headsettet Oculus rift. ​ ​

Både Oculus rift og dens konkurrent HTC Vive var fra starten af deres udgivelse for dyre til at kunne appellere til gennemsnitsforbrugeren af computerspil, og var stort set udelukkende et eje for spil entusiaster.

Men efterhånden er priserne på de virtuelle headset sunket til et niveau som flere kan være med på, dog mest i tilfældet af oculus rift, der har set drastiske prisfald inden for de seneste år. ( pris ved udgivelse 2016, 599 dollars (3), nuværende 2018, 452 dollars (4). ​ ​ ​ ​

Grundet vores egen fascination af konceptet virtual reality, og et kommende større og bedre understøttet mainstream marked, besluttede vi os selv for at undersøge hvordan processen af at skabe et VR spil forløber sig.

Problemformulering

I dette projekt arbejdes der med spørgsmålet:

Hvordan udvikles et spil som kan spillets mod en computer med virtual reality som brugerflade. -Hvad er det vi vil lave i dette projekt / hvad er formålet -Hvorledes skaber man(find synonym) et spil i virtual reality?

Side 6 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Teori

Da kursen var lagt mod et produkt i virtual reality stod næste opgave på at definere hvilken type spil vi skulle give os i kast med. Den første overvejelse hældede til at det kunne være et skydespil hvori spilleren selv løber rundt og skyder fjender, men med forhåndsviden om at bevægelighed ikke er fantastisk når man har et headset der blokere alt syn og en kort ledning i baghovedet, blev den ide droppet, og i stedet satte vi os for at lave et Tower Defense spil. Med genren valgt gav det os en ramme for hvorledes spillet skulle opbygges, og dermed også en række elementer vi var nødt til at have på plads før vi kunne tilføje vores egne variationer til genren. Et tower defense spil er betegnelsen for en spilgenre, hvor i det er spillerens opgave at beskytte en base mod et stigende antal fjender, der bliver sendt for at angribe spillerens base på et tidsinterval og som bliver stærkere og stærkere for hver runde de bliver sendt afsted. Spillerens primære form for forsvar er de tårne spilleren bygger og opgraderer igennem spillet. (deraf Tower Defense) I takt med at fjenderne bliver stærkere må forsvarsværkerne naturligvis følge trop. De taktiske elementer i spillet ,der sørger for at det ikke bliver helt trivielt, opstår igennem 3 forskellige aspekter. For det første er der typisk flere forskellige typer af fjender der skal bekæmpes af forskellige tårne. idet der kommer forskellige typer fjender der skal bekæmpes af forskellige tårne For det andet er der typisk en økonomi til stede der sørger for at spilleren ikke bare kan bygge af hjertens lyst,men at der faktisk skal tænkes over hvilke tårne der bliver købt og hvornår, og for det tredje følger fjenderne typisk en specifik sti, hvilket tvinger spilleren til at overveje hvor de forskellige tårne bedst placeres for at udnytte deres individuelle styrker, for dermed at forsvare basen bedst muligt.

Ud af ovenstående kan vi konkludere at vores spil som minimum måtte indeholde tårne, fjender og en base. Det næste spørgsmål blev derefter, hvordan skal vi bygge en virtuel verden op i 3d og dernæst få den smidt ind i et på briller?

VR(Vive)

Vores tower defense spil bliver lavet i virtual reality. Virtual reality (VR), er en teknologi som har haft en voksende tilgængelig for gennemsnitsforbrugeren gennem de seneste 10 år. De fleste VR kits består af et par virtual reality goggles, en form for controller(e) og nogle stationære sensorer til at skabe et virtuelt rum hvori spilleren kan bevæge sig frit og interagere med den virtuelle verden som ses gennem Brillerne (goggles). Vi bruger HTC Vive, en af de bedre konfigurationer, til at udvikle vores spil. Vive er lavet gennem et samarbejde blandt HTC og Valve. Valve har udviklet en API kaldet VR. Denne api er gjort tilgængelig i unity ved at Valve har lavet et plug-in til unity som implementere support for SteamVR. (5). Umiddelbart er ​ ​

Side 7 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg vores spilgenre ikke den mest konventionelle at udvikle i VR, idet tower defense spil oftest bliver spillet igennem et fugleperspektiv fordi det giver hurtig adgang til at få overblik over hele spillets scene. Det der adskiller vores tower defense fra andre, er det faktum at spilleren ikke nødvendigvis befinder sig over landskabet, men også kan bevæge sig frit rundt iblandt mobs og tårne.

Det er ikke alle der har oplevet VR, men det er bestemt noget man skal prøve selv for at forstå hvordan brugeroplevelsen er.

Selve HTC Vive setuppet består af 2 sensorer, som man typisk monterer fast på en væg, eller som i vores tilfælde, har siddende på 2 stativer, udover sensorerne er der selve vive headsettet, hvilket er udstyret med adskillige sensorer der sørger for at spillerens placering og orientering konstant er korrekt udregnet. Konfigurationen har en hub som man tilslutter direkte i den computer som skal køre htc vive systemet, denne hub er hvad der forbinder alt der skal bruges.

Her er et billede af den hardware som følger med i en standard HTC vive pakke, der findes nuværende også en anden nyere version af HTC vive, men oplevelsen er nogenlunde den samme. De to små sorte bokse kaldet “base station” er sensorer der optegner det 3D rum som man kan bevæge sig frit rundt i, Vive headsettet er således også udstyret med adskillige input sensorer som både snakker sammen med de to base stations, men som også måler headsettets orientation.

De to tilhørende controllere er også udstyret med adskillige måleenheder og er på samme måde som headsettet stort set er virkelighedstro i dets tracking. Når man opsætter det virtuelle 3d

Side 8 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg miljø man kan bevæge sig frit bruges controllerne også til at optegne den “streg” der markerer omkredsen af rummet.

Her et billede af gruppemedlem Georg der er midt i en test af vores tower defense spil. Hvis man kigger til højre kan det ses at outputtet som der går til brillerne som Georg har på hovedet også vises på computerskærmen konverteret til en normal skærm i realtime. Selve brillerne er udstyret med 2 skærme af panel typen OLED, hver med en opløsning af 1080×1200, hvilket sammenlagt giver en opløsning på 2160×1200. Det der er specielt ved paneltypen OLED, er at hver eneste pixel lyser sig selv op, hvilket er anderledes fra de mere konventionelle led/lcd paneler som får oplyst alle pixlerne samlet af et panel bag selve pixlerne. Dette resulterer i at OLED kan vise en langt højere kontrast, og også at når der vises sort på skærmen, så er det så sort som det muligt kan blive da de pixler der skal outputte sort praktisk talt slukker. En anden vigtig faktor er den mængde af frames der vises i sekundet. Vive viser 90 fps, hvilket er 30 fps mere end en gennemsnits pc monitor, dette er for at gøre orientation mere virkelighedstro, og det mindsker også samtidigt den mængde delay der er mellem skærm og input, dette er vigtigt da selv mindre fejl kan resultere i en ubehagelig oplevelse, dette fordybes i vores “Fejl og problemer” afsnit.

Side 9 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Her et billede af et af de to stativer vi har vores base station monteret fast på, vores “studie” har omtrent et virtuelt 3d. miljø på 2x2meter, dette kan dog variere en smule for hver gang der kalibreres. Grunden til at arealet på det virtuelle 3d. Miljø kan variere skyldes at den måde man kalibrerer på gøres manuelt med controllerne i hånden..

Git og Github

Git er et open source værktøj der har til formål at hjælpe udviklere med at få overblik over de forskellige versioner af deres projekter. En af de styrker Git har i forhold til andre værktøjer, er Gits tilgang til hvordan forskellige versioner af kode bliver sammenskrevet. Her er tilgangen at det skal være let og skal kunne ske ofte. Dette har medført at arbejdsgangen er, at man opretter en forgrening (kaldes en branch i git) hvor man kan ændre i koden uden at påvirke andre. De ændringer der laves kan så gemmens til den pågældende gren med en title of overskrift på ændringerne (kaldes commit i git). Når det er tid til at opdatere master grenen med de ændringere der er lavet i forskellige forgreninger, kan koden automatisk sammenskrives af git. Dette kaldes at merge træerne. Hvis der er to personer der har ændret det samme stykke kode, vil Git vise dette som en konflikt hvor begge versioner af koden vil blive præsenteret. For at løse konflikten kan den version af koden der menes at være korrekt, vælges som der så vil indgå i sammenfletningen. (6,7) ​

Github er en implementering af Git som en form for socialt netværk og cloud løsning. Cloud løsningen kommer til udtryk ved at koden der skal versions styres går gennem Githubs server ved hjælp af Git. Alle der arbejder med det pågældende projekt vil så kunne uploade og downloade til dette centrale punkt. Det sociale netværk ses ved at Github har mange

Side 10 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg muligheder for at samarbejde på det pågældende projekt. Her kan blandt andet indstilles hvem der må se projektet, hvem der kan tilføje ændringer og hvem der kan samle forskellige versioner. (7,8) ​

I forhold til dette projekt har github være meget centralt, hvor det er blevet benyttet til at flette særskilte kode-versioner sammen, til at lave backups af projektet ved større ændringer og mindre ændringer. Ved mindre ændringer har det været muligt at vende tilbage til en tidligere version af projektet ved at fortryde de commits der har været grunden til at en tidligere version har været ønsket. Ved større ændringer, som ved sletning af ubrugte scripts der var omskrevet, er der blevet taget en backup ved at lave en ny branch som ville forblive på den pågældende version. Samarbejdet over github var struktureret ved at hver havde sin egen personlige branch hvor man kunne lave de ændringer, tilføjelser og sletninger man ville. Når alle mødtes blev de personlige branches flettet sammen i master branchen. Dette foregik ved at en branch blev flettet sammen med masteren, hvorefter den næste branch skulle opdateres til den nye master branch. Ved konflikter mellem den nye master branch og den næste branch der skulle flettes ind i masteren branchen, skulle disse konflikter håndteres før processen kunne gentages. Dette blev often håndteret af en enkelt person, da gruppen erfarede at processen hurtigt blev problematisk hvis flere prøvede at flette de forskellige branches på samme tid. Nedenfor ses et billede af nogle af de branches der er blevet benyttet for at koordinere de forskellige kode versioner.

Unity

Begrebs Afklaring: Grundlæggende for “scripts”(det tekstdokument ens kode bliver skrevet i) i unity er der to hovedfunktioner “Start()” som bliver kaldt en gang når gameobject som holder den bliver instantiated og “Update()” som bliver kaldt hver eneste frame i spillet. Et “GameObject” er et objekt der befinder sig i spillets 3D miljø(bedre kendt som en “scene”), dette objekt bliver brugt til at indeholde diverse komponenter såsom et mesh(det der gør at

Side 11 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg spilleren kan se objektet), en collider(det der sørger for at objektet kan registrere om den kolliderer med andre gameobject) eller et script(det er her vi kan skrive vores kode og giver objektet funktionalitet) udover dette kan vi også i unity gemme et såkaldt “GameObject” med alle dens komponenter, dette kaldes et “Prefab”. Det sidste begreb er “Instantiated” hvilket vil sige at oprette/skabe et gameobject i scenen efter at spillet er påbegyndt.

Eftersom vi vidste at vi i forvejen havde en stor opgave foran os, ville vi gerne undgå at skulle sætte os ind i en måde at lave 3D figurer for slet ikke at nævne hele baggrunden til hvordan de så skulle loades ind i en virtuel reality setting. Derfor havde vi brug for en game engine og vores valg faldt på Unity. En engine er et software development environment hvor i der er produceret en række forskellige ​ ​ funktioner, der tillader brugeren at trække forskellige figurer og adfærds skemaer ind på en spilleplade(se bilag 1). Det betyder at en masse kode, der normalt skulle udvikles fra bunden i forbindelse med et 3D spil, såsom visuelle effekter og spillerens bevægelighed, allerede er skrevet for os. Derfor vil der i projektet være en del elementer vi ikke er direkte ophavsmænd til, og det vi reelt set afleverer er de specielt udviklede adfærds scripts vi har skabt, for at få de forskellige elementer af spillet til at tage form som vi ønsker. Unity scripts skrives i et programmeringssprog kaldt “C#”. Det er dog også muligt at skrive i unity med Java, men den mulighed så vi ikke nogen fordel ved. (9) ​ Unity er dog ikke bare et redskab der fuldstændig tillader brugeren at fralægge sig al evne til at programmere og oprette spil. Ikke uden at lave den fornødne opsætning. Sæt nu at brugeren skaber en figur og giver den et eller flere scripts for at rette dens adfærd, hvis der så er brug for flere af denne type figur med samme adfærd ville det være et forfærdeligt rod at skabe bare ti kopier, og hvis der, som i vores tilfælde, skal skabes flere og flere over en længere tidshorisont ville det være en umulig opgave. Unity har funktioner der gør denne opgave mulig at løse, en sådan funktion er eksempelvis prefabs. ​

Prefabs tillader brugeren at skabe pre-fabrikater af de objekter der skal bruges gentagne gange. De indeholde proportioner, adfærds scripts, tyngdekraft, kollisions tjek mm. Disse kan så både kaldes i de scripts der skrives til andre objekter og instantieres igennem dem. (se bilag 2 -3) Vi vil senere beskrive præcise hvilke prefabs vi har skabt og hvad de holder. Ud over prefabs har unity også en anden brugbar feature til når koden skal testes. Det er muligt at stoppe den aktuelle kode der bliver kørt i unity, ændre variablerne på et givent objekt og dernæst fortsætte koden, uden at compile endnu en gang. Det er med andre ord muligt at få testet flere iterationer igennem på relativt kort tid, i modsætning til hvis koden skulle compiles. Brugen af en 3D scene gør det derudover også muligt at have flere test scenarier kørende samtidig.

Inden vi begiver os i kast med de næste midler vi brugte til informationsdeling, vil vi kort nævne eksistensen af en anden game Engine: Unreal Engine. Vi har ikke sat os ind i forskellen på de to engines. Grunden dertil er, at valget på unity hurtigt blev vedtaget, fordi vi vidste der var

Side 12 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg hjælp at hente hos studerende og undervisere, der har brugt og stadig bruger programmet til deres egne projekter.

Game Design

I dette afsnit beskrives vores overvejelser for spillet. Hvordan vi lavede iterationer på de allerede etablerede koncepter i tower defense, og hvad vi håbede på at opnå med disse.

Tårne

Dette afsnit omhandler Tårne. Tårne er de forsvarsværker spilleren bygger. Dette afsnit beskriver hvordan man bekæmper fjenderne der bliver sendt imod spilleren, hvordan de bygges og hvordan de bruges til at opfordre til strategiske beslutninger.

Med redskaberne og den basale formel for et tower defense spil på plads gik vi igang med at brainstorme hvorledes vi ville differentiere vores produkt fra de iterationer der allerede eksisterer på tower defense konceptet. Det første vi kiggede på var grundelementerne af genren og hvordan de er udført i andre spil, dernæst hvorledes vi syntes de fungerede i gameplay forstand og hvad vi kunne overføre til vores spil, med det i mente at vores spil kommer til at foregå i en virtual reality verden og ikke bare på en computer skærm. De spil vi har hentet inspiration fra, er: Bloons Tower Defense (10), Onslaught Tower defense ​ ​ (11), Warcraft 3: The Frozen Throne Wintermaul (12) og In your face Tower Defense (13). ​ ​ ​ ​ ​

Fordi spillet foregår i en virtuel verden er det første spørgsmål enhver udvikler stiller sig selv altid: hvordan bevæger spilleren sig rundt? Opsætningen af HTC viven giver naturligt et område mellem de to sensorere hvori det er muligt rent fysisk at gå rundt, men hvis spilleren skal bevæge sig store distancer er det ikke længere en mulighed. Heldigvis for os har Valve Corporations, der også har udviklet HTC viven, løst dette ved at lave et frit tilgængeligt script der tillader spilleren at teleportere rundt på banen på. Dette har vi også valgt at gøre brug af.

Side 13 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

På billedet ses en controller, der bliver holdt op af spilleren , det grønne område indikerer hvortil spilleren ved tryk vil blive teleporteret til.

Fælles for alle disse spil er at de har forskellige typer af tårne med unikke egenskaber til at bekæmpe fjenderne. Nogle tårne skyder med missiler, andre med bomber, nogen tillader spilleren manuel kontrol over dem. Nogle skyder langsomt, andre skyder hurtigt, nogle kan skyde over lange distancer andre kan kun skyde når fjenden er helt tæt på. Dette sørger ikke blot for at der er variation i hvorledes spillet hver gang det spilles, det åbner også muligheden for at lave fjender der skal bekæmpes forskelligt. Et godt eksempel er Bloons TDs jern balloner, der skal bekæmpes med kanoner, fordi dartpile naturligvis ikke har den store effekt på en kugle af jern. I Warcraft 3 fungerer dette på en lidt anderledes måde, fordi spillet har en enormt stor variation i de tårne og modstandere der kan komme, har de beslutet at fjenderne skal have en bestemt rustnings type og alt efter hvilken rustning de har på tager de procentvis mere eller mindre skade fra bestemte typer tårne. Det betyder, at i modsætning til bloons tower defense hvori dartpile slet ingen effekt har på jern, så vil et scenarie Warcraft være, at hvis en fjende har 10 liv og er lavet af ild, vil et tårn der skyder med vand, der normalt ville skade 2, blive stærkere mod den fjende og give 200% i skade således at fjenden ville ende på 6 liv (10 -(2*2) efter at være blevet ramt, og hvis den skød en fjende lavet af jord ville den skade 75% så fjenden ville være ende på 8.5 liv (10-(2*0,75)) af sin originale skade. Hvis der skulle slukkes en ildebrand ville det at bruge vand være første valg, hvis der skulle stoppes et jordskred ville vand ikke være det bedste redskab, men det ville have en effekt, om end meget lille.

Vi synes ideen om forskellige typer af rustning og tårne bedst skaber interaktion mellem de to spil-elementer fordi de ikke blot tvinger spilleren til at tænke over placering af tårne, men også belønner strategi og kompositionen af de tårne spilleren så vælger at bygge. På denne måde opfordres der til variation og nytænkning, hvilket er essentielt for at gøre spillet interessant at komme tilbage til.

Side 14 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Billedet viser de forskellige tårne, grå er det helt basale tårn, rød er opgraderet til ild, blå til vand og det gule til lyn.

Bygmesteren

Dette afsnit omhandler placeringen / konstruktionen af tårne

Beslutningen om hvorledes vi ønskede tårne og fjenders interaktion ledte naturligt til det næste skridt i spillets udvikling: hvordan skal tårnene placeres? Modellen for placering af tårne har altid været den samme i tower defense spil. Fordi pladen ses oppefra har det været naturligt at lave en menu over de forskellige tårne, trække tårnene ud fra den menu og placere dem på spillepladen, akkurat som et brætspil, men vores spil foregår i en virtuel verden, hvor perspektivet er frø modsat fugl. Det gav os to problemstillinger: hvordan skal vi placere tårnene? Og hvordan skal vi præsentere spilleren for de forskellige tårne? Her var en reel mulighed for at anvende den virtuelle verden til interaktioner mellem spilleren og spillet. VI beslutede derfor at vi ville lade spilleren “bygge” tårnene i stedet for bare at trække dem ud fra en menu. I stil med at bygge lego, ville spilleren skulle trække forskellige objekter hen til den ønskede lokation for tårnet, placere forskellige byggeklodser i en bestemt rækkefølge for på den måde at skabe det ønskede tårn. Fordi det ikke skulle være for kompliceret fordi det skal kunne gøres hurtigt og under pres, blev vi enige om at der ikke skulle bruges mere end højst tre forskellige objekter til at bygge et tårn. Et fundament, en cylinder og til sidst et element, for at indikere hvilken type tårn der var tale om, en spand vand for et vandtårn, en fakkel for et ild tårn, en sten for et jord tårn osv. Dernæst skulle vi bruge en måde at præsentere de forskellige muligheder for spilleren på. Vi havde diskussioner frem og tilbage indtil vi faldt over In your face tower defense. In your face TD har en menu der kan åbnes ved et klik på controlleren, og elementerne kan så vælges ved at føre den anden controller hen over menuen og dernæst kan det element så placeres et sted i den virtuelle verden (13)(tidspunkt: 1:26 i videoen). ​ ​

Side 15 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Tilbage var kun at finde ud af hvordan vi skulle placere elementerne på banen når de først var valgt. Det var her, selv før vi faldt over In your face TD, logisk for os at bruge den samme teknik der bruges til at teleportere rundt på banen på til at placere tårne.

Som opsummering ville vi arbejde imod at skabe forskellige tårne og en måde at placerer dem på der gør brug af den virtuelle verden omkring spilleren. Vi gik dernæst videre til fjenderne.

Fjender

Dette afsnit handler om fjenderne, deres rolle i spillet og deres opførsel. Vores fjender er i spillet repræsenteret med 3 forskellige modeller, en ridder, en magiker og en bueskytte.

På billedet ses de tre typer af fjender der er synlige i vores spil

Traditionelt set har fjenderne i tower defense været temmelig forudsigelige. Deres rolle er at give spilleren modstand. Den modstand består som regel i at flytte sig fra et givent startpunkt imod spillerens base, og når de når dertil skal de gøre skade på basen og selvdestruere. De følger som regel en planlagt sti, så det er muligt at følge med i hvor tæt de er på målet og hvor tårnene har bedst mulighed for at gøre skade på fjenderne. Som nævnt i afsnittet om tårne har hvert spil deres variationer af forskellige fjender der opfordrer brugen af forskellige tårne, men deres modstand er sjældent manifesteret på andre måder end bevægelse og rustning. Vi synes ikke dette var helt nok og overvejede derfor alternativer. Ved hjælp af vores vejleder blev vi sporet ind på ideen om at gøre vores fjender “intelligente”. Det vil sige at de i stedet for at følge en i forvejen dikteret rute mod basen, ville de nu have mulighed for selv at vælge imellem en række af forskellige stier, baseret på hvor farlige de forskellige ruter blev i takt med spillets

Side 16 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg gang. Det ledte til ideen om at fjenderne ikke bare skulle reagere på information, de skulle også aktivt indsamle den, og det skulle være muligt for spilleren at bekæmpe fjendernes informations operation. Vi endte med at beslutte at der var to måder at opnå informationsindsamling og give spilleren et muligt modtræk på. Den ene var at give spilleren et våben og skyde de informanter der blev sendt ud, den anden ville være, at lade informanterne vende om og løbe tilbage igen, for at rapportere hvilke tårne de havde set og hvor mange, på den vej de var løbet. Det var langt ind i processen intentionen at udføre begge muligheder, men tiden løb fra os og vi valgte derfor at undlade våben til spilleren. Igen ledte dette videre til endnu et dilemma der denne gang var tematisk i natur, hvis fjenderne vender om for at rapportere deres observationer, hvem rapporterer de så til?

Overlorden og hans minions

Dette afsnit introducere Overlorden.

Overlorden er den usynlige fjende spilleren kæmper imod. Han er de mindre fjenders mester som holder styr på de informationer der bliver bragt tilbage og det er også ham der bestemmer hvilken type af fjender der sendes afsted, baseret på den indsamlede information. Vi synes det gav spillet en smule mere dramatik at spilleren ikke bare spiller mod computeren, men snarere mod en faktisk modstander med intentioner og motiver og mulighed for at slå tilbage mod spilleren. Af denne årsag navngav vi også fjenden Overlorden og de riddere / bueskytter / magikere spilleren kunne se og skyde, blev kaldt minions. De informationer Overlorden var nødt til at have for at kunne give spilleren modstand var som følger: hvilken vej løb den her minion og hvilke slags tårne så den her minion og hvor mange? På den måde ville det blive muligt at sende minions der var stærke imod ild ned af en sti der kun indeholdte tårne der var svage over for ild, for på den måde at give minions den bedst mulige chance for at nå ind i basen og gøre skade på spilleren. Målet her var at sørge for at spilleren ikke bare kunne ramme den perfekte kombination af forskellige tårne så spillet endte et sted hvor det eneste strategi der var tilbage ville være at opgradere tårnene. I stedet skulle spilleren aktivt evaluere hvor stor en variation der var på hvert stykke af stien.

Vi havde brug for en usynlig overlord der i baggrunden kommanderede de forskellige typer af minions ned af de forskellige stier hvor de kunne gøre mest skade og forskellige typer af minions. Det næste skridt var at definere de forskellige stier.

Pathing

I dette afsnit beskriver vi vores overvejelser om pathing.

Pathing refererer til hvordan Overlordens minions skal bevæge sig mod spillerens base. Som bekendt er vej/sti på engelsk oversat til Path, deraf udtrykket pathing.

Side 17 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Når der kigges på andre tower defense spil er en af de slående ligheder at minions altid følger en forudbestemt rute som er synlig for spilleren hele spillet igennem. Før vi havde besluttet os for at der skulle være en overlord som kunne operere imod spilleren var ideen at siden vi nu var i en virtuel verden ville vi lade fjenderne starte fra alle mulige vinkler, 360 grader rundt om spillerens base. Det gik dog hurtigt op for os at det ville være en dårlig oplevelse for spilleren, fordi de første mange runder af spillet ikke ville involvere anden strategi end at bygge tårne helt tæt på basen. Vi synes omvendt heller ikke det var en særlig god oplevelse bare at have en forudbestemt rute, hvori fjenderne udelukkende bevæger sig fra punkt A mod punkt B. Vi havde en kæmpe verden, det ville være en skam ikke at udnytte den. Derfra opstod ideen om Overlorden, som kunne styre hvordan minions skulle bevæge sig, banen udviklede sig til at der stadig skulle være forudbestemte stier, men at der skulle være flere af dem. Billedet nedenfor viser de forskellige ideer vi havde for stier, billede 3 er den idé der endte med at være i det endelige produkt.

Billede 1 viser den første idé, fjender fra alle sider. Billede 2 viser fjender der kommer fra 1 bestemt sti, og billede 3 viser minions der kommer fra flere stier og kan skifte bane undervejs ved de blå punkter.

Med pathing på plads havde vi grund nu formularen til vores spil klar. Spilleren skulle forsvarer sin base ved hjælp af Forskellige tårne som spilleren kan bygge manuelt i stil med legoklodser. Forskellige typer fjender der kommanderes af en usynlig overlord, der igennem spillet bliver klogere og kan udføre modtræk, ved at sende fjenderne ned af forskellige stier.

Næste skridt var dermed at lægge en tidsplan for projektet.

Side 18 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Side 19 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Planlægning

I begyndelsen af projektet var planlægningen af arbejdet meget løst. Primært var opgaverne at lære unity at kende og udføre nogle tutorials for bedre at kunne arbejde med programmet. Som projektet skred frem skulle tempoet hæves og arbejdet struktureres. Strukturen blev indført ved hjælp af en tidsplan med nogle mål der skulle nås til bestemte datoer.

D. 31 oktober 2018 Få lavet en funktion der giver minions mulighed for at finde vej på banen fra spawn til basen

Få lavet en funktion der kan lave en gruppe af n antal minions der bliver indført i spillet ​ ​ med en lille tidsforskydning mellem hinanden.

D. 7 november 2018 Få lavet alle påkrævede elementer for at det er muligt at bygge tårne i spillet.

D. 14 november 2018 Få beskrevet en konkret ide på hvordan minions smartere kan finde vej fra spawn til basen

D. 21 november 2018 En prototype på implementering af smartere vej-finding skal være klar

D. 29 november 2018 Et system til at give minions dækning hvis et tårn ikke kunne se minionen men var inden for skydeafstand.

Våben til spilleren så spilleren kan indgå mere aktivt i spillet.

December Prototypen på smartere vej-finding skal være færdig.

Implementeret ekstra ting.

Disse målsætninger blev indfriet for svingende succes. Nogle blev indfriet og ændret efterfølgende, nogle blev ikke nået hvor andre helt blev skippet til fordel for andre ting. Målet med at bygge tårne inden d. 7 november, blev indfriet som et af de allerførste elementer efter planen var lagt. Senere i forløbet blev det besluttet at ændre dette for at bringe den virtuelle reality mere ind i designet af hvordan tårne skulle bygges. Det nye system kom til at fungere ved at et tårn blev bygget med flere mindre elementer der tilsammen udgøre tårnet. Grundet dette nye design kunne datoen ikke holdes da systemet tog længere tid at implementeret end den første version som der var planlagt efter. Målet med at indføre våben til

Side 20 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg spilleren er ikke nået at blevet implementeret fordi andre elementer er blevet prioriteret højere. En af de systemer der var prioriteret højere var hvordan minions skulle indsamle data om tårnene, der så skulle bruges af Overlorden til at tage beslutninger om hvordan den ville angribe spillerens base. Dette informationssystem har taget længere tid end først antaget og idéen til implementeringen kom sent i forløbet, som derfor har været skyld i at nogle elementer ikke har kunne implementeres. En af de helt store syndere for tidsplanen har været den virtuelle reality del af projektet, hvor der ofte er løbet ind i blokader i forhold til bevægelse, feedback systemer ved teleportering og byggelse af tårne hvor raycasting tog lang tid at lære at benytte korrekt.

Side 21 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Abejdsprocess

Implementering af scripts i unity

Arbejdet I Unity.

Med en tidsplan og en skabelon for vores ide til spillet gik vi igang med at lære vores software og hardware at kende for at forberede os til processen af at lave et VR spil i unity. Det første vi gjorde var at kigge på github og finde ud af hvordan vi kunne opsætte de forskellige grene så det blev muligt for alle at arbejde på projektet på samme tid. Det startede ud relativt ligetil, når der sker ændringer pushes de til ens gren, og når der skulle hentes noget ned trækkes det fra den aktuelle gren(også kaldt Master).

Igennem de første måneder af projektet virkede github ligetil. Ændringerne kom langsomt op så alle kunne nå at hente og forstå dem til næste uge, hvor vi så arbejdede videre og folks personlige ændringer var aldrig for langt bagud eller foran det aktuelle projekt. Eftersom systemet virkede allerede den første dag uden at vi havde nogle diskussioner om hvorledes vi skulle informere hinanden om ændringer eller arbejdsopgaver gik vi straks videre til Unity og HTC viven. En af de første præmisser for at få et fundament var at skabe en virtuel verden hvori det var muligt for spilleren at teleportere sig rundt i. På det tidspunkt var VR udstyret allerede sat op for os, og vi gik derfor straks i gang med at anvende unity engine til at bygge verdenen. At bruge en game engine syntes ved første øjekast at være en temmelig fremmed måde at kode på. Pludselig var konceptet om classes ikke længere til stede, og det var muligt at have figurerer på banen med faktisk opførsel uden at skrive en eneste linje kode. At skabe en verden involverede ikke andet end at downloade og aktivere en udvidelse ved navn SteamVR, at trække en plade ind på scenen og give den eksisterende spiller SteamVRs teleport script. Da vi nu havde klaret den første opgave og etableret at projektet kunne lade sig gøre, kastede vi os for alvor ind i unity. Vi lærte at kombinationen mellem et script og en figur til et prefab mindede meget om de classes vi indtil nu havde anvendt i java. Arbejdet med tårne gik også let indtil vi skulle til at finde ud af hvordan vi skulle få dem bygget. Modsat tidligere projekter hvor vi har skulle konstruere projekter i en 2D setting, arbejdede vi i dette projekt i 3D. Dette betød at objekters position ikke længere bare var et x,y koordinat, nu var der også en z akse. Derudover var vi nødt til at sørge for at objekterne var underlagt tyngdekraften så de blev hvor vi havde sat dem, og at de havde en fast form så det var muligt for dem at støde sammen. Dette blev særlig tydeligt da vi forsøgte at implementere den menu hvori spilleren skulle vælge de forskellige byggeklodser til tårne. At skabe menuen gik uden problemer, men at få hevet elementerne ud på banen fra den håndholdte menu viste sig at være et problem. Vi havde som nævnt tænkt os at få placeret tårnene i samme stil som der teleporteres rundt i verdenen, ved at lave en indikator på hvor spilleren pegede hen og så sætte et objekt der, men fordi det var første gang vi arbejdede i 3D havde vi ingen anelse om hvorledes vi med kode kunne hente et x,y,z

Side 22 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg koordinat og anvende det samtidig med at spillet kørte. Unity har en funktion der hedder Raycast. Dens funktion er at tegne en streg lige ud i den retning spilleren peger, og første gang den støder imod noget returnerer den positionen hvor den har ramt. Det vil sige at hvis en spiller pegede lige ned i jorden ville den returnere spillerens position. Projektet endte med at vi succesfuldt fik anvendt en raycast funktion, men i starten af projektet kunne vi grundet vores manglende erfaring ikke få det til at fungere, og vi søgte derfor alternativer. Et af de alternativer var at skyde en kugle ud i den retning spilleren pegede controlleren. Derved kunne vi når kuglen ramte noget, anvende det koordinat til at placere det ønskede objekt på den ønskede position. Dette forsøg viste sig at være fuldstændig håbløst, idet det viste sig at controllerens retning ikke på nogen måde var pålidelig. Resultatet blev at kuglerne fløj fuldstændig uberegneligt rundt, og selv i det tilfælde hvor det lykkedes at placere et objekt med kuglen var oplevelsen ikke ønskværdig. Det føltes klodset og langsomt, hvilket ikke ville fungere når spillet kørte.

På billedet ses et tidligt eksperiment med de kugler der blev kaldt “building bullets”. De lilla kugler skulle med kontakt skabe det objekt spilleren havde valgt.

Med andre ting på programmet besluttede vi at det indtil videre var nok at skabe objekter på spillerens position og så rykke dem rundt efter behov. Efter alle havde afleveret deres eksaminer gik vi ind i intensiven og det var her at vores manglende samtale om github etikette viste sig problematisk. Et af gruppens medlemmer havde over en weekend beslutet sig for ændre drastisk i det skrevne kode.

I de tidlige iteration af projektet, blev der benyttet en struktur hvor hvert prefab havde et enkelt script. Dette medførte at det var nemt at tilgå de forskellige variabler og funktioner internt i det pågældende GameObject, da der ikke skulle kommunikeres mellem flere scripts. Det havde dog sine begrænsninger, idet koden kunne blive overvældende som scriptet voksede. I takt med at flere prefabs og scripts blev implementeret blev det mere og mere klart at de samme

Side 23 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg funktionaliteter, skulle implementeres flere steder. Eksempelvis havde de elementer der skulle bevæge sig, som minions og tårnenes projektiler, hver deres måde at bevæge sig på. Gruppe medlemmet besluttede at sætte sig for at opdele de forskellige scripts i flere dele så der eksempelvis var en standard måde at objekter bevægede sig på. På papiret var denne ændring ønskværdig, og strukturen i koden blev også forbedret, men bivirkningen var at koden nu blev fremmedgjort for de resterende gruppemedlemmer, der nu havde svært ved at arbejde videre. Som projektet lakkede mod enden, mødte vi også forhindringer af mere håndgribelig karakter. Fordi vores spil foregår i en virtuel verden er vores produkt uundgåeligt forbundet med VR udstyret. Dette viste sig at være endnu en kæp i hjulet da vi en dag mødte op til et sæt udstyr der ikke længere fungerede som det skulle. Sensorerne spillede ikke længere sammen, og det resulterede i at den virtuelle verden endte med at være et stort kaos fordi billedet ikke kunne fokusere, gulvet blev skævt og alting der blev vist på skærmen rystede. Vi endte med at måtte bruge 2 dage på at justere sensorere og fixe kabler, for at få udstyret tilbage til den tilstand vi havde modtaget det i først. Dette betød selvsagt yderligere forsinkelser. Lektion fra de dage blev at VR oplevelsen var vidt forskellig fra individ til individ, eftersom det blev klart at trivialiteter så som spillerens fysiske højde kunne have indflydelse på sensorernes samspil.

Det lykkedes os at få udstyret funktionsdygtigt igen, og vi arbejede derfra videre med programmet indtil vi grundet tiden så os nødsaget til at lave en simplere version af produktet for at nå vores deadline. De næste afsnit beskriver de prefabs og scripts der endte med at være i det færdige produkt.

Prefabs

Når der udvikles i unity arbejdes der ofte med prefabs som danner grundlag for mange elementer. Disse prefabs kan der tilføjes forskellige egenskaber til i forhold til den API unity lader udviklere arbejde med. Der er mulighed for at tilføje fysiske egenskaber som tyngdekraft, skubbe et element med en kraft for at give en tiltagende hastighed, forskellige måde at render elementet på og scripts for at programmere elementet til at påføre sig på en specifik måde. Hvis man relatere prefabs til objekt orienteret sprog, vil en prefab svarer til en class hvor et objekt vil være en instantiation af et prefab ved run-time. Nedenfor findes en kortere forklaring af de mere interessante prefabs som er benyttet i udviklingen af dette projekt.

Minions prefab I prefab minions mappen er der i alt tre prefabs, forskellen på de tre er hvordan de bliver renderet i unity. Der er et prefab af en ridder i rustning, et prefab med en bueskytte og et prefab med en troldmand. Fælles for alle tre er at de implementere 5 scripts. Et Health script så de har mulighed for at tage skade af tårne med en resistance profil til at reducere mængde af skade fra

Side 24 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg bestemte typer skade. Et Movement script der giver de tre minions mulighed for at bevæge sig fra punkt A til B. Et Path Finding script, der koordinere hvor minionen skal gå hen, dette script har blandt andet ruten den pågældende minion skal gå. Et intel script som håndtere står for at opbevare informationer fra banen, og en funktion til at vende om for at indrapportere til Overlorden. Til sidst er targeting scriptet, som bruges af Intel scripter til at vurdere om minionen kan “se” et tårn eller ej.

Overlord prefab Der er også lavet et prefab af Overlorden, selvom der ikke er det store behov da der kun vil være en overlord i spillet. Dette er gjort fordi det kan være en fordel at opsætte de forskellige settings i prefabet, som de implementerede scripts har behov for. Selve Overlorden implementerer to scripts som er overlord og MinionWaves. Overlord scriptet står for at implementere de forskellige beslutninger Overlorden skal tage forhold til hvilken information den har til rådighed og hvordan den sammensætter næste bølge af minions. MinionWave scriptet

Side 25 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg står for at modtage Overlordens beslutning og på et mere praktisk plan, står for at få kaldt spawneren i et defineret tidsinterval indtil hele bølgen er spawnet.

Spawner prefab Spawner prefab står for at oprette (instantiate) de minions som Overlorden dikterer gennem MinionWave scriptet. Det vigtige ved dette prefab er at den indeholder en Box Collider, som trigger en funktion i minion prefab, så de despawnes og sender deres rapport til Overlorden. Derudover implementerer denne prefab også spawner scriptet, som indeholder den specifikke function som oprettet instantiationen af det pågældende minion prefab.

Towers prefab I towers prefab mappen er der 5 forskellige prefabs af forskellige opsætninger ligesom ved minions prefabs. Fælles for alle er at de implementerer tre scripts. Tower scriptet står for størstedelen af opsætningen på de forskellige tårne, står for time intervallet mellem hvert skud, at kalde targeting scriptet for at få en minion at skyde på, at sigte på en minion og til sidst at skyde den pågældende minion. Targeting script er det samme som benyttes i minions prefab, som returnerer en minion som tårnet kan se.

Projectiles prefab Der er fire prefabs, et for hver type af towers, som alle implementerer de to scripts projectile og movement. Movement scriptet er det samme som ved Minions og står for at få projectiles frem til målet. Projectile scriptet, holder den data som bestemmer hvor hårdt den slår, hvor hurtigt projektilet bevæger sig, hvad type skade den giver og om målet er væk før projektilet er nået frem.

Building obj prefabs I building obj prefab mappen er der alle vores prefabs der har med konstruktionen af towers at gøre, fælles for disse prefabs på nær Tower Foundation er at de all implementerer 3 hoved scripts fra SteamVR. “Interactable” som gør at player controlleren får en gul outline rundt om sig, så playeren kan se at det objekt han/hun holder controlleren over er muligt at interagere med. Det andet script er “Throwable” som giver playeren mulighed for at samle det givne objekt op og smide/kaste det væk. Det sidste af de 3 er “Velocity Estimator” som beregner hastigheden objektet får pålagt sig når playeren kaster med det. Udover det har disse building obj prefabs 2 vigtige komponenter som er en collider der gør at vi kan registrere når der sker en kollision og en rigidbody som giver objektet en vægt og gør at den er påvirket af tyngdekraften.

Side 26 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Scripts

Nedenfor findes en række underkapitler der beskriver hvordan de forskellige scripts virker. Fælles for alle unity scripts er at de alle starter med to standard funktioner som er Start() som bliver kaldt en gang når gameobject som holder den bliver instantiated og Update() som bliver kaldt hver eneste frame i spillet.

AnimationEvents.cs Dette script indeholder nogle funktioner uden nogen funktionalitet, disse eksisterer kun for at blive kaldt af de importerede prefabs som danner grundlag for de minions der er i spillet. Uden dette script ville de generere en fejlbesked om at de ikke kan finde de pågældende funktioner. I alt er der 5 funktioner henholdsvis Attack1(), Idle(), Run(), FootR() og FootL().

BaseSpecs.cs Formålet med dette script er at give spillerens base en funktionalitet der gør det muligt at tabe spillet. Scriptet indeholder to variabler, en boolean for at markere om spillet er slut eller stadig er i gang, og en int for at markeret hvor meget liv basen har tilbage. Scriptet indeholder to funktioner der sammen med variablerne implementerer muligheden for at tabe spillet. Den første funktion er OnTriggerEnter(Collider Other) som bliver kaldt når et GameObject kolliderer med med basen. Funktionen tjekker om det gameobject der kolliderer har mærket (tag) enemy, hvis ja, bliver basens liv reduceret og det andet GameObject bliver slettet. Hvis basen har eller kommer under 0 health, så bliver basen destrueret og spillet er slut (gameover = true).

Den sidste funktion er ReduceHealth(int amount) som reducere basen med et antal health som bliver defineret når funktionen bliver kaldt.

Side 27 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

ControllerFlameThrower.cs I dette script tjekker flammekasteren på det givne tårn om tårnet har et mål eller ej. Altså om en modstander er inden for tårnets rækkevidde, hvis modstanderen er så tænder den for flammekasteren og hvis ikke der er en modstander så slukker den for flammekasteren.

Health.cs Dette afsnit omhandler destruktionen af fjender og hvordan de mister liv.

Som nævnt handler tower defense for spilleren om at forsvare sin borg mod fjendtlige indtrængere, og derfor er det naturligvis nødvendigt for fjenderne at være modtagelige overfor forsvarsværkernes angreb. Til det formål har vi udviklet et script der står for udregningerne bag fjendernes helbred. Som nævnt ønsker vi at have forskellige typer af fjender der tager forskellige mængder af skade baseret på deres egen type af rustning og spillerens typer af tårne. Det er derfor en essentiel funktion for health scriptet at kunne foretage en beregning der tager højde for fjendens type sammenlignet med tårnets projektil.

Health scriptet består af 3 metoder, Start, TakeDamage og setResistanceprofile. setResistanceProfile består kun i at modtage variablerne der er sat i minion prefabs i Unity scenen således at de kan anvende i TakeDamage metoden.

TakeDamage metoden tager en damageValue, en damageType og et tag. Damage value fås fra projektilet scriptet, der har arvet den fra tårn scriptet, som har hentet den ind igennem de tower prefabs der eksisterer ude i Unity scenen. TakeDamage kører så en switch case baseret på den damageType string den fik ind igennem konstruktoren. På denne måde kan det lade sig

Side 28 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg gøre at sætte resistance værdien afhængigt af hvilken type tårn der har skudt projektilet afsted. (damageType nedarves på samme måde som damageValue.)

Efter at switch casen er kørt og resistance værdien er fundet, bruges den efterfølgende beregning af hvor meget skade og hvor meget liv, fjenden har tilbage.

Udregningen reducerer skaden fjenden tager med den procentmæssige værdi der passer til fjendens resistance. Vores knight warrior har 25 resist mod physical damage, og 10 health, hvilket betyder at udregningen for et physical skud ville blive: 10 - 2 * (100-25) / 100) ⇔ 10 - 2 * 0.75 ⇔ 10-1,5 = 8,5 Liv efter skud. 1,5 skade modtaget. Hvis fjendens liv ryger ned på eller under 0, er fjenden død, og bliver således destrueret. Værdien af Overlordens minion count bliver reduceret således at den til sidst når 0 og aktiverer tidsintervallet for udsendelsen af den næste bølge af fjender.

Side 29 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Intel.cs Formålet med dette script er at give de forskellige fjender (minions), der bliver sendt mod spillerens base, mulighed for at håndtere information om spillerens forsvar. For at kunne implementere dette system er der behov for at kunne indsamle information, opbevare dette og kunne videregive det til Overlorden, som skal bruge denne information. Dette er gjort ved hjælp af tre funktioner; Update(), AddTarget(GameObject target) og GetSpottedList(). Update() er en funktion der stammer fra MonoBehaviour classen fra unity som denne class extender. Funktionen vil blive kaldt en gang pr billede (frame) som spillet viser. Koden I Update() kalder FindTarget funktionen i Targeting scriptet som kan se i “if” statemented (target.FindTarget(range)). Hvis der findes et mål (target) inden for den afstand (range) som bliver opgivet ved opsætning af dette script i unity, vil dette blive sendt videre til næste funktion AddTarget. Det vil sige at vi i Update() finder tårne der er inden for den afstand de kan ses fra. Derefter opdateres scriptet på hvor meget liv (health) der er tilbage, og hvis der er 3 eller mindre liv tilbage, vil denne minion forsøge at rapportere tilbage til Overlorden. Til sidst er en if statement der muliggør at få et udprint af de tårne minionen har set. Dette virker ved en public boolean som kan trykkes på i unity ved test, hvorefter PrintListDebug() bliver kaldt.

Når AddTarget bliver kaldt, så bliver listen af kendte tårne tjekket for det opgivne target, og hvis target ikke findes i listen bliver det tilføjet.

Side 30 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

GetSpottedList() er en simpel getter funktion, den bliver kaldt når en minion collider med spawn, hvor overlord har en setter funktion til at modtage listen.

PrintListDebug() er en funktion der løber spottedList igennem og printer navnet på hvert GameObject I list. Denne funktion er til at teste hvilke tårn den pågældende minion har set.

MinionWaves.cs Formålet med MinionWave scriptet er at håndtere hvordan en bølge af minions bliver lavet. En bølge af minions består af minions med en smule afstand fra hinanden, så de går på en række og ikke oven i hinanden, samtidig skal der kun genereres et bestemt antal minions. Antallet af minions bliver sat i unity. Når hele bølgen er genereret skal scriptet først bruges igen ved næste bølge. Scriptet består af to funktioner, SpawnWave() og Update(). Når SpawnWave() bliver kaldt, modtager den en liste of de minions der skal være i den pågældende wave. Listen bliver gemt som en lokal liste for scriptet, og ud fra listen bliver der sat hvor mange minions der i alt skal genereres. Til sidst sætter den en boolean true som holder styr på om der skal generes en bølge af minions eller ikke.

Side 31 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

I update() køre en timer kaldet countDown, som sørger for at sprede minion ud i en bølge med et lille tidsinterval mellem hinanden. Tidsintervallet bliver sat i unity i int variablen delayBetweenMinions. Hvis der er mere end 0 minions i listen som ikke er genereret endnu, bliver de sendt til spawner scriptet på spawneren. Ellers vil koden gå tilbage til et stadie hvor spawning af bølgen stopper, isSpawning = false.

Movement.cs Formålet med dette script er at få GameObjecter til at bevæge sig i spillet i forhold til hinanden. Samtidig er der en funktion der tjekker om GameObjektet vil nå hen til det andet GameObjekt inden for næste frame i spillet. Dette er implementeret ved to grundlæggende funktioner HaveReached() og Move() med en float variable kaldet moveSpeed til at definere hastigheden på bevægelsen.

Når HaveReached() bliver kaldt, skal den have en vektor og returnere true eller false. Funktionen skal bruges for at finde ud af om de to gameobjekter vil ramme hinanden inden for det tidsinterval der er mellem hvor funktionen bliver kaldt til det næste frame.

Side 32 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Dette er gjort ved at sammenligne størrelsen på den opgivne vektor med hastigheden ganget med den tiden der går til næste frame.

For at kalde den private HaveReached() funktion skal Reached() funktionen kaldes. Dette er to funktioner der er overloaded hvor en vektor bliver regnet ud fra enden et GameObjekt eller en Transform.

Den anden grundlæggende funktion er Move() som modtager en vektor i konstruktøren ligesom HaveReached() gjorde. Denne vektor bliver normaliseret så den har en magnitude på 1, og efterfølgende ganget sammen med bevægelseshastigheden og tiden til næste frame. Space.World betyder at disse koordinater skal relateres til hele spilleverdene og ikke i forhold til GameObjekter.

MoveTo() funktionen er også to overloaded funktioner der ligesom Reached() funktionen modtager enden et GameObjekt eller en Transform. Ud fra hvad funktionen modtager bliver en vektor udregnet som bliver videregivet til den private move() funktion.

Side 33 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Overlord.cs Formålet med overlord scriptet er at lave en modstander som der kan spilles mod. Der er en hel række krav til hvad Overlorden skal kunne. Overlorden skal kunne tage imod information der kom ind fra banen via returnerende minions. Overlorden skal kunne tage stilling til denne information, og tilpasse næste bølge af minions. Dette kommer på nuværende tidspunkt til udtryk ved at minions får tilpasset deres resistance over for de forskellige typer damage, baseret på hvilken information Overlorden har. Denne tilpasning er specifik for den path minionen er bestemt til at tage. Overlord scriptet var det sidste der er arbejdet på da det sammenfletter mange af de funktioner og elementer der er i spillet. Dette kan ses ved at scriptet ikke er blevet delt op i flere komponenter, men i stedet er samlet i et stort script. Grundet skred af tidsplanen og tilføjelser i sidste øjeblik, er det ikke nået at opdele scriptet. Nedenfor ses der mange af funktionerne med kode og forklaring til.

Update() funktionen er en af de længste der er i scriptet. Til at begynde med så tjekker den om der er flere minions i live, hvis ikke vil minionsCount være under 1 og timeren WaveCountDown begynder. Denne timer tæller ned til hvordan næste bølge minions skal sendes af sted. Når timeren har ramt 0, resetter den timeren til en defineret værdi fra unity, og sætter en boolean spawnNextWave til true. Derefter tjekker et if statement om der skal spawnes en ny bølge af minions. Hvis dette er sandt kaldes SpawnWave funktionen fra MinionWaves scriptet med AddMinionToSpawnList() funktionen som et argument.

Side 34 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

AddMinionToSpawnList() funktionen vil så generere en liste af minions som den returerer. Dette foregår ved at en ny liste bliver oprettet, hvor et for loop tilføjer minions til listen. Antallet af minions er bestemt i konstruktøren. I for loopet bliver en tilfældig type minion oprettet (instantiated) og sat til inaktive. Derefter får minionen tildelt en tilfældig rute (path), hvorefter en resistance profil bliver sat baseret på hvilken path minionen fik tildelt. Til sidst i loopet bliver den nylavede minion tilføjet til listen der skal returneres.

Side 35 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

I funktionen CalculateDamageRatios() bliver der foretaget nogle beregninger for at finde ud af i hvilket forhold hver type damage bliver givet i forhold til totalmængden. Når funktionen bliver kaldt, skal der gives en liste af de tårne der er spottede på den given path. I et for loop baseret på listens længde, bliver en total damage værdi beregnet, og en samlet damage værdi for hvert type tårn. Ud fra dette bliver der beregnet hvor stor en procentdel af skaden der kommer fra hvilken type og et sæt af floats kaldet dpmFireRatio (osv.) bliver sat til denne procentsats.

Side 36 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

I ApplyResistanceProfil() funktionen bliver disse dpmFireRatio(osv.) afrundet til en int fra en float og lagt til den grundlæggende resist på 10. Som et argument i funktionen skal der gives en minion, der får tilføjet denne resistance profil og efterfølgende returneret.

For at gøre det muligt at skifte lidt mellem de forskellige minion typer og ruter (paths) så er der to funktioner, RandomMinion() og RandomPath(), som returnerer en tilfældig minion eller rute. Nedenfor ses funktionen af RandomMinion og RandomPath() er opbygget på samme måde.

Side 37 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Når en minion kommer retur med information fra spillet, bliver ReceiveSpotList() funktionen kaldt når minionen collider med spawneren. Når funktionen kaldes skal der opgives en list af de tårne der er spottet og hvilken rute som minionen har gået. Nedenfor ses hvordan hver path bliver tjekket i et if statement og efterfølgende om tårnet i minionens liste er kendt. Hvis det er et ukendt tårn bliver det tilføjet til Overlordens liste af tårne.

PrintListDebug() er en test funktion som printer en given liste af tårne i konsolen. På denne måde kan vi se hvilket og hvor mange tårne Overlorden kender til.

PathPointList.cs Dette afsnit omhandler PathpointList, der er et kort script der har til opgave at indeholde en liste af alle eksisterende pathpoints på banen ved spillets start.

Side 38 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

PathpointList scriptet er virkelig kort fordi det kun skal udføre en funktion. Det skal skabe en liste og tilføje alle de mulige pathpoints der er defineret gennem unity på banen. Dette sørger for at der ikke er tomme pladser i listen. Først er listen defineret som path, der er en get funktion som sørger for vi kan hente listen i andre scripts og der er en funktion der tilføjer et gameobject til listen fra konstruktøren, såfremt punktet ikke er i listen i forvejen.

Side 39 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

PathFinding.cs Dette afsnit omhandler pathfinding, det er det script der sørger for at minionsne bevæger sig i den ønskede retning.

PathFinding scriptet skal sørge for at Overlordens minions kan finde vej mod basen. Det skal sørge for at minions kan finde vej tilbage igen hvis de bliver bedt om at flygte, og det skal opfange om minions når hen til de punkter de er på vej imod så de kan udføre den opgave de skal gøre ved de forskellige punkter. Hvis de når basen skal de skade basen og destruere sig selv, hvis de bliver bedt om at vende om og når tilbage skal de videregive information og hvis de når et pathpoint skal de bevæge sig mod det næste punkt.

I start metoden ser vi at alting bliver hentet ind. Funktioner fra movement, informations indsamling fra Intel relation til Overlorden, og stien fra pathlist. I update tjekker vi hver frame på minions status. Hvis den er nået til et pathpoint, reportIntel er true og det ikke er det første punkt i path listen, skal den gå til det forrige punkt i listen den besøgte. Hvis den derimod er nået til start punktet skal den destruere sig selv. Endelig er der en If statement der alt efter isReportIntels status enten rykker minionen frem til det næste punkt i listen hvis den ikke er sand og til det index den er på hvis den er, på den måde kan vi i OnTriggerEnter funktionen nedenfor nå at ændre pathIndex til en mindre værdi, hvilket vil få minionen til at bevæge sig tilbage til start punktet.

Side 40 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Ud over vores setters og getters i bunden af scriptet, der kan ses i bilaget, er OnTriggerEnter den sidste funktion vores PathFinding behøver. Det er her vi bestemmer interaktionen mellem minions og hvad end den rammer. OnTriggerEnter er en unity metode der tjekker på kollisionen mellem gameObjekter, igen er vores isReportIntel boolean vigtig for at bestemme om der skal tælles op eller ned i listen. Hvis isReportIntel er falsk og der kollideres med et pathpoint skal der rykkes frem, hvis den er sand skal der rykkes tilbage, hvis er sand og der kollideres med det første punkt i listen indikeret ved enemySpawn, skal den give listen af tårne den har set til Overlorden og destruere sig selv, og til sidst hvis den når spillerens base skal den gøre skade på basen og destruere sig selv.

Projectile.cs Projectile scriptet skal holde styr på hvorvidt de projektiler tårnene skyder ud har ramt deres mål, og når de har skal de påførere den ramte minion skade.

Projectile scriptet står for at projektilerne kan finde hen til tårnets mål og give skade til dem. Derfor bliver de naturligvis nødt til først at finde et mål og have en måde at tjekke på om de faktisk har et mål.

Dertil har den en setTarget funktion, der bliver kaldt i Tower scriptet når tårnet

Side 41 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

har fundet et mål der er indenfor rækkevidde.. I update kører boolean funktionen gotTarget hver frame, og når den returnerer true betyder det at tårnet har valgt et mål og skudt et skud af, hvilket leder videre til at projektilet kan køre sine resterende funktioner. Fra movement scriptet tjekker projektilet om det har ramt sit mål, hvis det ikke har, så kører det moveTo for at bevæge sig tættere på målet og når det så når dertil kører det damage funktionen. I damage funktionen bruges health scriptet til at kalde take damage funktionen så projektilet kan give den korrekte mængde skade afhængigt af hvilket tårn der har skudt det afsted. Dernæst bliver projektilet destrueret så det ikke ender med at der flyder hundredvis af kugler rundt på banen

Projektile Update

Spawner.cs Dette afsnit omhandler spawneren. Spawneren er det script der indeholder funktionerne til at skabe de minions Overlorden sender imod spilleren. Funktionene skal bruges i MinionWaves scriptet, til at skabe minionsne på starten af stien.

Det første vi har er en private funktion der tager et Gameobject og en Vektor ind. GameObjectets position bliver så vektoren der tages ind i konstruktøren og objektet bliver

Side 42 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg derefter sat til aktvivt. Det vil sige at det bliver placeret i scenen. Den eksistere før den bliver sat til aktiv, dette sker i den næste metode: spawn.

Spawn metoden tager også et GameObjekt. Den skaber derefter en ny vektor og sætter den vektor til positionen af det objekt der holder scriptet. I dette tilfælde er det vores første pathpoint, der er begyndelsen af stien. Hvilket vil sige at vektoren bliver stat til starten af stien. Derefter bliver den forrige funktion kaldt, nu med den bestemte minionType og start position korrekt ved starten af stien.

Spawneren har en ekstra Spawn metode der er et levn fra tidligere iterationer hvori de minions fik en path allerede når de blev skabt i spawn scriptet, dette er nu rykket til overlord scriptet.

StatsUpgrade.cs Formålet med dette script er at tilføje muligheden for at upgradere de forskellige værdier (stats) for GameObjekter der har et Tower script. Der er nogle variabler hvor der kan defineres i unity hvor meget hver værdi skal stige når en upgradering sker. Her kan nævnes upgradeDamage og upgradeRange som er to int variabler. Scriptet består af 4 funktioner der danner grundlag for denne funktionalitet. upgradeStats(), upDamage(), upRange() og Update(). Update() funktionen er til for at teste om scriptet virker og ved at give en knap i unity hvor man kan kalde på upgradeStats() som ses i billede nedenfor.

Side 43 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

upgradeStats() er den funktion der kan kaldes ude fra scriptet, og som holder styr på om der kan upgraderes mere eller om max er nået. upgradeLevel er en int variable der starter på 0 og som bliver forøget med 1 for hver gang koden i if statementet kører. maxLevel er en int variable der bliver defineret i unity til hvad max levelet ønskes for det pågældende tårn. Til sidst i funktionen kaldes funktionerne upDamage() og upRange() som er de to stats der kan upgraderes i denne version, en ny funktion for projectile hastighed eller skydehastighed kan eksempelvis tilføjes her.

Nedenfor ses de to funktioner der opdaterer værdierne for tårnets stats. Dette bliver i begge funktioner gjort ved at hente tårnets værdier med gettere og efter beregning bliver de sat igen med setters.

Side 44 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Targeting.cs Formålet med dette script er at finde et GameObjekt med et defineret tag, som ses nedenfor som en string variable targetTag. Scriptet finder alle GameObjekter og laver et array med alle de fundne gameobject. Arrayet bliver tjekket for hvilket der er tættest på det GameObjekt der søger et target. Hvis det nærmeste target er inden for range bliver target returneret ellers bliver null returneret.

Side 45 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Side 46 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Tower.cs Formålet med dette script er at finde afstanden til modstander inden for det givne tårns rækkevidde, ændre tårnets rotation så den peger mod modstanderen og affyre et projektil mod den.

Reloading funktionen bliver brugt til at sætte et tidsinterval mellem hver projektil bliver affyret mod modstanderen.

Aim funktionen roterer det givne tårn så den peger mod den modstander der skal skydes på.

Shoot funktionen laver et nyt gameobject som er vores projektil som får et target(den modstander den skal flyve mod), en speed(hastigheden den bevæger sig), et damageAmount(den skade den påføre modstanderen) og en damageType(den form for skade projektilet påfører).

Side 47 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Scripts for Tower Building

Herunder findes de scripts som bruges til Tower Building hvor der forklares hvordan hvert script virker og hvad det bruges til.

Tårne er forsvarsværker som spilleren bygger for at bekæmpe de fjender der stormer borgen. Tårnene starter med at være effektive, men jo længere spillet vare jo stærkere bliver fjenderne og derfor er det nødvendigt med en måde også at opgradere tårnene på. Derudover ønsker vi også at give fjenderne styrke og svagheder mod visse elementer, og derfor bliver tårnene i samme stil nødt til at have forskellige typer, så der kan bekæmpes ild med vand og så videre.

TowerCreation.cs I dette script tjekker den om der er sket en kollision med et andet objekt som har “buildMaterial” taget og hvis der er så destruerer den det objekt og sætter et nyt tårn, hvor det gamle stod, hvorefter bygge objektet selvdestruerer.

TowerTypeUpgrade.cs I dette script tjekker den om der er sket en kollision med et andet objekt som har “BasicTower” taget og hvis der er så destruere den det tårn og sætter et nyt tårn med den nye type hvor det gamle stod, hvorefter upgrade objektet selvdestruerer.

Side 48 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Upgrade.cs I dette script tjekker den om der er sket en kollision med et andet objekt som har “TowerUpgradeAble” taget og hvis der er så kalder den et komponent i det objekt som er “StatsUpgrade” og beder den om at opgradere, hvorefter dette objekt selvdestruerer.

Scripts for Hand Menu

Her findes de scripts som hand menu’en gør brug af, hvordan de virker og hvad de bruges til.

Side 49 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

BuildingMenu.cs I dette script har vi lavet 3 funktioner som bliver kaldt i update, hvilket vil sige at de bliver kaldt hver eneste frame i spillet. Den første af dem er “MenuOpen” som åbner eller lukker vores menu når spilleren klikker på “ApplicationMenu” knappen som er den lille knap øverst på vive controlleren med de 3 vandrette streger.

Den anden funktion er “CheckItemID” som skulle køre gennem menu ved hjælp af touchpaden, men denne funktion nåede vi ikke at få til at virke. Sidst men ikke mindst har vi “PlaceObject” funktion som først tjekker om spilleren holder et af de 3 menu objekter fra “ButtonHandler” scriptet og hvis spilleren gør så vises der en streg ud fra spillerens controller. Denne streg eller laser er grøn hvis controlleren peger på et sted hvor der kan placeres et tårn og den er rød hvis controlleren peger op i luften eller et sted hvor der ikke kan placeres et tårn.

Side 50 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Herefter tjekker den hvilken af de 3 objekter spilleren holder (holdingFoundation, holdingBall, holdingUpgrade) og hvorledes triggeren er blevet integreret med. Når triggeren bliver trukket ind så gemmer den positionen af hvor stregen/laseren peger på og når triggeren bliver givet slip på instantierer den et nyt object af det spilleren holder på den position der blev gemt tidligere.

ButtonHandler.cs I dette script holder de 3 menu objekter (TowerFoundation, BuildingBall, UpgradeObject) styr på hvilken af dem som har kollideret med den anden virtuelle hånd, hvorefter dette object sættes som værende holdt af den anden virtuelle hånd og kan herefter benyttes af BuildingMenu scriptet.

Side 51 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Side 52 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Diskussion

For gruppens medlemmer er det første gang at vi i fællesskab arbejder på et større IT projekt, og det har ikke været uden sine problemer. Eksemplet med github viser at vi fra start lavede to store fejl helt i planlægningsfasen. For det første skulle det have været diskuteret hvorledes vi ville implementere hinandens iterationer i det overordnede produkt. Manglen på en github etikette resulterede ultimativt i at gruppen var splittet i forhold til forståelse af koden og dens opsætning, hvilket belyser den anden store fejl: manglen på et konsensus omkring systemets opbygning. Alle i gruppen har haft eller er ved at tage software engineering og alligevel formåede vi ikke at forudse behovet for en overordnet IT strategi der kunne have fungeret som et samlingspunkt hvorfra en arbejdsfordeling kunne have fundet sted. Hvis vi havde konstrueret UML diagrammer eller andet visuelt materiale og planlagt de forskellige scripts funktionaliteter fra start havde vi helt sikkert kunne have nået mere og fået mere ud af vores projekt. Det skal dog nævnes at vi i starten gjorde et forsøg på at konstruere disse, men vores manglende viden om unity og sproget C# resulterede i at de arbejdssessioner endte i spekulationer snarere end konkrete planer. Den korrekte strategi havde sandsynligvis været i fællesskab at udføre en lille oplæringsprocess, hvorefter vi kunne have genstartet den planlægende fase.

Vi bliver også nødt til at nævne at vi fuldstændig havde overset muligheden for de tekniske problemer hardwaren kunne forårsage. Ikke at vi kunne have gjort noget for at forhindre hardwarens sammenbrud, men med bedre planlægning ville det have haft mindre betydning end det endte med at få.

Som programmet står nu, endte vi med at være nødt til at strege en række af de elementer i spillet vi satte os for at udfører. Overlorden endte med bare at sende en lille koalition på 10 fjender afsted på et tidsinterval, og selvom vi har funktionen til at tilbagesende information gør vi kun brug af den i mindre grad. Derudover nåede vi ikke at få lavet begrænsninger på hvor tårnene kan stå og det er stadig muligt at bygge dem oven på hinanden. Vi ville også gerne have haft en mulighed for at teleportere tilbage op på borgen før hver runde så det blev muligt for spilleren at danne sig et overblik over spillets udfoldelse og hvor det ville være strategisk at bygge flere tårne.

Side 53 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Konklusion

I dette projekt har vi undersøgt mulighederne for at udvikle et spil i virtual reality der kan spilles imod computeren. Fremgangsmåden har været selv at produceret et computerspil igennem unity, der kan tages og anvendes igennem HTC viven. Igennem vores projekt har vi ved hjælp af github arbejdet sammen om at konstruere et tower defense spil, hvori spilleren skulle forsvare sin base mod en usynlig fjende. I vores iver efter at skabe et spil vi selv ville synes var fantastisk har vi overset behovet for udførlig planlægning og i stedet lagt for meget vægt på ideer til spillets design, snarere end den kode mæssige struktur.

Vores resultat er desværre ikke i nærheden af hvor vi havde håbet at ende ude med, og set i bakspejlet skyldes det at vi ikke har udnyttet de redskaber vi har haft til rådighed til fulde. Baseret på vores manglende planlægning og det faktum at vi ikke nåede hvad vi satte os for, må vi drage den konklusion, at der også i spiludvikling er et behov for udførlig planlægning af de forskellige komponenter, deres indhold og deres forhold til de hinanden. Hvis vi havde sørget for udførligt planlægning, havde det været muligt at opnå en mere struktureret arbejdsfordeling der højst sandsynligt kunne have skubbet projektet tættere på det endelige mål end hvor vi står nu.

Vi kan i hvert fald konkludere at det er sandsynligt at støde på forsinkelser og uventede forhindringer under processen af at skabe et computerspil, men med det taget i mente har vi valgt at inkludere dette velkendte citat fra skaberen af Mario spillene;

“A delayed game is eventually good, but a rushed game is forever bad”. - Shigeru Miyamoto

Vores spil er til afleveringsdato muligvis ikke alt vi havde håbet på at det kunne være, men det stopper os ikke fra at forbedre det fremover.

Side 54 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Reference list

1. Hardware - Virtual Boy « Planet Virtual Boy [Internet]. [henvist 17. december 2018]. Tilgængelig hos: https://www.planetvb.com/modules/hardware/index.php?type=vb&sec=main 2. Oculus Rift: Step Into the Game [Internet]. Kickstarter. [henvist 17. december 2018]. Tilgængelig hos: https://www.kickstarter.com/projects/1523379957/oculus-rift-step-into-the-game 3. Kain E. Is $599 Too Much To Pay For The Oculus Rift? [Internet]. Forbes. [henvist 17. december 2018]. Tilgængelig hos: https://www.forbes.com/sites/erikkain/2016/01/06/is-599-too-much-to-pay-for-the-oculus-rift/ 4. Oculus Rift | Oculus [Internet]. [henvist 17. december 2018]. Tilgængelig hos: https://www.oculus.com/rift/#oui-csl-rift-games=robo-recall 5. SteamVR Plugin - Asset Store [Internet]. [henvist 14. december 2018]. Tilgængelig hos: https://assetstore.unity.com/packages/tools/integration/steamvr-plugin-32647 6. About - Git [Internet]. [henvist 14. december 2018]. Tilgængelig hos: https://git-scm.com/about/ 7. Brown K. What Is GitHub, and What Is It Used For? [Internet]. How-To Geek. 2016 [henvist 14. december 2018]. Tilgængelig hos: https://www.howtogeek.com/180167/htg-explains-what-is-github-and-what-do-geeks-use-it-f or/ 8. GitHub features: the right tools for the job [Internet]. GitHub. [henvist 14. december 2018]. Tilgængelig hos: https://github.com/features 9. Products - Unity [Internet]. Unity. [henvist 14. december 2018]. Tilgængelig hos: https://unity3d.com/unity 10. Shyguymask. Evolution Of Bloons Tower Defense (2007-2018) [Internet]. [henvist 16. december 2018]. Tilgængelig hos: https://www.youtube.com/watch?v=OwRZzPt1xRk 11. imafartist. Onslaught tower defence strategy [Internet]. [henvist 16. december 2018]. Tilgængelig hos: https://www.youtube.com/watch?v=pV9SFtE_KCo 12. TOP NET Games. Warcraft 3 - Tower Defense - Wintermaul [Full HD] [Internet]. [henvist 16. december 2018]. Tilgængelig hos: https://www.youtube.com/watch?v=FbBmnNXbU4E 13. Ben Plays VR. The BEST Tower Defense game in VR - “In your face TD” - HTC Vive [Internet]. [henvist 16. december 2018]. Tilgængelig hos: https://www.youtube.com/watch?v=_r-HNqmhTSg

Side 55 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag

Bilag 1 : Unity Interface

Billedet viser hvordan unity interfacet er sat op. Det øverste billede er scenen set ovenfra, med den sorte bog tættest på, det nederste viser hvad HTC viven har i fokus. Til højre ses et gameObject i scenen.

Side 56 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag 2: Unity Prefabs eksempel

Billedet her viser hvorledes vores knight prefab, blandt andet har en forudbestemt størrelse samt en række scripts.(Der ses kun 1, af hensyn til billede størrelsen).

Side 57 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Scripts

Bilag: AnimationEvents.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * the purpose of this script is to catch some calls the imported prefabs do, * this will only prevent an error showing up in the console from the prefabs */ public class AnimationEvents : MonoBehaviour { void Attack1() { }

void Idle() { }

void Run() { }

void FootR() { }

void FootL() { } }

Side 58 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: BaseSpecs.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * the purpose of this script is to add functionallity to the base, * so it can report to the overlord if it has taken damaged and despawned a minion */ public class BaseSpecs : MonoBehaviour { bool gameover = false;

[Header("Setup")] public int health;

void OnTriggerEnter(Collider Other) { if (Other.tag == "Enemy") { health--; Destroy(Other.gameObject); if (health <= 0) { gameover = true; Destroy(gameObject); } } }

public void ReduceHealth(int amount) { health = health - amount; } }

Side 59 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: ControllerFlameThrower.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this Class is to enable and disable the flamethrower which is mounted on the fire tower */ public class ControllerFlameThrower : MonoBehaviour { public GameObject flameThrower; private GameObject target;

private void Update() { target = GetComponent().GetTarget(); if (target == null) { flameThrower.SetActive(false); } else { flameThrower.SetActive(true); } } }

Side 60 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Health.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this class is to enable prefabs to have health, and a set of resistense to different type of damage */ public class Health : MonoBehaviour { private GameObject overlord;

[Header("Defensive Stats")] public float health; public int resistancePhysical; public int resistanceFire; public int resistanceWater; public int resistanceLightning; private int resistance;

// set resistance to the resisten of the specific damageType that is being dealt. public void TakeDamage(int damageValue, string damageType, string objTag) { switch (damageType) { case "physical": resistance = resistancePhysical; break; case "fire": resistance = resistanceFire; break; case "water": resistance = resistanceWater; break; case "lightning": resistance = resistanceLightning; break; }

health = health - (float)damageValue * ((float)(100 - resistance) / 100);

// Take care of despawning the minion if killed if (health <= 0) {

Side 61 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

overlord.GetComponent().DecreaseMinionCount(); Destroy(gameObject); } }

void Start() { overlord = GameObject.FindGameObjectWithTag("Overlord"); overlord.GetComponent().IncreaseMinionCount(); }

// function to set all the resistances of the minion public void SetResistanceProfil(int physical, int fire, int water, int lightning) { resistancePhysical = physical; resistanceFire = fire; resistanceWater = water; resistanceLightning = lightning; } }

Side 62 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Intel.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this class is to gathere information about the towers and report this to the overlord */ public class Intel : MonoBehaviour { public bool reportIntel; public float range; private float health; private Targeting targeting; private List spottedList; public bool printList; // to run test function

private void Start() { spottedList = new List(); targeting = GetComponent(); }

void Update() { if (targeting.FindTarget(range) != null) { //add a non-null Tower-GameObject to the List AddTarget(targeting.FindTarget(range)); }

health = GetComponent().health;

// return to overlord if health is 3 or lower if (health <= 3) { reportIntel = true; }

// runs test script if (printList == true) PrintListDebug(); }

void AddTarget(GameObject target) { // This statement adds spotted towers to a list of gameObjects rather than their types.

Side 63 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

// This is to ensure that we dont end up with a list of strings so that a minion can spot multiple towers of the same type. if (!spottedList.Contains(target)) { spottedList.Add(target); } }

// getter public List GetSpottedList() { return spottedList; }

// a function to test if a minion got a list of towers. void PrintListDebug() { for (int i = 0; i < spottedList.Count; i++) { Debug.Log(spottedList[i].name); } printList = false; } }

Side 64 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: MinionWaves.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this script is to call the spawner scripts with each of * the minions in a list given to this script in a timely manner */ public class MinionWaves : MonoBehaviour { public GameObject spawner; private GameObject path; private GameObject minion; private List minionList; public float delayBetweenMinions;

[Header("ReadOnly Info")] public float countDown; public int minionsToSpawn = 0; public bool isSpawning = false; private int i;

public void SpawnWave(List minionList) { this.minionList = minionList; this.minionsToSpawn = minionList.Count; isSpawning = true; }

private void Update() { countDown -= Time.deltaTime; if (isSpawning && countDown <= 0) { countDown = delayBetweenMinions; if (minionsToSpawn > 0) { minionsToSpawn--;

// call the spawner script with the minion on i position in the list spawner.GetComponent().Spawn(minionList[i]); i++; } else if (minionsToSpawn <= 0)

Side 65 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

{ isSpawning = false; i = 0; } } } }

Side 66 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Movement.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this class is to move a object to a given position, * it also have a function to check if it will reach the position within the next frame */ public class Movement : MonoBehaviour { [Header("Movement")] public float moveSpeed;

// check if the object will reach its target within the next frame private bool HaveReached(Vector3 direction) { if (direction.magnitude <= moveSpeed * Time.deltaTime) { return true; } else { return false; } }

// this function handle the movement of the object. private void Move(Vector3 direction) { transform.Translate(direction.normalized * moveSpeed * Time.deltaTime, Space.World); }

public void SetSpeed(float speed) { this.moveSpeed = speed; }

// Public overloaded caller function for core logic public bool Reached(GameObject target) { Vector3 dir = target.transform.position - transform.position; return HaveReached(dir); }

Side 67 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

// Below are the public functions which are overloaded to call the private move() function // this is to ensure that whatever type is given it can be converted to a point the move() function understands. public bool Reached(Transform target) { Vector3 dir = target.position - transform.position; return HaveReached(dir); }

public void MoveTo(GameObject point) { Vector3 direction = point.transform.position - transform.position; Move(direction); }

public void MoveTo(Transform point) { Vector3 direction = point.position - transform.position; Move(direction); } }

Side 68 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Overlord.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this script is to control the behavior of the overlord * This includes building a list of instantiated minions and pass it to the spawner * Change the stats and paths of the minions before spawn * receive reports from returning minions */ public class Overlord : MonoBehaviour { private List towerListPath1; private List towerListPath2; private List towerListPath3; public bool printList; public DropDown whichPathToTest;

[Header("Setup")] public GameObject spawner; public bool test; public bool spawnNextWave;

[Header("Minions")] public GameObject knight; public GameObject archer; public GameObject mage; public int maxResistPoints;

[Header("Paths")] public GameObject path1; public GameObject path2; public GameObject path3;

[Header("Wave Stats")] public int waveSize; public int minionsCount; public float timeBetweenWaves; public float WaveCountDown;

[Header("Read Only")] public int waveNumber;

private float dpmFireRatio = 0.25f;

Side 69 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

private float dpmWaterRatio = 0.25f; private float dpmLightningRatio = 0.25f; private float dpmPhysicalRatio = 0.25f;

public enum DropDown { //the different paths to choose from in a dropdown menu in unity inspector path1, path2, path3 };

void Start() { towerListPath1 = new List(); towerListPath2 = new List(); towerListPath3 = new List(); }

void Update() { if (minionsCount < 1) { minionsCount = 0; WaveCountDown -= Time.deltaTime;

if (WaveCountDown <= 0) { WaveCountDown = timeBetweenWaves; spawnNextWave = true; waveNumber++; } }

if (spawnNextWave == true) { // this calls the SpawnWave function in the MinionWaves Script // where it add the list from generateListToSpawn to the constructur GetComponent().SpawnWave(AddMinionToSpawnList(waveSize)); spawnNextWave = false; }

// test for printing objects of list into console depending on which path you wanna know about if (printList == true) { if (whichPathToTest.ToString() == "path1") { PrintListDebug(towerListPath1); CalculateDamageRatios(towerListPath1);

Side 70 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

}

if (whichPathToTest.ToString() == "path2") { PrintListDebug(towerListPath2); CalculateDamageRatios(towerListPath2); }

if (whichPathToTest.ToString() == "path3") { PrintListDebug(towerListPath3); CalculateDamageRatios(towerListPath3); } } }

// generate a list of random minions with random paths private List AddMinionToSpawnList(int waveSize) { List list = new List();

for (int i = 0; i < waveSize; i++) { //Instantiate a to change it stats from the prefab without interfering with other instantiated minions, and lastly disable it GameObject minion = GameObject.Instantiate(RandomMinion()); minion.SetActive(false);

// set path to minion GameObject path = RandomPath(); minion.GetComponent().SetPathList(path);

// add more health depending on which wave the game is at float minionHealth = minion.GetComponent().health + waveNumber; minion.GetComponent().health = minionHealth;

// add a resist profil to the minion if (path == path1) { CalculateDamageRatios(towerListPath1); } else if (path == path2) { CalculateDamageRatios(towerListPath1); } else if (path == path3) { CalculateDamageRatios(towerListPath1);

Side 71 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

}

minion = ApplyResistanceProfil(minion);

// add minion to the listToSpawn list.Add(minion); } return list; }

// this will take a minion, generate and add a resistProfile to the minion, and lastely return the minion with the new stats private GameObject ApplyResistanceProfil(GameObject minion) { int physical = 10 + Mathf.RoundToInt(dpmPhysicalRatio); int fire = 10 + Mathf.RoundToInt(dpmFireRatio); int water = 10 + Mathf.RoundToInt(dpmWaterRatio); int lightning = 10 + Mathf.RoundToInt(dpmLightningRatio);

minion.GetComponent().SetResistanceProfil(physical, fire, water, lightning); return minion; }

// function to add information about reporting minions to the storage lists public void ReceiveSpotList(List list, GameObject path) { for (int i = 0; i < list.Count; i++) { if (path == path1 && !towerListPath1.Contains(list[i])) { towerListPath1.Add(list[i]); } else if (path == path2 && !towerListPath1.Contains(list[i])) { towerListPath2.Add(list[i]); } else if (path == path3 && !towerListPath1.Contains(list[i])) { towerListPath3.Add(list[i]); } } }

// return a random minion to caller private GameObject RandomMinion() { switch (Random.Range(0, 3)) {

Side 72 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

case 0: return knight; case 1: return archer; case 2: return mage; } return knight; //Default fallover }

// return a random path to caller private GameObject RandomPath() { switch (Random.Range(0, 3)) { case 0: return path1; case 1: return path2; case 2: return path3; } return path3; //Default fallover }

// 2 functions to increase or decrease minion count public void IncreaseMinionCount() { this.minionsCount++; }

public void DecreaseMinionCount() { this.minionsCount--; }

// this function evaluete the ratio of damage of the specific damage types from a given path void CalculateDamageRatios(List path) { float dpmFire = 0.0f; float dpmWater = 0.0f; float dpmLightning = 0.0f; float dpmPhysical = 0.0f; float dpmTotal = 0.0f;

for (int i = 0; i < path.Count; i++) { dpmTotal = dpmTotal + (float)path[i].GetComponent().getDamagePerMinut(); switch (path[i].GetComponent().getDamageType().ToString()) { case "fire": dpmFire = dpmFire + (float)path[i].GetComponent().getDamagePerMinut(); break;

Side 73 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

case "water": dpmWater = dpmWater + (float)path[i].GetComponent().getDamagePerMinut(); break;

case "lightning": dpmLightning = dpmLightning + (float)path[i].GetComponent().getDamagePerMinut(); break;

case "physical": dpmPhysical = dpmPhysical + (float)path[i].GetComponent().getDamagePerMinut(); break; } }

if (dpmTotal != 0) { dpmFireRatio = dpmFire / dpmTotal * maxResistPoints; dpmWaterRatio = dpmWater / dpmTotal * maxResistPoints; dpmLightningRatio = dpmLightning / dpmTotal * maxResistPoints; dpmPhysicalRatio = dpmPhysical / dpmTotal * maxResistPoints; }

// output results in console Debug.Log("Fire dmg: " + dpmFire); Debug.Log("Water dmg: " + dpmWater); Debug.Log("Lightning dmg: " + dpmLightning); Debug.Log("Physical dmg: " + dpmPhysical); Debug.Log("Total dmg: " + dpmTotal); }

// this is a function to print the list of spotted towers to console depending on which path that is selected in unity void PrintListDebug(List path) { for (int i = 0; i < path.Count; i++) { Debug.Log(path[i].name); } printList = false; } }

Side 74 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: PathFinding.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this class is to enable a minion to move along a path, this is implemented by following a list of positions the minion will have to move to. */ public class PathFinding : MonoBehaviour { GameObject overlord; Movement movement; Intel intel; bool isReportIntel;

[Header("Pathing")] public GameObject pathList; List path; public int pathIndex;

void Start() { overlord = GameObject.FindGameObjectWithTag("Overlord"); movement = GetComponent(); intel = GetComponent(); path = pathList.gameObject.GetComponent().getPathList(); }

void Update() { isReportIntel = intel.reportIntel; //get if Intel script want to report to overlord (return back the path)

// lower the pathIndex until at 0 for moving to spawner if (movement.Reached(path[pathIndex]) && isReportIntel == true && pathIndex != 0) pathIndex--;

// if reporting so early that you haven't left spawner, this destroy gameobject sincec a collider will not happen if (movement.Reached(path[0]) && isReportIntel == true) Destroy(gameObject);

// let you change direction of movement between 2 pathpoints if (isReportIntel) { movement.MoveTo(path[pathIndex]); }

Side 75 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

else { movement.MoveTo(path[pathIndex + 1]); } }

void OnTriggerEnter(Collider Other) { // control the pathIndex depending on if moving towards base or spawner if (Other.tag == "PathPoint" && isReportIntel == false) { pathIndex++; } else if (Other.tag == "PathPoint" && isReportIntel == true) { pathIndex--; } else if (Other.tag == "EnemySpawn" && isReportIntel == true) { overlord.GetComponent().ReceiveSpotList(intel.GetSpottedList(), pathList); overlord.GetComponent().DecreaseMinionCount(); Destroy(gameObject); } else if (Other.tag == "Base") { Other.GetComponent().ReduceHealth(1); overlord.GetComponent().DecreaseMinionCount(); Destroy(gameObject); } }

// setters and getter public void SetPathList(GameObject pathList) { this.pathList = pathList; }

public void SetPathList(List pathList) { this.path = pathList; }

public GameObject GetPathList() { return this.pathList; } }

Side 76 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: PathPointList.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this path is to make a template where we can add gameobjects to, and generate a list a minion can follow. * in short this contains the route the minion have to follow */ public class PathPointList : MonoBehaviour { public GameObject pathPoint0; public GameObject pathPoint1; public GameObject pathPoint2; public GameObject pathPoint3; public GameObject pathPoint4; public GameObject pathPoint5; public GameObject pathPoint6; public GameObject pathPoint7; public GameObject pathPoint8; public GameObject pathPoint9;

public List path = new List();

public List getPathList() { return path; }

//adds all the points to the list if it isn't null void listAdder(GameObject point) { if (point != null) { path.Add(point); } }

// Use this for initialization void Start() { listAdder(pathPoint0); listAdder(pathPoint1); listAdder(pathPoint2);

Side 77 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

listAdder(pathPoint3); listAdder(pathPoint4); listAdder(pathPoint5); listAdder(pathPoint6); listAdder(pathPoint7); listAdder(pathPoint8); listAdder(pathPoint9); } }

Side 78 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Projectile.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this clas is to different some behaivours to our projectiles fired from the towers */ public class Projectile : MonoBehaviour { private GameObject target; private float speed; public int damageAmount; // read only public string damageType; // read only

void Update() { Movement movement = GetComponent(); movement.SetSpeed(speed); gotTarget();

// checks if the projectile will hit the target in the next frame, if true it will damage the target and despawn. if (gotTarget() == true) { if (movement.Reached(target)) Damage(target); movement.MoveTo(target); } else { Destroy(this.gameObject); } }

// check if the target is still alive else it despawns the projectile. bool gotTarget() { if (target == null) { return false; } else { return true;

Side 79 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

} }

// damage target when hitting it void Damage(GameObject target) { target.GetComponent().TakeDamage(damageAmount, damageType, target.tag); Destroy(this.gameObject); }

//setters public void setTarget(GameObject target) { this.target = target; }

public void setSpeed(float speed) { this.speed = speed; }

public void setDamageAmount(int damageAmount) { this.damageAmount = damageAmount; }

public void setType(string damageType) { this.damageType = damageType; } }

Side 80 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Spawner.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this script is to enable the spawner to activet minions into the scene */ public class Spawner : MonoBehaviour { // this instantiate the minion into the unity scene private void SpawnMinion(GameObject minion, Vector3 spawnPoint) { minion.transform.position = spawnPoint; minion.SetActive(true); }

// here are 2 overloaded methods to pass this script a minion to spawn public void Spawn(GameObject minionType, GameObject path) { Vector3 vector = transform.position; minionType.GetComponent().SetPathList(path); SpawnMinion(minionType, vector); }

public void Spawn(GameObject minionType) { Vector3 vector = transform.position; SpawnMinion(minionType, vector); }

// below is variables and Update function to test spawning [Header("testing Settings")] public bool testing; private float time; public float periode = 2; public GameObject testPath; public GameObject testMinion;

void Update() { if (time > periode && testing == true) { time = 0; Spawn(testMinion, testPath);

Side 81 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

} time = time + Time.deltaTime; } }

Side 82 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: StatsUpgrade.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this class is to add the feature to upgrade the stats of the towers, * it has predefined stats which it adds to the tower when upgradeStats() is called */ public class StatsUpgrade : MonoBehaviour { Tower towerStats; public int maxLevel; public int upgradeLevel; public float upgradeRange; public int upgradeDamage; public bool testUpgrade; // adds a button to unity inspector to test if upgrading stats works

private void Start() { towerStats = GetComponent(); }

private void Update() { //the test button, which add a level per click if (testUpgrade == true) { upgradeStats(); testUpgrade = false; } }

public void upgradeStats() { if (upgradeLevel < maxLevel) { upgradeLevel++; upDamage(); upRange(); } }

// unused functions, which is made to upgrade specific stats. private void upDamage()

Side 83 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

{ int damage; damage = towerStats.getDamage() + upgradeDamage; towerStats.setDamage(damage); }

private void upRange() { float range; range = towerStats.getRange() + upgradeRange; towerStats.setRange(range); } }

Side 84 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Targeting.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * the purpose of this class is to find a object with a given tag (set in unity) inside a given range */ public class Targeting : MonoBehaviour { public string targetTag;

public GameObject FindTarget(float range) { GameObject nearestTarget = null; //makes a list of all known enemies GameObject[] allEnemies = GameObject.FindGameObjectsWithTag(targetTag); float shortestDistance = Mathf.Infinity;

foreach (GameObject target in allEnemies) { float distanceToEnemy = Vector3.Distance(transform.position, target.transform.position);

if (distanceToEnemy < shortestDistance) { shortestDistance = distanceToEnemy; nearestTarget = target; //.GetComponent().hitbox; } }

// return the targen when it has a target and it is inside the given range if (nearestTarget != null && shortestDistance <= range) { return nearestTarget as GameObject; } else { return null; } } }

Side 85 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Tower.cs using System.Collections; using System.Collections.Generic; using UnityEngine;

/* * The purpose of this class is to add behaviours to the towers */ public class Tower : MonoBehaviour {

private GameObject target;

[Header("Attributes")] public float range; public float roundsPerMinut; private float reloadTime; public float reloadProgress = 0f;

[Header("Projectile Settings")] public float speed; public int damageAmount; public DropDown damageType;

[Header("Setup")] // public string enemyTag = "Enemy"; public Transform towerRotation; public GameObject projectilePrefab; public Transform firePoint;

public enum DropDown { //the different damageTypes the bullet can deal, in a list in unity physical, fire, water, lightning };

void Start() { reloadTime = 60.0f / roundsPerMinut; }

// loop betweening looking for a target, aiming and shooting if it got a target. void Update() {

Side 86 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

target = GetComponent().FindTarget(range); if (target == null) return; Aim(target); if (reloading() == false) { Shoot(target); reloadProgress = 0; } }

// deplay between shots bool reloading() { if (reloadTime >= reloadProgress) { reloadProgress += Time.deltaTime; return true; } else { return false; } }

// the tower is rendered as aiming at the target void Aim(GameObject target) { Vector3 dir = target.transform.position - transform.position; Quaternion targetRotation = Quaternion.LookRotation(dir); Vector3 rotation = targetRotation.eulerAngles; towerRotation.rotation = Quaternion.Euler(0f, rotation.y, 0f); }

// shoot the given target, and set the instatianted projectile's stats void Shoot(GameObject target) { GameObject bullet = (GameObject)Instantiate(projectilePrefab, firePoint.position, firePoint.rotation); bullet.GetComponent().setTarget(target); bullet.GetComponent().setSpeed(speed); bullet.GetComponent().setDamageAmount(damageAmount); bullet.GetComponent().setType(damageType.ToString());

}

// draw range in unity private void OnDrawGizmosSelected()

Side 87 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

{ Gizmos.DrawWireSphere(transform.position, range); }

//Getters and setters public GameObject GetTarget() { return target; }

public void setDamage(int damage) { this.damageAmount = damage; }

public void setRange(float range) { this.range = range; }

public float getRange() { return this.range; }

public int getDamage() { return this.damageAmount; }

public string getDamageType() { return damageType.ToString(); }

public float getDamagePerMinut() { float value; value = (float)this.damageAmount * this.roundsPerMinut; return value; } }

Side 88 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Hand Menu/BuildingMenu.cs using UnityEngine; using System.Collections; using Valve.VR; public class BuildingMenu : MonoBehaviour {

Vector3 BenjaminPoints = new Vector3(); SteamVR_TrackedObject obj; // finding the controller [Header("Setup")] public int itemID; public GameObject buttonHolder; //empty object that contains the buttons public GameObject Hand; public GameObject TowerFoundation; public GameObject BuildingBall; public GameObject UpgradeObject;

[Header("Debug")] public bool buttonEnabled; // saying whether or the empty object is enabled

// Public booleans, need to be accesed in other scripts public bool holding; public bool holdingFoundation; public bool holdingBall; public bool holdingUpgrade; public bool validBuildVec; Vector3 buildVec; RaycastHit hit;

void Start() { obj = GetComponent(); buttonHolder.SetActive(false); buttonEnabled = false; holdingFoundation = false; holdingBall = false; holding = false; holdingUpgrade = false; validBuildVec = false; } // Start end

void Update() { MenuOpen(); CheckItemID();

Side 89 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

PlaceObject(); } // Update end

void MenuOpen() {

var device = SteamVR_Controller.Input(4); //you are getting the device and setting it here if (device.GetPressDown(SteamVR_Controller.ButtonMask.ApplicationMenu)) //When you press the button above the Dpad you will do this function { Debug.Log("Buttons should be enabled"); if (buttonEnabled == false) { buttonHolder.SetActive(true); buttonEnabled = true; } else if (buttonEnabled == true) { buttonHolder.SetActive(false); buttonEnabled = false; }

} }// MenuOpen end

void CheckItemID() { var device = SteamVR_Controller.Input(4); if (device.GetTouchDown(SteamVR_Controller.ButtonMask.Axis0)) { itemID++; Debug.Log(itemID); } else if (device.GetTouchDown(SteamVR_Controller.ButtonMask.Axis1)) { itemID--; Debug.Log(itemID); } }

void PlaceObject() {

var device = SteamVR_Controller.Input(3);

if (holdingFoundation || holdingBall || holdingUpgrade) { Hand.GetComponent().pointer.active = true;

Side 90 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

var ray = new Ray(Hand.transform.position, Hand.transform.forward);

if (Physics.Raycast(Hand.transform.position, Hand.transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity)) { validBuildVec = true;

Hand.GetComponent().pointer.GetComponent().material.colo r = Color.green; } else { validBuildVec = false;

Hand.GetComponent().pointer.GetComponent().material.colo r = Color.red; } } else { Hand.GetComponent().pointer.active = false; }

if (holdingFoundation) { if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger)) { if (validBuildVec) { buildVec = hit.point; } } if (device.GetPressUp(SteamVR_Controller.ButtonMask.Trigger)) { Instantiate(TowerFoundation, buildVec, Quaternion.identity);

holdingFoundation = false; holding = false; }

} if (holdingBall) { if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger)) { if (validBuildVec) {

Side 91 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

buildVec = hit.point; } }

if (device.GetPressUp(SteamVR_Controller.ButtonMask.Trigger)) { if (validBuildVec) { Instantiate(BuildingBall, buildVec, Hand.transform.rotation);

holdingBall = false; holding = false; } } } if (holdingUpgrade) {

if (device.GetPressDown(SteamVR_Controller.ButtonMask.Trigger)) { if (validBuildVec) { buildVec = hit.point; } } if (device.GetPressUp(SteamVR_Controller.ButtonMask.Trigger)) { if (validBuildVec) { Instantiate(UpgradeObject, buildVec, Hand.transform.rotation);

holdingUpgrade = false; holding = false; } }

} } // PlaceObjecft end

} // BuildingMenu end

Side 92 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Hand Menu/ButtonHandler.cs - mangler script using System.Collections; using System.Collections.Generic; using UnityEngine; public class ButtonHandler : MonoBehaviour { [Header("Setup")] public bool button1; public bool button2; public bool button3; public GameObject Hand; BuildingMenu buildingMenu;

void OnTriggerEnter(Collider Other) { if (Other.tag == "VRHand2") { buildingMenu = Hand.GetComponent();

if (button1 && !buildingMenu.holding) { buildingMenu.holdingFoundation = true; buildingMenu.holding = true; } if (button2 && !buildingMenu.holding) { buildingMenu.holdingBall = true; buildingMenu.holding = true; } if (button3 && !buildingMenu.holding) { buildingMenu.holdingUpgrade = true; buildingMenu.holding = true; } } }// OnTriggerEnter end }// ButtonHandler end

Side 93 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Tower Building/TowerCreation.cs - mangler script using System.Collections; using System.Collections.Generic; using UnityEngine; public class TowerCreation : MonoBehaviour {

public GameObject theFirstTower; Transform creatPos;

void createStructure() {

Instantiate(theFirstTower, creatPos.position, Quaternion.identity);

}

void OnCollisionEnter(Collision matCol) { if (matCol.gameObject.tag == "buildMaterial") { creatPos = matCol.gameObject.transform; createStructure(); Destroy(matCol.gameObject); Destroy(gameObject); } } }

Side 94 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Tower Building/TowerTypeUpgrade.cs - mangler script using System.Collections; using System.Collections.Generic; using UnityEngine; public class TowerTypeUpgrade : MonoBehaviour {

public string type;

public GameObject TowerFire; public GameObject TowerWater; public GameObject TowerLightning; public GameObject TowerPhysical;

// Upgrades tower to the specific tower type private void OnCollisionEnter(Collision Other) { if (Other.gameObject.tag == "BasicTower") {

Vector3 vec = Other.gameObject.transform.position; Destroy(Other.gameObject); Destroy(gameObject);

switch (type) {

case "Fire":

Instantiate(TowerFire, vec, Quaternion.identity);

break;

case "Water":

Instantiate(TowerWater, vec, Quaternion.identity);

break;

case "Lightning":

Instantiate(TowerLightning, vec, Quaternion.identity);

break;

Side 95 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

case "Physical":

Instantiate(TowerPhysical, vec, Quaternion.identity);

break;

} } } }

Side 96 af 97 Benjamin Jul Westermann, Niels Georg Vendelø, Lasse Bjerg Christiansen, Mads Zeuch Ethelberg

Bilag: Tower Building/Upgrade.cs - mangler script using System.Collections; using System.Collections.Generic; using UnityEngine; public class Upgrade : MonoBehaviour {

// Use this for initialization void Start () {

}

// Update is called once per frame void Update () {

}

void OnCollisionEnter(Collision Other) {

if (Other.gameObject.tag == "TowerUpgradeAble") {

StatsUpgrade Upgrading = Other.gameObject.GetComponent(); Upgrading.upgradeStats(); Destroy(gameObject);

} } }

Side 97 af 97