Unitytorial: Lær Unity, mekk spill

Yetipants

Mein Gampf
Medlem av ledelsen
#1
Er du klar for Unitytorial? Jeg tenkte det kunne være artig med en liten innføring i det som offesielt er det enkleste verktøyet for å mekke 3D-spill, nemlig Unity. Mål for dagen:
  • Å lage et svært enkelt førstepersonsskytespill med simulerte omgivelser, en spillerfigur og enkle grafiske effekter.
  • Lære om Unity-grensesnittet og arbeidsflyten i Unity.
  • Lære litt om hvordan man kan legge til sine egne programmeringsscripts i Unity.
  • Lære om prefabs, som er en svært viktig funksjon i Unity.
Dine forutsetninger trenger ikke være mer enn grunnleggende datainteresse, vilje til å lære om spillmekking, og en relativt ny PC (pekepinn: Min to og et halvt år gamle laptop med integrert Intel-grafikk klarer å dra Unity uten nevneverdige problemer). Noen ting kommer jeg til å forklare relativt grundig, andre ting må du finne ut av selv, siden det er rimelig nytteløst å forklare og de er enkle å forstå ved å prøve seg fram. Ikke vær redd for å eksperimentere med helt andre ting enn det jeg skriver ned, men pass på å lagre først (BÅDE "Save project" og "Save Scene", forøvrig), sånn at du kan gå tilbake hvis du klarer å skru deg selv helt i pompen.

Faktisk... du kommer ikke så fryktelig langt her uten å prøve deg fram litt. Hvis det er noe du virkelig ikke forstår, så si fra sånn at jeg kan svare.

Før du begynner: Last ned og installer Unity. Skulle skrevet guide til det også, men ærlig talt: Hvis du ikke klarer det selv, spørs det hvor mye nytte du får av selve Unity.

En liten notis til: Jeg er ingen god programmerer, men jeg liker å dille med det uansett. Programmering er like mye et håndverk som en ferdighet; så lenge du driver nok med det, finner du gjerne dine egne måter å gjøre ting på, eller nye innfallsvinkler på gamle problemstillinger. Jeg forklarer ikke så mye av hvordan Unity fungerer under panseret (sjekk heller ut scripting-referansen på nettsida hvis du er interessert (bonus: Den referansen er innimellom rimelig utdatert, så kodeeksemplene ikke funker i nyeste Unity. Kos deg med det)), men jeg skal prøve å forklare konkret hva jeg gjør underveis uten at jeg går veldig inn i syntaks og sånt. Prøv å henge med, og hvis du føler du trenger å lære mer programmering, sjekk ut Codecademy eller andre ressurser.

Så, da er vi klare. Smell i gang Unity og så kjører vi.

Først av alt: Start et nytt prosjekt. Unity laster inn et demo-prosjekt første gang du kjører det, men vi skal starte helt fra bånn av så derfor...

Lagre prosjektet akkurat hvor du vil. Unity bruker en mappestruktur hvor selve prosjektet ditt er hele mappa du lager på harddisken, og alle filer du legger til i prosjektet legges på et passende sted i mappa. Ikke bruk Utforsker (eller hva det heter for noe fint på Mac) til å organisere denne mappa; Unity liker å gjøre det selv, så bruk utforskeren i selve Unity. En viktig ting når du mekker prosjektet ditt: Huk av på "Character Controller.unityPackage" under "Import the following packages". Vi skal bruke FPS-kontrolleren i denne pakka, og da er det kjekt å ha den med fra start. Ikke at du ikke kan legge den til seinere, men hvis du haker av her, slipper du det.

Etter litt jobbing får du opp selve Unity-vinduet. Hvis du har holdt på en liten stund, kan det se ut omtrent som dette:

Forvirrende? Frykt ikke. Her følger en kjapp gjennomgang.
  • Scene - Det store vinduet øverst til venstre. Dette er en forhåndstitt på den aktuelle scenen du har lastet inn for øyeblikket. Hvert Unity-program består av en eller flere scener, som igjen inneholder spillobjekter og annet dilldall. Du kan ikke ha et Unity-prosjekt uten en scene og et kamera som viser fram en del av scena, derfor er en tom scene og et standard-kamera de eneste objektene som lages automatisk i et tomt Unity-prosjekt.
  • Project - den lille lista nede til høyre under scenevinduet. Dette er alle objektene spillet ditt inneholder. Må ikke forveksles med:
  • Hierarchy - den lille lista nede til venstre under scenevinduet. Dette er hierarkiet for den gjeldende scena, altså alle objektene som finnes i scena du har lastet inn for øyeblikket. Arbeidsflyten i Unity er slik at du enten lager en ny Asset, GameObject eller Component (disse begrepene omfatter alle slags objekter du kan bruke i et Unity-prosjekt), og plasserer det i Project-lista. Deretter kan du dra objektet derfra og over i scena eller hierarkiet for å plassere det i den gjeldende scena. Forskjellene på Assets, Components, GameObjects og hurramegrundt er ikke så fryktelig nøye akkurat nå, vi tar det etter hvert.
  • Inspector - sist men ikke minst, den (ofte) lange lista helt til høyre i vinduet. Denne lista viser alle Components som er tilknyttet det GameObject-et du har valgt i enten hierarkiet eller prosjektutforskeren.
Forvirrende? La meg forklare litt. I bildet over, kan du se at jeg har valgt en First Person Controller i hierarkiet nede til venstre. Dermed viser Inspector hvilke dingsebomser den består av: En Transform, en Character Controller, og tre Scriptsved navn Mouse Look, Character Motor ooog FPSInput Controller. En First Person Controller er en Standard Asset, som ble importert da jeg valgte Character Controller.unityPackage da jeg lagde prosjektet mitt.

Husk at hvert objekt i Unity kan tilknyttes flere ulike komponenter; i dette tilfellet de komponentene jeg listet opp tidligere. En Transform er bare et navn på alt som har med visning av et objekt i 3D-framstillingen å gjøre, det vil si selve 3D-modellen, rotasjon, skalering, posisjon og ymse annet. En Character Controller er - som navnet antyder - et standard Unity-objekt som lar spilleren styre en figur (som muligens også er en karakter, det er opp til deg). Skriptene er små snutter med programmeringskode som legger til bestemte funksjoner på objektet. I dette tilfellet ser du av navnene hva de ulike skriptene gjør, eller hva? Uansett, vi skal snart komme i gang med å skrive våre egne skript, jeg ville bare vise fram hvordan ting kan se ut etter en stund. Hvis du ikke skjønner bæret, prøv å følge med likevel, så forstår du forhåpentligvis mer underveis.

Vi går tilbake til vårt tomme prosjekt.

Som du ser, har vi bare den tomme scena (den er ikke lagret ennå, derfor finner du den ikke i utforskeren) og et Main Camera akkurat nå, i tillegg til Standard Assets-mappa. Før vi mekker en spillfigur, trenger vi et landskap figuren kan bevege seg rundt i. På menylinja øverst, velger du Terrain, og så Create Terrain. Legg merke til at terrenget legges til i hierarkiet og utforskeren, samt at det dukker opp i Inspector-vinduet øverst til høyre. Sjekk at terrenget er plassert på koordinatene 0,0,0 (altså: X = 0, Y = 0, Z = 0), at rotasjonen er det samme (0,0,0) og at skaleringen er 1,1,1. Rett opp verdiene i Inspector hvis de er feil.

Nå kan det hende at kameraet ikke er ideelt plassert. For å flytte på det, kan du - hold deg fast - holde inne høyre mustast i scenevisningen, og bruke WASD og musa for å flytte det rundt selv (hold inne Shift for at det ikke skal gå dritsakte). Sos rundt med kameraet til du har fin utsikt over terrenget ditt, eventuelt til du har fått nok.

Vel, nok tull: På tide å gjøre dette til et ordentlig spill. Et rimelig dølt spill, men samme det. Velg Standard Assets i utforskeren, utvid mappene nedover til du har åpnet mappa Character Controllers og kan velge First Person Controller. Klikk og dra den over i enten hierarkiet, eller scenevisningen.

For enkelhets skyld, pass på at du har valgt First Person Controller i hierarkiet, og sjekk Inspector-vinduet til høyre. Under "Position", endre verdiene slik at objektet er plassert på koordinatene X = 10, Y = 10, Z = 10. Og nå... trykk på Play-knappen midt i vinduet, øverst under menylinja. Jaggu! Bruker du WASD og musa nå, styrer du din helt egne spillfigur rundt i det megaspennende 3D-miljøet du selv har laget.

Ikke så fryktelig spennende, sier du? Makan til klaging. Uansett, vi får vel ordne litt på det, da. Velg Terrain i hierarkiet, og trykk på Terrain-menyen øverst. Velg Set Resolution, og sett både bredden og lengden på terrenget til 200 (standardverdien er 2000). Terrenget blir mye mindre da, og da er det litt lettere å håndtere til å begynne med. Bare overse de andre innstillingene (eller lek litt med dem hvis du skjønner hva de gjør). Så, med et litt mindre terreng, er det bare å... forme det.

Velg terrenget i hierarkiet hvis det ikke allerede er valgt, og trykk på knappen helt til venstre i Inspector (Raise/lower terrain). Velg en pensel og tegn og kluss litt på terrenget for å se hva som skjer (hint: høyden på terrenget endrer seg).

One does not simply walk into Mordor. Ahem. Uansett, det kan hende at FPS-kontrolleren din blir plassert under terrenget mens du går berserk med penselen. Velg den i hierarkiet og plasser den noen meter opp i lufta for å være sikker. Trykk nå for all del på Play igjen sånn at du kan bli mektig imponert over hvor sabla god du er til å designe spillverdener. HAHA kødda alt blir grått hvis du gjør det. Det er fordi scena di ikke har noe lys, og ingen teksturer. Best at vi fikser det, da.

Pass på at du IKKE har valgt terrenget, og klikk på Create i hierarkiet, så Directional Light. Nå har du akkurat lagt til en ny komponent i scena di, nemlig en lyskilde. Sjekk i Inspector at den vises i lista. Det er vanligvis ikke spesielt lurt å legge til lyskilder direkte på terrenget (punktlys må, naturlig nok, være egne objekter som kan plasseres rundt i scena istedenfor å være knyttet til et objekt), blant annet fordi du ikke kan endre plassering eller rotasjon på lyset. Gjør det nå, slik at du får det sånn du vil ha det (protip plasseringa har ingenting å si for retningsbaserte lys).

I Unity har du fire forskjellige lystyper. Du har Spot, som forhåpentligvis er selvforklarende; Point, som er omtrent som ei lyspære - altså, et enkelt lyspunkt som stråler lys i alle retninger; Directional, som brukes for å simulere retningsbasert lys som dekker hele scena likt; sollys, med andre ord. Eller månelys, da; og Area, som... vel, det er innbakte lys på brettgeometri, hvis det sier deg noe. Hvis ikke, ta det med ro: Områdelys krever Unity Pro siden du bare har tilgang til grunnleggende lysshadere i gratisversjonen av Unity. Uansett, velg lyset du akkurat lagde i Inspector, og endre innstillingene litt til du har et retningsbasert lys som ser koselig ut. Hvis en innstilling virker pussig, prøv deg fram eller bare overse den, du bestemmer.

Tid for litt dekor på selve terrenget. Åpne bilderedigeringsprogrammet du liker aller best, og lag noen fine teksturer til terrenget ditt (eller bare stjæl mine). En huskeregel er å bruke noenlunde kvadratiske teksturer - iallfall på terreng - og at toerpotenser er det du går for når du skal angi størrelse på bildet ditt. Et greit kompromiss mellom detaljnivå og minnebruk er 512x512, som jeg bruker. Unngå å lage alt for store teksturer til spillet ditt, med mindre du har uendelig mye minne på skjermkortet ditt (spoiler det har du ikke).

Sånn. Dra dem inn i utforskeren i Unity, så vil du se at de dukker opp der ganske umiddelbart. Siden det fort blir rotete at alt bare ligger og slenger i rotmappa, lag en egen mappe og sleng teksturene inn der.

Så, på tide å legge litt farge på terrenget. Velg terrenget i hierarkiet igjen, og i Inspector velger du malekost-knappen som heter "Paint the terrain texture". Før du kan male, må du velge teksturene dine ved å trykke på "Edit textures...". Legg til teksturene dine i tur og orden, så kan du male i vei ved å bytte mellom dem.

Prøv deg fram!

Majestetisk. Nåh, terrenget er så klart vel og bra, men hva med bygninger? Landsbyer? Hus? Jo, det må vi ha. Eller, ett hus iallfall, det tar pisslang tid å mekke hus. Ett får holde. Vi jukser litt, og lager et hus ved hjelp av kube-verktøyet i Unity (det går også helt fint an å modellere 3D-objekter med et 3D-program og importere dem, det fikser Unity automatisk). Velg et sted huset skal stå, og legg et fundament: En kube du forstørrer (enten via Inspector eller skaleringsverktøyet øverst til venstre) sånn at du har en fin, flat... flate å bygge på. Velg GameObject-menyen, Create Other, og Cube. Det tar litt tid å bli vant med 3D-editoren i Unity, og dette er et typisk tidspunkt hvor det slår inn. For hvor i fasiken ble det av den kuben? Sjekk i Inspector, finn koordinatene, og se om du kan skimte kuben i scenevinduet (den blir plassert en viss distanse foran kameraet ditt, så muligens under terrenget). Flytt den oppover, juks den litt fram og tilbake, til du får den der du vil ha den. Gjør den større. Kos deg.

Kuber (eller, altså, 3D-modeller), vettu, de ække terrenger vettu. De bruker ikke bare teksturer, de bruker materialer. Materialer KAN også inneholde teksturer, men... andre ting også. Spennende! Her har jeg laget en ny mappe (høyreklikk i utforsker), og et nytt material (også høyreklikk, se om du klarer å finne ut hvordan du mekker et material), og lagt det på kuben min (klikk og dra). Detta ække vanskelig, folkens. Siden du er så tøff, kan du til og med prøve deg på å redigere materialet i Inspector. Se hvilke effekter du kan få til (hint: du må prøve lista som heter Shader)!

Nå skal du få prøve deg fram selv. Mekk noen greier du kan hoppe rundt på. Et hus? Noen kasser? Pornografi? Du bestemmer. Legg til nye kuber (eller til og med sfærer, eller andre ting), og lag noen fine omgivelser. Se hva du får til.

NYDELIG. Nei, det ble ikke et hus, jeg veit. Gadd ikke. Uansett, hva mangler vi nå? Jo, kanskje det viktigste av alt: Drap. Et FPS uten skyting blir ikke mye tess. Da er det tid for litt proggings. Men! Bare rolig, hvis du synes dette er skummelt, bare heng med så kanskje du skjønner mer etter hvert.

Men først, en viktig ting: Prefabs. Unity har et konsept som kalles prefabs, altså ferdiglagde objekter, klare til bruk og - ikke minst - masseproduksjon. Tenk etter: Når du skal skyte kulene dine, så håndlager du ikke en og en kule, eller hva? Nei, du lager en kule, og så bare ber du om en ny av den når du skal ha flere. Akkurat som i virkeligheten. Prefabs kan kobles til dine egne scripts, og så bare mekker Unity dem automatisk for deg. Dette blir spennende, heng med.

Lag et nytt script (nå forteller jeg deg ikke hvordan du skal gjøre det, sånn at du må finne det ut selv). Pass på! Jeg bruker C#-scripts, men det går også an å bruke JavaScript eller noe som heter Boo som jeg ikke kjenner til. Det er ikke VELDIG store forskjeller på JavaScript og C#, og de er egentlig ganske kjedelige med mindre du er glad i programmering, så jeg hopper over å fortelle deg dem. Bare pass på at du enten gjør om scriptene mine til JavaScript/Boo hvis du heller vil bruke det, eller at du har valgt C# når du programmerer (protip: Det går an å bruke flere forskjellige typer scripts i samme spill uten at Unity klager).

Uansett, lag scriptet, kall det "Bullet Shooter" eller noe annet kreativt, og lag gjerne en egen mappe til det sånn at det blir fornøyd. Dobbelklikk på det for å redigere det. Obs! Unity bruker MonoDevelop, et gratis koderedigeringsprogram til å redigere koden. MonoDevelop er helt greit, men det er bare halvveis integrert med Unity. Rediger scriptet ditt, hvis du får noen røde feillinjer må du rette opp i noe. Så lagrer du, og så kjører du spillet ditt i Unity for å sjekke hva det gjorde. Nå skal jeg late som om jeg kan å programmere her, så følg med. Når du åpner scriptet, får du dette:
Kode:
using UnityEngine;
using System.Collections;
 
public class BulletShooter : MonoBehaviour {
 
    // Use this for initialization
    void Start () {
 
    }
 
    // Update is called once per frame
    void Update () {
 
    }
}
Hvis du ikke skjønner noe av dette, bare blås i det. Du lærer det etter hvert. Uansett: Koden i Start() kjøres når objektet først legges til i spillet (altså, ved starten av scena hvis det er plassert i scena, eller når det lastes inn av koden din). Vi bruker ikke Start() akkurat nå. Det vi skal gjøre, er å legge til kode som sjekker om venstre mustast blir trykket ned, og hvis så er tilfelle, skal spillet skyte en kule i den retningen kameraet er vendt. Vi begynner med å gjøre klar kula, og her kommer prefabs inn. Ved å lage en public-variabel, kan du endre den direkte i Unity; inklusive å sette referanser til prefabs. Det er helt gull, uansett om du forstår hva det innebærer eller ikke. Først legger du til denne koden i etter teksten MonoBehaviour {, men før teksten // Use this for initialization:
Kode:
public Rigidbody bullet;
Nå har vi lagt inn koden for prefab-en vår, dvs. at vi kan legge til et objekt av typen Rigidbody som seinere kan brukes i koden under navnet bullet. Rigidbody er en av objekt-typene som brukes av fysikkmotoren til Unity. Så veit du det. Nå trenger vi å legge til koden som faktisk avfyrer kuler, så gå til Update() og skriv inn følgende mellom krøllparentesene i Update():
Kode:
if (Input.GetButtonDown ("Fire1"))
 
{
         
    Rigidbody projectile = (Rigidbody)Instantiate(bullet, transform.position, transform.rotation);
    projectile.velocity = transform.TransformDirection (Vector3.forward * 20);
 
}
Sånn, ikke så vanskelig, eller hur? Skritt for skritt, så sjekker nå Update (som kjøres hver gang spillet oppdateres, dvs. rundt 60 ganger i sekundet hvis du går for 60fps) om en av knappene knyttet til handlingen "Fire1" er nedtrykket (du kan sjekke hvilke knapper dette gjelder ved å sjekke innstillingene til spillet ditt). Så, hvis det er tilfelle, lager spillet en ny Rigidbody kalt "projectile" (vi lager en ny sånn at scriptet kan få kontroll over den), ved å klone "bullet", og sette posisjonen og rotasjonen til det samme som objektet scriptet hører til. I neste linje setter scriptet hastigheten på prosjektilet til 20. 20 hva? Si det.

Nesten ferdig! Nå må vi lage kule-prefaben vår. Bare lag en ny sfære, krymp den ned litt, pynt den slik du vil ha den, og velg den i Unity-editoren. Nå kommer det vanskelige: Pass på at kula di er valgt, og så velger du Component-menyen, Physics, og så Rigidbody. Bæm. Og nå lager du en ny Prefab i utforskeren (igjen, dette må du klare å finne ut selv), drar kula di over i den, og SMOKK. Nå har du en prefab. Nå skal vi legge skytescriptet vårt på FPS-kontrolleren. Og her må du passe på. Velg First Person Controller i hierarkiet, og utvid den så du ser begge underobjektene (Graphics og Main Camera). Nå må du dra scriptet ditt over på Main Camera-objektet som hører inn under First Person Controller, sånn at det kan få tilgang til rotasjonen og plasseringen til kameraet.

Dette er litt knotete, og kanskje litt vrient å fatte, men bare hold ut så forklarer jeg mer seinere. Okay? Nå er alt som gjenstår, å velge Main Camera-komponenten under First Person Controller, og finne fram til skytescriptet ditt. Ser du feltet som heter "Bullet"? Nå drar du kule-prefaben din over på den plassen, og bæm.

Hvis du spiller spillet ditt nå, kan du skyte kulene dine rundt omkring med venstre mustast (Ctrl funker også). Du vil legge merke til at de spretter rundt som om de var fotballer, istedenfor å fyke av gårde i en geværkule-aktig bane. Dette kommer av at vi bare la til en Rigidbody på dem uten å tweake Rigidbodyen på noen som helst måte, forøvrig. Klarer du å endre på oppførselen til kulene uten å skrive en eneste linje med kode? Prøv deg fram. Klarer du å endre på hastigheten til kulene ved å endre litt på koden? Klarer du å avfyre flere kuler på en gang? Hmmm!

Uansett, dette var første del av Unitytorial. Hva har vi lært? Jeg har ikke lært så mye, men jeg håper du som har lest dette har lært litt om Unity. Som for eksempel at det er veldig enkelt å få i gang noe spillbart med det. Uten å bruke noen eksterne assets (bortsett fra teksturer) har vi nå et landskap, en spillfigur (implisitt om ikke eksplisitt), våpenspill (et særdeles spennende sådan), lekre omgivelser og... ikke så mye mer. Men! Dette er bare begynnelsen. Neste gang blir det fiender, AI (eller noe som kan minne om det på en god dag), bedre våpen, mer grafikk (kanskje så mye som femten flere grafikker), og kanskje andre greier. Har du kommentarer eller spørsmål? Ikke nøl med å spørre, altså.
 

Yetipants

Mein Gampf
Medlem av ledelsen
#3
Lag nå noen jævla spill da ok.
 

kakarlsen

Høyere yrkesfaglig
#5
Fist for effort, meg jeg kommer fortsatt ikke til å gidde.
 

Jante

privileged CIS shitlord
#6
Du kan ikke rushe geniet. Jeg har installert Unity, jeg lover!
 

Yetipants

Mein Gampf
Medlem av ledelsen
#7
DEL 2 - litt grafikksnacks

Okay, neste ÅNKLIE tytorial blir fiender og AI, men jeg tenkte å lage et lite stop-gap som går bittelitt mer i dybden på GameObject-systemet i Unity, siden det er mye å lære der. Observer følgende nydelige diagram:

Dette er bare et (ufullstendig) eksempel på hvordan et GameObject kan være strukturert. Hvert GameObject består av ett eller flere Components, og hvert Component kan bestå av en (og noen ganger flere) Assets. Assets er da datafiler som inneholder teksturer, data, mesh-informasjon, etc. Både Assets, Components og GameObjects blir lagt inn i prosjektutforskeren i Unity (altså, Project-lista), og det er greit å holde styr på hva som er hva. Som sagt er arbeidsflyten slik at du lager en eller flere Assets, importerer dem i Unity, legger dem inn i Components (som regel blir det lagt til ett eller flere Components på objektet når du importerer det; eksempelvis får 3D-modeller automatisk et Mesh Filter og et Material, i det minste), og så kombinerer du Components til du får ett eller flere GameObjects som er klare til bruk. Forvirrende? Du blir vant med det etter hvert, og om du ikke klarer å navngi de eksakte forskjellene på GameObjects og Components så er ikke det så farlig. Bare vær klar over at i praksis så lager Unity de komponentene du trenger for å bruke objektene dine, så lenge du importerer dem riktig. Veldig hendig.

I denne tytorialen skal vi gjøre følgende:
  • Lage en enkel, teksturert simulert himmel ved å lage et Sky Dome-GameObject.
  • Lage en enkel lyseffekt som simulerer en brennende flamme.
  • Lære litt mer om arbeidsflyten i Unity, og lære om å importere Assets.

Enkelt og greit. Vi starter med en skydome.

Skydome
Som navnet hinter til, er en skydome et navn på en grafisk effekt som simulerer en himmel ved å omslutte spilleren med en diger, teksturert kule. Du kan teksturere den med akkurat hva du vil, men i dette tilfellet skal vi bruke en tekstur som - med litt velvilje - kan minne om en blå himmel med skyer på. Hvis noen gjetter på at jeg også denne gangen bruker akkurat samme tekstur som tidligere med litt andre farger, så er det helt riktig. En ting som er annerledes fra før, er at jeg skal lage min egen 3D-modell og smekke teksturen opp på denne. Årsaken er at kula skal omslutte spilleren, dermed må den være synlig fra innsiden. Unity har meg bekjent ingen enkel måte å lage en invertert kule på (standard i 3D-rendering er at 3D-polygoner ikke rendres hvis du ser dem bakfra; såkalt backface culling), men jeg tar sikkert feil. Har ikke sjekka så nøye. Uansett, dette er litt for læringens skyld også. Vi kjører i gang.

3D-modelleringsprogrammet jeg sverger til (mest fordi det er skikkelig enkelt å lære seg) er Wings 3D. Det er gratis, og en lenke til det finner du i spillmekketråden. Wings 3D er et såkalt subdivision modelling-program, dvs. at du starter med en enkel form som du deler opp i mindre deler, som du så igjen deler opp og bygger opp detaljer. Det egner seg bra til enkel spillgrafikk, siden du selv styrer akkurat hvor mange polygoner modellen din består av, og dermed kan lage modeller med få polygoner for å spare minne og prosessorkraft på skjermkortet. Kult nok. Ikke at vi skal dra på så voldsomt nå, det holder å lage ei standard-kule.

Her har jeg laget ei kule i Wings. Enkelt og greit. Hvis jeg ser denne utenfra i spillet mitt, ser jeg ei... kule. Ser jeg den innenifra, ser jeg ikke en dritt fordi normalene til polygonene er vendt utover. Heldigvis lar Wings meg enkelt invertere normalene, slik at kula blir synlig innenifra.

Invert gjør susen. Sånn, nå har jeg ei invertert kule. Det kreves en ting til for at den skal vises riktig i Unity, nemlig teksturkoordinater. Wings 3D har innebygd støtte for UV-mapping, som sikkert betyr noe spesielt men som jeg ikke har giddet å sjekke. Uansett, jeg velger UV Mapping og gønner på.

Her har jeg valgt en edgeloop som deler kula på midten. Hadde dette vært seriøs bisniss hadde jeg nok gått mer grundig til verks, men bare for å demonstrere gjør jeg det enkelt nå. Jeg skal teksturmappe modellen ved å bruke UV-unwrapping; altså, at 3D-modellen brettes ut til 2D-format sånn at jeg kan mappe en tekstur på alle polygonene. Jeg angir at Wings skal skjære opp modellen langs edgeloopen jeg har valgt her, og får...

...denne nydelige teksturen. Akkurat hvordan teksturen ser ut gir jeg blanke i, den skal byttes ut i Unity uansett. UV-mappinga er bare for å ha teksturkoordinater på modellen sånn at den viser teksturen noenlunde riktig. Som nevnt, skal du ha et seriøst resultat gjør du dette mye mer nøyaktig.

Jeg eksporterer modellen i Collada-format. Dette er fordi... det ikke blir riktig hvis jeg gjør det annerledes. Aner ikke hvorfor, men enten Wings eller Unity takler ikke helt dette 3D-modellstyret ellers.

Her har jeg importert styret. Merk Import Settings i Inspector. Jeg har valgt at normalene skal importeres; du kan også velge at Unity skal regne dem ut selv. Har hatt problemer med noen modeller hvor normalene ble føkka hvis jeg ikke satte Unity til å regne dem ut, men akkurat nå skal jeg lage et objekt som ikke skal ha noen lysutregninger utført overhodet, så jeg lar dem bare være. Jeg importerer materialet også; teksturen følger ikke med, men jeg skal uansett bytte den ut.

Her har vi materialet (med nytt navn). Tiling og Offset sier seg kanskje selv; Tiling er hvor mange ganger teksturen skal gjentas på modellen (standard er 1), og Offset er hvor langt teksturen skal forskyves bortover på overflaten. Gidder ikke endre noen av disse, men øverst ser vi at jeg har valgt en annen shader enn den som er standard: Unlit/Texture. Denne shaderen regner ikke ut noe lys overhodet, sånn at himmelen vår ikke blir påvirket av lyset i scena (noe annet ville sett teit ut). Texture innebærer så klart at vi kan sette på en egen tekstur, i dette tilfellet den nydelige himmelteksturen jeg har laget.

Og nå skaper vi litt magi her (ikke egentlig). Jeg lager et nytt script, og kaller det SkyDome. Akkurat som før skal dette plasseres på Main Camera-komponenten i First Person Controller-GameObject-et.
Kode:
using UnityEngine;
using System.Collections;

public class SkyDome : MonoBehaviour {
    
    public Transform domeMdl;
    private Transform skyDome;
    
    // Use this for initialization
    void Start () 
    {
        skyDome = (Transform)Instantiate (domeMdl);
        skyDome.transform.localScale = new Vector3(1000.0f, 1000.0f, 1000.0f);        
    }
    
    // Update is called once per frame
    void Update () 
    {
        skyDome.transform.position = transform.position;
        skyDome.transform.RotateAround(transform.position, Vector3.up, 1.0f * Time.deltaTime);
    }
}
Som dere ser, har jeg rom for en prefab - domeMdl - og dessuten en privat variabel kalt skyDome (referanse til prefab-en). I Start velger jeg at scriptet skal klone en ny SkyDome til meg, og så skalerer jeg den opp slik at den blir diiiger (husk at den skal omslutte hele det synlige spillområdet). Nå valgte jeg bare noen tilfeldige verdier her, men det kan hende at disse verdiene må økes hvis du har et stort spillområde.

I Update() setter jeg posisjonen til SkyDome-modellen til den samme som posisjonen til objektet scriptet er knyttet til, og så legger jeg på en enkel rotasjon for å simulere at skyene beveger seg. Sånn, ferdig. Jeg lagrer scriptet, og drar det over på Main Camera i First Person Controller-objektet i hierarkiet. Og så må det testes...

Riktig så vakkert... vil være feil beskrivelse, men det er hovedsaklig fordi jeg slurva så innmari med å lage skydome-effekten min. Hvis jeg hadde giddet, kunne jeg lagt til et eget objekt med et skylag som beveget seg uavhengig av himmelen, flere objekter som tegnet opp teksturer for å simulere landskap mot horisonten, og så videre. Det er helt opp til den som mekker.

Poenget er iallfall at jeg har lagt til en simpel, men (i prinsippet) stilig effekt som lett kan bygges ut så mye jeg gidder, og det tok cirka ti minutter.

Flammeeffekt
Dette blir enda enklere. Jeg skal legge til et farget lys som, mens spillet kjører, får lysstyrken endret tilfeldig slik at den gir inntrykk av en levende flamme. Gratisversjonen av Unity har ikke støtte for sanntidslyssetting (som i, simulerte skygger og sanntidslyskilder), men du kan få til noen ålreite effekter med det som følger med likevel. Jeg begynner med å legge til et Point Light.

Her har jeg lagt til lyset mitt. Fargen settes til oransje, for å etterligne et bål eller noe sånt. Jeg gønna opp rekkevidden til 100, sånn at det skal synes bra, og... det er det hele. Nøkkelen her er Intensity, som angir lysstyrken. Den kan stå på akkurat hva den vil, siden jeg skal endre den i et script. Dette scriptet, faktisk:
Kode:
using UnityEngine;
using System.Collections;

public class LightFlicker : MonoBehaviour {
    
    private int timer = 0;
    private float intense = 5.0f;
    
    // Use this for initialization
    void Start () 
    {
    
    }
    
    // Update is called once per frame
    void Update () 
    {
        timer++;
        if (timer > 10)
        {
            intense = Random.Range(2.0f, 5.0f);
            timer = 0;
        }
        intense -= 0.1f;
        light.intensity = intense;
    }
}
Her har jeg to variabler: En timer-variabel, for at ikke lysstyrken skal endres for fort, og en variabel som endrer selve lysstyrken. I Update øker jeg timeren, og når den når 20 (dvs. tre ganger i sekundet ved 60fps) setter jeg lysstyrken til en tilfeldig verdi mellom 2 og 5 (maks er 8, som er overbright). Så senker jeg lysstyrken såvidt for å jevne ut overgangene slik at de ikke ser altfor kunstige ut, og endrer lysets lysstyrke til den endrede variabelen. Sånn, bare å smekke dette scriptet på lyset jeg lagde istad, så er alt i boks.

Sånn ja. Ikke at det synes på bildet, men lyset pulserer sånn passe tilfeldig sånn at det med litt godvilje minner om lyset fra en brennende flamme. Hadde jeg giddet, kunne jeg ha slengt inn en modell av et bål eller noe, men effort. Så, joda, effekter er fortsatt mulig selv om man ikke betaler for seg, man må bare godta at det blir mer PC-spill fra 1996-preg over det enn noe Crytek-aktig. Fortsatt ikke verst for noe som tok to minutter.

Uansett, det var da en liten stop-gap. Nådde jeg målet på femten flere grafikker? Så klart, det var minst tredve grafikker der skal jeg si dere. Uansett, håper dere hadde det moro og lagde et par fullskala-MMO-er som følge av denne tytorialen. Vi sees neste gang, med minst 30000% mer AI og fiender.
 

Buggz

Jævla Buggz
Medlem av ledelsen
#8
Jeg skal tenke ut en premie for denne glimrende efforten! Jeg skal også hive meg ned med dette ved første ordentlige anledning (antageligvis ikke før til helga).
 

Yetipants

Mein Gampf
Medlem av ledelsen
#9
DEL 3 - AI og skyting

Jaggu, nok en runde med Unitytorial og denne gang tar vi vårt første ordentlige skritt mot et skikkelig spill. Nemlig AI (altså, Artificial Intelligence, altså, kunstig intelligens, altså, ting som gjør ting). Nå skal dere få høre en liten hemmelighet: AI har ingenting med intelligens å gjøre overhodet. Det man kaller "AI" er bare et sett med regler som definerer hvordan en figur i spillet ditt skal oppføre seg. Det er opp til den som lager spillet å finne ut hvor avansert eller simpel hver enkelt AI-implementasjon skal være, og man må ta med i betraktningen hva slags spill en skal lage og hvordan ting skal fungere. Så, det er ikke noe mer magisk enn at figurene hele tida sjekker noen bestemte variabler, og gjør det de får beskjed om i hvert enkelt tilfelle. Dermed handler AI mer om å passe på at man har laget regler for de aller fleste situasjoner, enn om å lage noe som kan minne om "realistisk" kunstig intelligens. Så veit du det.

Mål for dagen:
  • Lage en enkel fiende med skikkelig enkel AI som kan bygges ut til noe brukbart etter hvert.
  • Gi spilleren et våpen som er litt bedre egnet til å bekjempe slemminger.
Fiende-AI
Jeg bestemmer at den første fienden i spillet mitt skal være en skikkelig teit robot som ikke gjør stort mer enn å følge etter spilleren og være til bry (enn så lenge). Ja, og så må han dø når han blir skutt på nok. Enkelt nok? Nja, det er noen ting å tenke på selv med så beskjedne mål. Heng med.

Møt roboten jeg har valgt å kalle Johannes:

Nei, jeg la ikke ned veldig mye arbeid i Johannes. Uansett, hvis du vil lage din egen 3D-modell, så bare gå berserk. Jeg holdt det enkelt siden det neppe blir veldig mange oransje piler i det ferdige spillet. Du KAN bruke en standard Unity-modell også, for eksempel en kube, men det ser kanskje litt mer ålreit ut med noe mindre kjipt. Du bestemmer. Uansett, bare få inn en greie der ok.

Nå skal vi mekke et script som styrer Johannes. Her er det en god del småtteri som kanskje er litt vrient å få grep om, men det er likevel VELDIG VIKTIG. Så forbered deg på en ganske grundig gjennomgang ok?
Kode:
using UnityEngine;
using System.Collections;
 
// enemy script relies on CharacterMotor, so specify it as a requirement
[RequireComponent (typeof (CharacterMotor))]
 
public class BasicEnemyScript : MonoBehaviour
{
    // public variables
    public float speed;
    public float awarenessRadius;
    public int hitpoints;
   
    private CharacterMotor motor;    // for enemy movement and collision detection
    private Transform player;        // reference to player object
       
    // Use this for initialization
    void Start ()
    {
        // get a reference to character motor component
        motor = GetComponent<CharacterMotor>();
        // get a reference to player transform
        player = GameObject.FindWithTag("Player").GetComponent<Transform>();
    }
   
    // Update is called once per frame
    void Update ()
    {
        // calculate direction vector towards player
        Vector3 targetVector = player.position - transform.position;
        if (targetVector.x <= awarenessRadius && targetVector.z <= awarenessRadius)
        {
            if (targetVector.x >= -awarenessRadius && targetVector.z >= -awarenessRadius)
            {
                // look at player
                Vector3 playerPos2D = player.position;
                playerPos2D.y = transform.position.y;
                transform.LookAt(playerPos2D);
                // move towards player
                motor.inputMoveDirection = targetVector * speed * Time.deltaTime;
            }
            else motor.inputMoveDirection = Vector3.zero;
        }
        else motor.inputMoveDirection = Vector3.zero;
    }
   
    public void BulletHit(int power)
    {
        hitpoints -= power;
        if (hitpoints <= 0)
            Destroy(gameObject);
    }
}
Ålreit! Ålreit. Vi tar det fra toppen av. For dere som ikke programmerer på si: Linjer som starter med // er kommentarer. De er der for å forklare hva i helvete ting gjør i scriptet ditt. Lurt å skrive ned, sånn i tilfelle du glemmer hva du har holdt på med (protip det gjør du).

Uansett, vi skal bruke en CharacterMotor for å bevege Johannes. CharacterMotor er en standard Unity-komponent (samme type som driver FPS-kontrolleren vår), men uten den funker ikke scriptet vårt. Derfor legger vi inn en linje som spesifiserer overfor Unity at den må med, så blir den automatisk lagt til når vi legger dette scriptet på et GameObject. Hendig.

Når vi først starter på klassen vår, ser vi at vi har en del public-variabler; dvs., variabler som påvirker hvordan Johannes oppfører seg, og som kan endres direkte i Unity-editoren uten at man må endre på selve scriptet. Speed er hvor raskt Johannes beveger seg bortover, Awareness Radius er en definisjon på et område rundt Johannes hvor han oppfatter at spilleren befinner seg; han reagerer ikke før du kommer nære nok, ooog Hitpoints er hvor mye liv Johannes har. Enkelt og greit.

Under der har vi to private variabler, som er referanser til andre objekter. Motor er en referanse til CharacterMotor-komponenten som befinner seg på Johannes' GameObject, og Player er en referanse til spillerobjektet.

I Start() setter vi bare opp referansene våre. En viktig ting er at Player-referansen er avhengig av en Tag. Tags er en feature i Unity som innebærer at du kan sette et egendefinert navn på objekter, og søke dem opp i koden din ved å søke etter taggen. Akkurat dette scriptet er avhengig av at det bare er ett objekt som har taggen "Player", så det er litt viktig.

(Ålø ny versjon av Unity a gitt.) Sjekk øverst til høyre i Inspector. Her kan man se da, at jeg har lagt inn FPSController-objektet i en prefab som heter Player. Rett under navnet ser du at det står "Tag", og at jeg har valgt "Player". Nå er denne prefab-en søkbar ved å bruke GameObject.FindWithTag. Vi trenger bare en referanse til Transform-komponentet, så derfor søker jeg opp det ved å bruke GetComponent og et cast.

I Update() skjer magien. Først tar vi en enkel subtraksjon, og finner avstanden mellom Johannes og spilleren. Hvis den er mindre enn Awareness Radius, så roterer Johannes mot spilleren og GÅR TIL ANGREP. Eller beveger seg veldig sakte bortover, da. Hvis spilleren beveger seg utenfor Awareness Radius, så stopper Johannes. Teite Johannes.

Noen notiser til dette: Først av alt, dere veit hva en Vector3 er, ikke sant? Hvis ikke: En Vector3 er bare en datatype som består av tre vektorer, ved navn x, y, og z. De kan brukes til å representere fryktelig mye rart, men et veldig vanlig bruksområde er retninger i 3D-rom. Og det er hva vi bruker en Vector3 til her. Enkelt og greit. Hvis dere ser på koden som roterer Johannes mot spilleren, så bruker jeg først en midlertidig Vector3 hvor jeg setter y-koordinaten til å bli den samme som den til Johannes. Hvorfor ikke bare bruke transform.LookAt(player.position) lurer kanskje noen på? Prøv det, så får du se hvorfor. Hvis du ikke gidder, kan jeg avsløre at hvis du gjør det sånn, så roterer Johannes bakover når spilleren kommer nærme siden Y-koordinaten på transformene blir ulike, og det ser sabla teit ut. Så sånn er nok det.

Dere ser også at jeg ganger opp bevegelsesvektorene med både Speed og noe kalt Time.deltaTime. Speed sier seg selv, men hva er Time.deltaTime? Jo, en viktig ting når man mekker spill, er at ting skjer synkront. Hvis du ikke synkroniserer handlingene i spillet ditt mot et fast holdepunkt, så kan hastigheten i spillet endre seg fra maskin til maskin. For eksempel: Hvis du ikke synkroniserer i det hele tatt, kjøres multiplikasjonen med samme faktor hver gang spillet oppdateres, noe som kan være alt fra 20 til 60 ganger i sekunder (eller mer). Ved å gange med deltaTime, ganger du med antall millisekunder som har gått siden forrige oppdatering, og dermed blir frekvensen den samme uansett hvor rask PC-en du kjører spillet på er. Slike timere med høy presisjon er utrolig viktige for konsistent spillflyt, og de er fullstendig påkrevd hvis du skal lage et multiplayer-spill. Heldigvis har Unity en innebygd slik timer, så bare bruk Time.deltaTime hvis du skal regne ut en translation eller tilsvarende greier som må vises likt uavhengig av ytelse.

Under Update() finner vi funksjonen BulletHit(), som ganske enkelt fyres av hvis Johannes blir truffet av ei kule. Altså, den blir ikke det nå, men den blir det seinere. Som man ser av koden, skjer det ikke noe mer spennende enn at livet til Johannes minker, og at han plutselig forsvinner i løse lufta hvis han mister alt livet. Jeg holder det enkelt her, men dette er stedet å legge til en funksjon som tryller fram en raff eksplosjon, gir spilleren poeng, spiller av lydeffekter, eller hva nå enn du måtte ha lyst til. Du bestemmer.

Så, det er alt vi trenger akkurat nå. Hvis du plasserer scriptet på Johannes, og plasserer Johannes et stykke unna spilleren, og så setter Speed, Awareness Radius og Hitpoints til noe passende (jeg har 2, 30 og 50 på min Johannes, prøv deg fram), så skal han følge etter deg når du kommer borttil og kikke i din retning. Ikke spesielt avansert AI, men det er da en start. Poenget her er å lage noe som lett lar seg bygge ut, og det... kan man trygt si at det er godt potensial for her. Uansett, den egentlige seieren her er at Unity fikser alt av collision detection for oss, slik at vi slipper å mekke selv at Johannes ikke skal falle gjennom bakken eller gå tvers gjennom trær, at han skal gå saktere i oppoverbakke og raskere når han faller, osv. Alt dette kan du selv endre hvis du tweaker Character Controller-komponentet som inkluderes som en del av Character Motor, forresten. Velbekomme.

No skytings
Okay. Da vi lagde børsa i første del av tytorialen, gjorde vi det såpass enkelt at vi bare brukte Instantiate() for å klone en prefab og sende den av gårde i samme retning som kameraet pekte. Vi skal ta noen skritt videre nå, og gjøre ting litt mer ordentlig, så åpne først opp scriptet som heter Bullet Shooter (eller tilsvarende). Endre Update() slik at den ser sånn ut:
Kode:
void Update ()
{
    if (Input.GetButtonDown ("Fire1"))
    {
        Instantiate(bullet, transform.position, transform.rotation);
    }
}
Sånn. Det som ble endret, var bare at Bullet Shooter-scriptet ikke lenger har noen kontroll over Bullet-prefab-en etter at den ble klonet; posisjon og rotasjon blir satt idet den klones, og så bryr ikke Bullet Shooter seg noe mer med den. Dette betyr at vi trenger et nytt script som kan kontrollere kula litt mer nøyaktig. Lag et som heter Bullet.
Kode:
using UnityEngine;
using System.Collections;
 
public class Bullet : MonoBehaviour {
   
    // public variables
    public float speed;
    public int power;
   
    // private variables
    private int lifetime;
   
    // Use this for initialization
    void Start ()
    {
        rigidbody.velocity = transform.TransformDirection(Vector3.forward * speed);
        lifetime = 0;
    }
   
    // Update is called once per frame
    void Update ()
    {
        lifetime++;
        // bullets only exist for three seconds
        if (lifetime >= 180)
            Destroy (gameObject);
    }
   
    void OnCollisionEnter(Collision collision)
    {
        foreach(ContactPoint contact in collision.contacts)
        {
            if (contact.otherCollider.tag == "Enemy")
            {
                BasicEnemyScript enemyScript = contact.otherCollider.GetComponent<BasicEnemyScript>();
                if (enemyScript != null)
                    enemyScript.BulletHit(power);
            }
        }
        Destroy (gameObject);
    }
}
Sånn, litt bedre. Public-variablene lar deg kontrollere hastigheten på kula, samt hvor mye skade den skal gjøre, direkte i Unity-editoren. Den private variabelen Lifetime styrer bare hvor lenge kula skal eksistere før den blir fjernet fra spillet. Dette er fordi hver nye kule som lages bruker opp litt minne, og hvis du ikke sletter kulene etter at de har gjort nytta si, blir ikke minnet frigjort (iallfall ikke på en god stund). For å unngå unødig minnebruk, pass på å fjerne objektene dine når du ikke trenger dem mer.

Start() gjør ikke annet enn å sette hastigheten på kula til Speed (notis: Ser du at jeg ikke ganger med Time.deltaTime selv om dette er noe som MÅ være synkronisert? Ah, fysikkmotoren i Unity styrer det selv når vi bruker Velocity. Herlig), og sette Lifetime til 0.

Update() øker Lifetime, og hvis den er over 180 (dårlig form å hardkode den begrensningen forøvrig, jeg gjør det mest for å irritere Buggz), noe som er cirka tre sekunder ved 60fps, så fjernes kula fra spillet. Det kan være litt for kort, kan du si, men dette er bare for å vise fram konseptet.

Så kommer selve moroa i OnCollisionEnter. Dette er en standard Unity-funksjon som følger gratis med Rigidbody-typen vi la på kula. Den fyres av når kula kolliderer med noe, automatisk. Parameteret Collision er en egen datatype Unity bygger opp, som består av informasjon om objektene fysikkmotoren har registrert at er med i kollisjonen. Ansvaret vårt er å sjekke etter de objektene vi er interesserte i, og behandle dem slik vi ønsker. Foreach-instruksen går gjennom alle kollisjonspunktene som ble funnet (det KAN hende at kula kolliderer med mer enn en ting), og siden vi er interesserte i om kula kolliderer med vår venn Johannes, sjekker vi etter taggen "Enemy". For å bruke denne taggen, må du lage en egendefinert tagg, og akkurat hvordan du gjør det synes jeg du skal få finne ut selv NEIIIDA. Velg Johannes i Unity-editoren, og under Tag i Inspector, velger du "Add Tag". Nå må du utvide elementet som heter "Tags", og til høyre for "Element 0" må du skrive inn "Enemy". Jeg tror du kan ha så mange tags du vil, forresten.

Uansett: Hvis fysikkmotoren finner et objekt med taggen Enemy i kollisjonen vår, så prøver vi å finne fiende-scriptkomponentet på objektet. GetComponent() returnerer null hvis den ikke finner noe script, og siden you don't want to end up in the middle of invalid memory, så kjører vi bare scriptet hvis det faktisk ble funnet. Jepp. Som man kan se, sender vi avgårde Power-variabelen for å indikerer hvor mye skade kula gjør. Og, uansett, så lenge kula kolliderer med noe, så skal den fjernes fra spillet så da gjør vi det. Oisann, kjekke saker.

Så, ja. Sleng Bullet-scriptet på kule-prefab-en din, endre variablene til noe vettugt (Speed = 50 og Power = 5 her i gården), og så er vi NESTEN klare. Sjekk ut Rigidbody-en på kula før du tester, er du grei. Du ser boksen hvor det står Collision Detection? Jo, når ting beveger seg fryktelig fort, som for eksempel kuler gjør, må fysikkmotoren få vite at den må sjekke etter kollisjoner ganske ofte. Derfor velger du "Continuous Dynamic" i lista. Continuous Dynamic-sjekking krever en god del juice, så ikke bruk den på alt. Bare bruk den på raske ting, ok? Ja, og hvis du vil, kan du slå av gravitasjon på kula sånn at den ikke faller til marken etter kort tid. Det gjorde jeg.

Vel, det var det. Test spillet ditt nå, så kan du både forfølges av en ond (og teit) robot, og dessuten ta livet av ham. Plasser ut noen flere også, hvis du vil. Lag et script som spawner roboter med (u)jevne mellomrom, og vips har du din helt egne (ræva) Horde Mode. CliffyB har jo slutta i Epic, kanskje dette er din sjanse?

Neste gang: Mer effekter! Nytt våpen? Kanskje TO nye våpen?!?!? Kanskje Johannes får en bror? Kanskje Johannes får seg kjæreste?!?! Kanskje Johannes lærer seg noe mer enn å være en creepy stalkerfaen!??!?!?!
 

Yetipants

Mein Gampf
Medlem av ledelsen
#10
Fotnøtter: Character Controller
Et lite tillegg om Character Controller her, folkens. Dette er pirkegreier, men det kan ha mye å si for spillet ditt, så jeg tar det med her. Tok det ikke med i selve tytorialen fordi det blir mye utenompreik som fjerner fokus fra hovedemnet (les: Jeg glemte det).

Her har jeg valgt Johannes i editoren. Legg merke til Character Controller-feltet i Inspector. Unity legger på en slik Character Controller når du legger til en Character Motor, og det er noen ting det er viktig å være klar over her. For det første: En Character Controller definerer størrelsen på komponentet uavhengig av størrelsen på modellen din. En Character Controller bruker en kapsel-bounding box, og størrelsen og formen på den matcher ofte dårlig med modellen din. Derfor er det viktig at du tweaker den litt. Det viktige her, er å se på Center, Radius, og Height i Inspector-panelet. Sjekk bounding box-en på komponentet i editoren. Stikker den ned under modellen din? Mest sannsynlig ja, og da kan det bli krøll når Unity skal flytte på figuren din. Pass på at bunnen på bounding box-en matcher med bunnen på modellen din. Juster størrelsen og midtpunktet til det passer, slik jeg har gjort det i mitt eksempel. Dett var dett.
 

Yetipants

Mein Gampf
Medlem av ledelsen
#11
DEL 4 - litt om GUI og sånt
Nok en stopgap, dere. Kom på et par ting jeg ville trekke fram, og som passer som en egen del av tytorialen. Jeg snakker om GUI, altså Graphical User Interface, altså grafisk brukergrensesnitt, altså ting som gjør ting (visste du at, prikk prikk prikk, Unity består 100% av ting som gjør ting?). GUI-funksjonene i Unity er satans bra. Dette er et faktum. Likevel... øh, en ting jeg har oppdaget, er at GUI-greiene ikke er så fryktelig konsekvente, men etter hva jeg kan se er sluttresultatet at du som sitter og mekker får flere kjekke ting å bruke, og flere teknikker som gjør ting lettere for deg, så jeg har ikke tenkt å begynne å klage på akkurat det.

Mål for dagen:
  • Lage en simpel HUD (Heads Up Display) som viser litt info om spilleren, og som enkelt kan bygges ut.
  • Lage en enkel meny som inneholder noen kjekke funksjoner, som dukker opp når spilleren ber om det, og som - riktig - enkelt kan bygges ut.
Vi begynner med HUD, altså de dingsebomsene som ligger over selve spillet og viser nødvendig info til spilleren; hvor mye liv han eller hun har, hvor mange skytekuler vedkommende bærer med seg til pangebørsa si, om personen det gjelder har snytt på skatten, og så videre. Jeg tenker vi starter enkelt, med en indikator som viser hvor mye liv spilleren har igjen, hvor mye ammo det er i børsa hans/hennes, og et lite siktemiddel som informerer spilleren om at heisann du, dette her er faktisk midten av skjermen så her dukker det opp ting når du skyter.

Vi trenger tre ting hvis dette skal funke. Vi trenger noe grafikk som kan representere ammo og helse samt siktemiddel, vi trenger noe skrift som kan brukes til å skrive ut informasjonen, oooog vi trenger et script som kan fortelle Unity hvordan ting skal se ut. Best å sette i gang, da.

Herlig. Det er mulig du gidder å legge litt mer arbeid i dette, jeg veit ikke. Uansett, det var en av tre ting i boks. Neste? Vår helt egne, superspennende skrifttype. Eller, altså, du kan jo bare kjøre på med standard-teksten Unity bruker. Den er skikkelig fin.

Vel, altså, du KAN bruke Arial som font, jeg kan ikke akkurat hindre deg i det. Men når hjernerystelsen din har gitt seg og du innser at Arial ser dritt ut som HUD-font, så kan det være nyttig å ha en annen skrifttype klar, eller hva?

Så, dere har sikkert lagt merke til at jeg har laget alt av assets selv hittil. Det er fordi jeg synes det er moro å lage ting selv, og fordi jeg ville irritert meg over det hvis jeg hadde brukt stæsj andre hadde mekka. Skrifttyper er unntaket, og det er rett og slett fordi jeg veit bare litt mer om typografi enn jeg veit om mammografi (det har med mammuter å gjøre, ikke sant?). Så, seil ut på det vide Internett og finn en skrifttype du liker. Pass på! Mange skrifttyper du finner til gratis nedlasting, kan bare brukes til personlige formål. Hvis du inkluderer skrifttypen i spillet ditt og legger det ut på nett, kan i teorien den som har laget skrifttypen finne den og søke en sak mot deg hvor vedkommende skal ha alle pengene. Det er lurt å finne en som ikke ser helt forjævlig ut, og som lar deg gjøre hva du vil med den. Et eksempel på det er en kis som kaller seg Ænigma, som har mekka ørten stilige fonter (noen av litt mer tvilsom legalitet enn andre, som f.eks. Perfect Dark- og Zelda-fontene) du kan gjøre akkuat hva pokker du vil med. Nettsida hans er dessverre konk nå om dagen, men du finner fontene lett tilgjengelig her, for eksempel. Jeg valgte en som heter Narrow eller noe sånt, men du bestemmer så klart selv.

Uansett, lagre font-fila et sted og importer den i Unity, legg den i ei egen Fonts-mappe hvis ikke du LIKER Å HA DET ROTETE og så er vi klare til siste post: Et script som syr det hele sammen. Lag et nytt GUIText-GameObject i spillet ditt, og legg på et script som heter Hud eller noe. Sånn ser det ut:
Kode:
using UnityEngine;
using System.Collections;
 
public class Hud : MonoBehaviour {
 
    public Font hudFont;
    public Texture2D healthIcon;
    public Texture2D ammoIcon;
    public Texture2D crosshair;
 
    private GUIStyle defStyle;
    private PlayerScript player;
 
    // Use this for initialization
    void Start ()
    {
        player = GameObject.FindWithTag("Player").GetComponent<PlayerScript>();
        defStyle = new GUIStyle();
        defStyle.alignment = TextAnchor.UpperLeft;
        defStyle.fontSize = 28;
        defStyle.font = hudFont;
    }
 
    void OnGUI()
    {
        DrawHealthHud (16, (Screen.height - 64));
        DrawAmmoHud (112, (Screen.height - 64));
        DrawCrosshair ();
    }
 
    // method for drawing text with a black border
    void DrawBorderedText (int originx, int originy, int width, int height, string str, GUIStyle textStyle)
    {
        for (int bordery = -1; bordery <= 1; bordery++)
        {
            for (int borderx = -1; borderx <= 1; borderx++)
            {
                // draw black text nine times, slightly offset so they form a nice border
                textStyle.normal.textColor = Color.black;
             
                GUI.Label(new Rect(originx + borderx, originy + bordery, width, height), str, textStyle);
                 
            }
        }
        // draw white text on top to make it all pretty
        textStyle.normal.textColor = Color.white;
        GUI.Label(new Rect(originx, originy, width, height), str, textStyle);
    }
 
    void DrawAmmoHud(int x, int y)
    {
        GUI.DrawTexture (new Rect(x, y, ammoIcon.width, ammoIcon.height), ammoIcon, ScaleMode.ScaleToFit, true, 0.0f);
        DrawBorderedText ((x + ammoIcon.width + 8), y, 64, 64, player.ammo.ToString(), defStyle);
    }
 
    void DrawHealthHud(int x, int y)
    {
        GUI.DrawTexture (new Rect(x, y, healthIcon.width, healthIcon.height), healthIcon, ScaleMode.ScaleToFit, true, 0.0f);
        DrawBorderedText ((x + healthIcon.width + 8), y, 64, 64, "<color=#ff0000>" + player.health.ToString() + "</color>", defStyle);
    }
 
    void DrawCrosshair()
    {
        GUI.DrawTexture (new Rect((Screen.width / 2) - (crosshair.width / 2), (Screen.height / 2) - (crosshair.height / 2), crosshair.width, crosshair.height), crosshair, ScaleMode.ScaleToFit, true, 0.0f);
    }
}
Observante observatører legger merke til at her har vi ingen Update()-funksjon; GUI-systemet i Unity bruker isteden en funksjon kalt OnGUI(), som kjøres etter at 3D-rendringa har funnet sted (rykter vil ha det til at hvis du vil ha raskere oppdateringer enn det spillet ditt kjører, så er OnGUI() stedet å gjøre det; dette kommer vi eventuelt tilbake til når det blir aktuelt, men bare en liten heads-up der altså). Bare smell inn alle funksjonene du vil skal kjøre på GUI-et i OnGUI(), så vises det automatisk. Herlig? Herlig.

Fra toppen av så ser vi at vi har publics til både fonten vi skal bruke, og de tre ikonene som skal vises på skjermen. Privates er bare et GUIStyle-objekt som forteller Unity hvordan teksten skal vises på skjermen, og en referanse til et PlayerScript (ja du må lage et nytt script som heter PlayerScript, legge det på Player-prefaben din og gi det to public-variabler som heter health og ammo forresten, har ikke tenkt å lage noen oppskrift på det så finn ut av det selv hehe), som brukes til å hente info om spilleren. PlayerScript er ganske tom i denne tytorialen, men hvis vi bare lager et nå så har vi et fundament til seinere. Start() setter opp referansen til PlayerScript og gjør klar GUIStyle-objektet sånn at det blir riktig. Hvis du vil ha en annen tekststørrelse, så er det bare å endre det her.

I selve OnGUI() kjører vi bare de funksjonene vi har laget som skal tegne opp HUD-en. Du KAN bare legge inn all opptegninga direkte i OnGUI(), det er opp til deg; jeg gjør det sånn som dette siden jeg da enkelt kan legge til eller fjerne funksjoner, sånn at HUD-en lett kan tilpasses til spillerens ønsker. Man kan se at jeg bruker de innebygde variablene Screen.width og Screen.height for å plassere ting på skjermen; siden spillvinduet i Unity ikke har noen fast størrelse (brukeren bestemmer selv hvor stort det skal være), må du ta høyde for det når du plasserer GUI-et ditt. Bruk kantene på skjermen som referanser isteden for å plassere ting fast.

Draw-funksjonene mine er ganske så uspennende, men i DrawHealthHud() ser vi et eksempel på hvor fleksibelt Unity er. Jeg legger til litt tekst foran og bak informasjonen fra PlayerScript, bare for å få farge på teksten. Du kan faktisk style teksten med mange vanlige HTML-tagger, sjekk manualen til Unity for ytterligere info. Det er uansett superkvalitet på den.

Ja, og den der fleksibiliteten jeg nevnte? Hvis du bare lager en GUIText og ikke scripter den, så får du en statisk tekststreng på et forhåndsbestemt sted på skjermen. Sletter du teksten i GUIText-objektet og legger på et script, så kan du - som jeg viser her - legge på akkurat hva faen du har lyst, hvor som helst. Det er ganske stilig.

Uansett, kjør spillen din nå (etter å ha lagt inn font og teksturer, og å ha satt PlayerScript.health og PlayerScript.ammo til noe vettugt, så klart), så vil du kanskje få se noe som... dette:

Herlig, herlig. Den der DrawBorderedText()-funksjonen er noe jeg har smelt sammen bare for å få litt tydeligere tekst i spillet, ved at det tegnes opp en svart kant rundt. Den er så klart horribelt dyr å kjøre (hver tekststreng krever ti operasjoner istedenfor en, og alpha-blending er notorisk prosessorkrevende) så dette er skikkelig luksus, men vi må koste på oss såpass. Det finnes mer effektive måter å gjøre det der på, meeeen jeg gidder ikke gjøre det akkurat nå.

Men: Ny meny
Vi må også ha en spillmeny såklabbert. Det vanligste i PC-spill er jo seff å trykke Escape for å få opp menyen, så det må jo vi også ha. Jeg antar du KAN hardkode inn i spillet ditt at du sjekker om spilleren har trykket Escape, men siden det er for skitne løsarbeidere skal vi heller gjøre det på skikkelig vis og lage en egendefinert input-akse.

Kort om input i Unity: Alle inputs defineres som en akse, med positive og negative verdier. Nå er det ikke alltid du trenger en analog akse med positive og negative verdier, men... da bare sjekker du etter den positive. Hvis det ikke høres spesielt praktisk ut å gjøre ting på denne måten, tenk på at med dette systemet kan du ganske enkelt lage en analog input som fungerer akkurat like bra uansett om du bruker en digital enhet (tastatur, for eksempel) eller en analog enhet (joystick eller berøringsskjerm) til å kontrollere den. Som for eksempel den FPS Controller-greia vi har brukt fra første stund. Sjekk koden til den og se hvor latterlig enkelt det er å få ting til å røre på seg.

Okay, greit, det er kjekt, vi må altså lage en egen input. Er det vanskelig? Hah. Edit-menyen, Project Settings, så Input. Utvid Axes, velg Size, sett den til en mer enn det som står der fra før av, og så får du en ny analogakse. Den het Jump da jeg lagde min, men det duger jo ikke (du sjekker input ved å sende navnet på aksen, så de må være unike). Kall den Menu isteden. På "Positive Button" skriver du inn "escape" - akkurat sånn, med liten e, uten anførselstegn - og så har du laget en ny akse. Easy peasy.

Og så... er resten egentlig bare litt kode. Menyen blir rimelig simpel akkurat nå, men gjett hva: Den kan bygges ut seinere. Vi legger ikke på noe mer nå enn, si, muligheten til å starte spillet på nytt eller å avslutte. Eller, føkk itt, vi mekker et par options også.

Vi starter med å legge til noen flere private klassevariabler i HUD-scriptet:
Kode:
private bool menuActive = false;
private int menuTimer = 0;
private bool drawHud = true;
private bool drawCrosshair = true;
Enkelt nok, håper jeg. Først en bool som sier ifra om at spillet skal tegne menyen eller la være, en liten timer for å forhindre at menyen konstant åpner og lukker seg når man trykker Escape, og bools for de alternativene vi skal legge inn.

Siden vi skal hente input, er det lurt å slenge inn Update() igjen. Som nevnt oppdateres OnGUI() mye oftere enn Update(), og dermed kan ting bli uforutsigbart hvis vi legger inn input-sjekker i OnGUI().
Kode:
void Update()
    {
        if (menuTimer <= 0)
        {
            float menuInput = Input.GetAxis("Menu");
            if (menuInput != 0)
            {
                menuActive = !menuActive;
                menuTimer = 20;
            }
        }
        else
            menuTimer--;
    }
Fortsatt enkelt, håper jeg. Vi sjekker Menu-aksen (akkurat som FPSController sjekker Horizontal- og Vertical-aksene), og hvis den ikke er 0 (altså, at brukeren har trykket Escape), endrer vi verdien på menuActive. Easy peasy? Timeren er der som nevnt bare for at ikke menuActive konstant skal endres så lenge brukeren trykker Escape (husk, dette sjekkes cirka seksti ganger i sekundet, og så rask på labben er det få som er). Finnes så klart andre måter å løse dette på (ved å sjekke om knappen har blitt sluppet igjen siden sist verdien ble endret, for eksempel), men nå gjør jeg det sånn. Seinere skal vi se på andre metoder.

OnGUI har så klart også blitt endret for å ta høyde for menyen og options.
Kode:
    void OnGUI()
    {
        if (menuActive)
            DrawMenu();
     
        if (drawHud)
        {
            DrawHealthHud (16, (Screen.height - 64));
            DrawAmmoHud (112, (Screen.height - 64));
        }
        if (drawCrosshair)
            DrawCrosshair ();
    }
Og helt til slutt har vi DrawMenu().
Kode:
void DrawMenu()
    {
        GUI.Box (new Rect(10, 10, 200, 200), "Hyper Menu");
        // restart button
        if (GUI.Button (new Rect(20, 40, 180, 20), "Restart game"))
            Application.LoadLevel(0);
        // quit button
        if (GUI.Button (new Rect(20, 70, 180, 20), "Quit game"))
            Application.Quit();
        // draw HUD toggle
        drawHud = GUI.Toggle (new Rect(20, 100, 180, 20), drawHud, "Draw HUD");
        // draw crosshair toggle
        drawCrosshair = GUI.Toggle (new Rect(20, 130, 180, 20), drawCrosshair, "Draw crosshair");
    }
Håper dere skjønner hvor latterlig enkelt GUI-systemet i Unity er. Sjekk ut sakene, så kan du også erfare hvordan Escape fungerer som Unclose Hyper Menu (props hvis du tar den referansen).

Merk at hvis du spiller greiene direkte i Unity-editoren, så fungerer bare Restart Level og options siden det ikke gir mening å avslutte spillet i editoren. Hvis du derimot tester Build & Run, så får du en fullt fungerende Hyper Menu som kan avslutte spillet og gode greier. Herlig? Herlig!

Og, ja, menyen bruker Arial nå. Det ser kjipt ut. Grunnen er forøvrig at man må mekke en helt egen GUIStyle som tar høyde for alle elementer som knapper, toggles og så videre, ooog det gadd jeg ikke helt nå. Du kan prøve å legge defStyle på elementene i menyen og se hvordan det ser ut. Neste gang, jeg lover!
 

Agradula

Ridder Jonatann
Medlem av ledelsen
#12
Må man bruke de kodinggreiene, eller er det egne bokser man kan trykke i? Har ikke lest alt veldig nøye her altså, jeg bare vet at med en gang jeg ser på kode så dør jeg. Prøvd meg på codeacademy, og moro en liten stund, helt til jeg bare blir sint, frustrert og mongohore på koden. Ja, jeg suger på å skrive de få antall linjene med kode jeg ikke engang husker, men jeg vet at så lenge jeg må kode noe som helst, noensinne, så kommer jeg ikke til å lage spill. Har tålmodighet til mye rart. Kan male små figurer, til og med andres figurer. Kan photoshoppe. Kan lære meg illustrator igjen, brukte det mye før men ikke brukt det på 5 år. Jeg kan poly-modellere, men er ikke så flink på low poly ennå, men prøver å ha så ryddige mesher som mulig. Uvw-unwrappinga begynner å komme på stell. Lys og slikt er jeg ganske flink til å ordne. Relativt flink med materialer og fikse dem med norm-maps, bumps. Ikke så god på specularmaps.
Kan mekke litt musikk. Men som sagt koding. Det har jeg prøvd på flere ganger, men orker ikke. Sikkert samme grunn til at jeg aldri har likt å gå på skolen. For mye døll skriving.
Demake ville ha revet av seg resten av håret sitt hvis han visste hvor dårlig jeg er i matte f.eks.
"Kjære dagbok"-innlegg. Slutt. Takk.
 

Yetipants

Mein Gampf
Medlem av ledelsen
#13
Hørm. Skulle ønske jeg kunne sagt at det bare er å trykke og dra så har du et spill, men... vel, for meg er Unity trykk-og-dra. Det jeg liker er at jeg kan kode AKKURAT det jeg vil ha og så bare ER det der, uten at jeg må mekke grafikkmotor, sette opp komponenthierarki, sørge for at ikke lydbufferet driter på seg osv. Men nei, selv GUI-systemet krever at du programmerer det. Bare så det er sagt: Jeg har ikke vært borti noe GUI-system verdt en sur sild som bare er "egne bokser man kan trykke i"; Visual Studio har jo det superenkle WPF-systemet nå om dagen, men også der ligger det kode bak. Så, sårri bårri. Hør om Sofie har lyst til å lære seg C# så kan hun være programmer girl.
 

Yetipants

Mein Gampf
Medlem av ledelsen
#14
Lite addendum til GUI-greia: Mekk en GUI Skin-asset i prosjektet ditt. Mekk en public GUISkin-variabel kalt menuSkin i HUD-scriptet. Sett menuSkin til å være det skinnet du akkurat mekka, og legg inn linja GUI.skin = menuSkin; før du tegner opp menyen. Blæm, nå kan du endre menyskinnet nøyaktig slik du vil. Dette innebærer å mekke masse små bilder for å representere knapper, toggles, menybakgrunn og så videre, og bør være rimelig selvforklarende for den som er interessert. Jeg mekka litt akkurat nå, og fikk fine blå knapper og en fin grønn bakgrunn (ps det ble ikke så fint). Uansett, det var ikke komplisert. Koz deg med det hvis du vil.
 

Yetipants

Mein Gampf
Medlem av ledelsen
#15
Trippelpost vettø. Har dilla mer i dag, men er ikke helt klar for neste del av tytorialen helt ennå. Lyst til å se hvordan ting ligger an? Unity lar deg spille rett i leseren, vettu! Kan prøve å huske å oppdatere etter hvert som det kommer mer. Akkurat nå: To forskjellige våpen, HUD som funker (delvis), ooog det er vel det. Bytt våpen med høyre mustast Alt er du grei. Hvis du går tom for ammo må du foreløpig restarte brettet (låll) med Esc og Restart Game.
 

Yetipants

Mein Gampf
Medlem av ledelsen
#16
Kvadruppelpost for seieren. Det har vært en stund siden sist nå, men det er en kombinasjon av lite tid og en del sære bugs som må ta skylda for akkurat det. Det er fortsatt minst en sær bug igjen, men veit du hva, her er en ny versjon av Det Offesiele Unitytorial-Spillen (R)(C)TM. Som i, det er fortsatt ikke så mye å gjøre etter at du har skutt Johannes (når det er sagt kan jeg bare legge til akkurat så mange Johanneser jeg vil, jeg bare gidder ikke ennå), men du kan i det minste gå deg vill i skauen (den nydelige skauen) og prøve ut de fantastiske våpnene mine: Laserpistol (suger), granatkaster (suger mindre) og railgun (den er SWEET og ligner faktisk ganske mye på den i Quake 3 bare at den ikke skyter gjennom folk (fordi jeg ikke gidder)). Skal skrible litt i løpet av uka bare jeg får tid, sånn at dere også kan mekke dere noen herlige våpen. LIKE for Justin Bieber SHARE for One Direction COMMENT for Rihanna IGNORE if u = rapist.
 

kakarlsen

Høyere yrkesfaglig
#17
Hvordan inverterer jeg siktinga?

Edit: Åja der ja. Enda godt du hadde tenkt på det :cool:
 

Yetipants

Mein Gampf
Medlem av ledelsen
#18
Ehe, det er en av de sære buggene jeg ikke har fått fiksa ennå. Men ja.
 

Yetipants

Mein Gampf
Medlem av ledelsen
#19
Jarra. Gadd å ta en liten runde med no bugfiksings og no mekkings i kveld, så nå kan dere nyte Sir, You Are Being Johannesed i all sin prakt. Mer ammo! Flere fiender! Mer landskap! Invert på musa! En borg jeg nesten kom i gang med å bygge!

Ja, og det er muligens litt skumlere å gå seg bort i skauen nå. =D