3D-Platformer

1.2.2019
Tässä harjoituksessa tehdään Unityllä 3D-tasohyppely, jossa tarkoituksena on kerätä kentän kaikki aarteen mahdollisimman nopeasti, varoa kentältä putoamista ja väistää vihollisia.

Tässä harjoituksessa opittavia uusia asioita:
  • Standard Assets
  • Tekoäly ja reitinhaku (Nav mesh agent)
  • Äänet
  • Pysyvän tiedon tallentaminen
  • Valaistus
  • Maaston 
  • Älykäs kamera
  • Scenen vaihtaminen
Esimerkki valmiista työstä:

1. Pelihahmo ja kontrollit

Luo uusi 3D-projekti ja tee sille oma kansio.

Hae projektiisi Asset Storen (Windows | Asset Store) kautta Third Person Character (by Unity Technologies). Download ja sen jälkeen Import. Tämän jälkeen sinulla pitäisi olla Assets -kansiossa uusi kansio nimeltä Standard Assets, joka sisältää paljon valmiiksi tehtyjä assetteja pelin tekemistä helpottamaan.

Standard Asseteista käytämme lähinnä pelihahmoa, koska oman 3D-hahmon animointi ja skriptaaminen ei ole tämän harjoituksen teema.

Luo Assetsiin kolme kansiota: Prefabs, Scenes ja Scripts joihin asetellaan tarvittavat assetit.

Tuo ThirdPersonController Prefab PlayerArmature Hierarchyyn raahaamalla se sinne ja nimeä se uudelleen Player –nimellä.

Nyt sinulla pitäisi olla Scenellä Player-niminen hahmo, jolla on komponentteina Rigidbody, Capsule collider, Third Person User Control ja Third Person Character Script. Play-tilassa hahmon pitäisi pudota pohjattomaan kenttään, koska Rigidbody tuo sille painovoiman vaikutuksen, eikä ketässä ole maata mikä ottaisi vastaan. Luo pelihahmon alle taso, vaikkapa Plane –Gameobjekti, jotta pelihahmo ei putoaisi.

Pelihahmolla on eri logiikka maassa ja ilmassa liikkumiselle. Välilyönti toimii oletuksena hyppy-näppäimenä ja WASD:illa ohjataan hahmoa, Shift-näppäimellä hahmo juoksee. Näppäinkomentoja voidaan muuttaa toiminnolla Edit | Project Settings | Input. Voit lisäillä kenttään erimuotoisia objekteja joiden päällä hyppiä. Voit tallentaa tämän Scenen vaikkapa nimellä PlayerTest. Tallenna myös koko Unity-projekti.

2. Aarteet ja pistelaskuri


Teemme seuraavaksi pelin varsinaiset kentät: Kolme pelattavaa kenttää (luonnollisesti jokainen edellistä vaikeampi) sekä aloitus-Scene.

Talleta Player projektin Prefabiksi. Luo uusi scene. Tästä tulee tasohyppelypelisi ensimmäinen kenttä.

Tässä scenessä tulemme luomaan seuraavat asiat:


- Kerättävä aarre (prefab), täytyy sisältää Collider-komponentin jotta törmäykset havaitaan.

- Skripti aarteelle

- Tagi aarteelle jotta kenttälogiikka löytää aarteet

- CharacterController ei saa mennä esineiden sisään, joten aarteesta pitää tehdä ns. Trigger

- Lisäksi tarvitaan peliskripti joka laskee aikaa ja kerättyjä aarteita. Tästäkin kannattaa tehdä prefab.


Piirrä ensin sopiva aarre, vaikkapa aarrearkku. Tee siitä prefab. Voit lisätä siihen vaikka keltaisen värin lisäämällä ensin assetteihin uuden materiaalin ja määrittämällä tämän keltaiseksi, lisäämällä metallisuutta, ja määrittämällä Treasurelle sitten tämä materiaali. Anna aarteelle nimi Treasure. Aseta aarteen prefabille myös tagi "Treasure". Aarre voi olla vaikkapa arkun tai kolikon näköinen. Alla oleva kolikko on tehty yksinkertaisesti kavennetulla sylinterillä, jolle on annettu kullanvärinen materiaali.



Tuollainen kolikko voisi olla hyvä laittaa pyörimään Hello World -harjoituksesta tutun skriptin avulla.

Valitse pelaajahahmo Prefab ja aseta sille tagiksi ”Player” Inspectorin ylälaidassa.

Tee uusi skripti nimeltä Treasure ja raahaa se Treasure-prefabin komponentiksi. Tee Treasure-skriptiin seuraava funktio:





Emme käytä perus OnCollision –tarkistusta, koska silloin pelihahmo voi mennä objektin sisälle ja kosketuksia voi tulla useita kerralla. Aseta aarteen Collider –komponentista isTrigger päälle, koska käytämme nyt Trigger-ominaisuutta. Nyt peliä testatessa pelaajan törmätessä aarteisiin aarre katoaa.

Tee tyhjä peliobjekti nimeltä Level. Luo uusi skripti nimeltä Level ja raahaa skripti samannimiseen peliobjektiin. Tee peliobjektista Prefab.

Aseta Level-skriptiin tämä koodipätkä.

https://pastebin.com/TMfB18qM

Lisää vielä Treasure-koodin OnTriggerEnteriin yksi rivi jotta aarteet saadaan laskettua:




Nyt pelin pitäisi seurata aarteiden keräämistä.

Rakenna nyt tästä kentästä ensimmäinen, helpoin kenttä, jossa aarteet pitää kerätä. Aseta aarteita ainakin kolme. Voit rakentaa venytettyjä laattoja joilla hyppiä kuten opettajan esimerkissä. Aarteen voi asettaa laatan lapseksi (child) jolloin aarre liikkuu laatan mukana. Muista että voit säätää myös pelihahmon nopeutta ja hyppyvoimaa jotta se pääsee esteiden yli. Tallenna scene vaikkapa nimellä Level1.

3. Kamera 

Jos ja kun haluat asettaa kameran seuraamaan pelaajaa, lisää Hierarchystä scenelle Cinemachine FreeLook Camera:



Asena FreeLook Cameran Inspectorista seuraamisen kohteeksi PlayerArmature ja Look At kohteeksi PlayerCameraRoot. Kokeile peliä. Tutustu muihinkin kameran asetuksiin, ja säädä sen toiminta mieleiseksesi.
Aseta Virtual Cameran Follow-ominaisuus seuraamaan pelihahmon PlayerCameraRoot -peliobjektia. Tässä tapauksessa se on valmiiksi tehty, itse tehtyyn peliobjektiin sellainen pitäisi erikseen lisätä lapsiobjektiksi.

Testaa peliä. Kamera seuraa nyt pelihahmoa lukittuna, ja hiirellä voit katsella hahmoa eri suunnista. Tutustu nyt Virtual Cameran asetuksiin, ja tee siitä parempi kolmannen persoonan kamera.

Aseta kamera sivuttaissuunnassa sopivaan kohtaan Camera Side -säätimellä. Camera Distancella voit säätää kameran etäisyyttä kohteesta. Body | Damping | Z-arvolla voimme säätää kuinka nopeasti kamera saavuttaa pelaajan kun pelaaja lähtee liikkeelle, testaa. Jo nyt kamera on paljon miellyttävämpi kuin pelkästään pelihahmoon lukittu lapsiobjekti-kamera.

Aseta vielä Virtual Cameran Body-asetuksista Camera Collision Filter asentoon Default, jolloin kamera ei jää törmäyslaatikoita sisältävien objektien taakse piiloon.


4. Tason pelilogiikka ja alkuruutu


Onnistuminen = kaikki aarteet kerätty

Epäonnistuminen = Tippuu pois kartalta

Epäonnistuminen voidaan määrittää yksinkertaisesti tarkkailemalla tippuuko pelaajan transform.position Y-koordinaatti liian pieneksi. Sekä onnistumisen että epäonnistumisen jälkeen haluamme näyttää viestin suorituksesta ja odottaa että pelaaja painaa nappia, josta pääsee suorituksesta riippuen joko seuraavaan kenttään tai ensimmäiseen kenttään.

Sceneihin voi skriptissä viitata nimillä tai numeroilla. Tässä tapauksessa numerointi on kätevämpää. Numerointi tapahtuu File-valikossa Build Settings –toiminnolla. Lisää kenttä 1 toiminnolla Add current. Lisäämme myöhemmin muut kentät ja aloitus ja lopetusruudut. Numerojärjestystä voi vaihtaa vetämällä.

Lisää Level-skriptin ihan alkuun yksi lisäkirjasto, jolla scenen vaihteluja hallitaan:

using UnityEngine.SceneManagement;

Seuraavaksi tehdään Level-skriptiin seuraavat lisäykset. (huomioi kaksi pastebin linkkiä)

https://pastebin.com/f25B6i5Z

https://pastebin.com/j2fQ6xqR

LevelEndMessage on apufunktio, joka näyttää viestin ja painettavan napin. GUI.Button() palauttaa true kun nappia painettiin.

Päivitä vielä Level-skriptin OnGui-funktio alla olevan näköiseksi:

https://pastebin.com/tDyZZN5b

SceneManager.LoadScene lataa scenen (numeron perusteella, nykyisen scenen numero saadaan GetActiveScene.buildIndex –muuttujasta).

Jos testaat peliä, huomaat että kentän pelattuasi tulee virhe, kun Unity yrittää ladata seuraavaa kenttää, jota ei ole. Tee siis seuraava kenttä. Sama periaate, mutta hieman vaikeampi. Tallenna vaikkapa Level2 -nimiseksi. Lisää myös kenttänumerointiin Build Settingsillä. Pelaajan pudotessa pitäisi tulla nappi josta kenttä alkaa uudestaan. Etsi koodista miten tippumiskorkeutta säädetään!

Tee kenttiä niin että niitä on yhteensä kolme. Tallenna kaikki ja lisää ne Build Settingsillä kenttäjärjestykseen.

Seuraavaksi lisäämme alkuruudun. Tee uusi tyhjä Scene ja taiteillaan siihen alkukuva. Luo projektiisi alikansio Assets\Textures. Piirrä kuva vaikkapa Paintilla ja nimeä TitleScreen.png ja tallenna se Textures-kansioon.

Luo uusi tyhjä peliobjekti. Luo uusi skripti nimeltä TitleScreen ja lisää se tyhjään peliobjektiin. Tallenna Scene nimellä TitleScreen ja lisää se Build Settingsillä ensimmäiseksi.

Lisää myös TitleScreen -skriptin alkuun using UnityEngine.SceneManagement;

Lisää TitleScreen –skriptiin seuraavat asiat:


Projektiin tuotu alkukuva täytyy vetää Inspectorissa alkuscenen tyhjän Gameobjektin Title Texture-kenttään jotta skripti osaa piirtää sen. Peli lähtee käyntiin 1. kentästä kun nappia painetaan.

Tehdään Level-skriptin OnGUI-funktioon muutos, että peli palaa tylysti alkuun kun kaikki kentät on läpikäyty:



Tallenna kaikki ja testaa muutoksia.

5. Tallentuvat ajat ja luovuttaminen


Normaalisti uuden scenen ladatessa kaikki olemassaolleet peliobjektit tuhoutuvat. Voidaan myös kutsua funktiota GameObject.DontDestroyOnLoad() pysyvän peliobjektin luomiseksi. Toteutetaan tämän avulla peliin aikaennätysten tallentaminen.

Luo uusi skripti nimeltä Hiscores. Älä vielä lisää sitä mihinkään peliobjektiin.

Jos pysyvä peliobjekti olisi suoraan osa sceneä, siitä syntyisi monta kopiota kun scene ladataan uudestaan. Tämän vuoksi täytyy luoda skriptissä ja tarkistaa onko jo olemassa. Luodaan pysyvä peliobjekti jo olemassa olevaan TitleScreen –skriptiin ja siellä Start() –funktioon:



Peliobjektin voi rakentaa täysin skriptistä käsin ja lisätä siihen komponentteja ja skriptejä. Toinen vaihtoehto olisi ollut tehdä hiscores-skriptille prefab ja instantioida se skriptistä.

Seuraavaksi lisätään Hiscores-skriptille seuraava sisältö.

https://pastebin.com/njHTzY4n

Hiscores-skripti käyttää C# Dictionary-luokkaa aikojen tallettamiseen. Hakuavaimena toimii kentän järjestysnumero, sama kuin Build Settingsissä, 1 = ensimmäinen kenttä. Jos kenttäkohtaista aikaa ei ole tallennettu, tallennetaan se. Jos aika on olemassa, tarkistetaan onko uusi aika parempi. Funktio GetTimes() käy läpi dictionaryn ja palauttaa talletetut ajat merkkijonona joka voidaan näyttää ruudulla. \n tarkoittaa rivinvaihtoa.

Kentän läpäistessä kutsutaan StoreTime() –funktiota, ja ajat näytetään alkuruudussa GetTimes() –funktiolla.

Jos peliä ei käynnistetty alkuruudusta, Hiscores-peliobjektia ei ole vielä olemassa. Olemassaolo täytyy tarkistaa jottei tule virheitä, joten lisää keltataustainen koodi Level-skriptiin:

Sitten lisätään TitleScreen –skriptin OnGUI()-funktioon alta lihavoidut rivit:



Peli toimii nyt muuten hyvin, mutta hiscoret menetetään kun peli käynnistetään uudestaan. Unity tukee pysyvän tiedon tallentamista: Windowsissa registryyn, mobiililaitteilla laitteen muistiin. Tallentaminen tapahtuu PlayerPrefs-luokan kautta. Se on Dictionary-luokan kaltainen tietovarasto jossa avaimet ovat merkkijonoja, ja se voi tallentaa kokonais- tai liukulukja ja merkkijonoja. GetInt(), SetInt() jne.

Jotta PlayerPrefsiä voisi käyttää, pitää asettaa pelin kehittäjän nimi ja pelin nimi, joilla tieto tunnistetaan, eli Edit | Project Settings | Player.

6. Äänet


Peli kaipaa vielä vähän tehosteita. Äänen soittaminen skriptistä on tarkoitettu tehtäväksi AudioSource –komponentilla, mutta se onnistuu yksinkertaisimmillaan ilmankin. Jos halutaan vain soittaa ääni scenen tietyissä paikoissa, on siihen olemassa staattinen funktio AudioSource.PlayClipAtPoint(). Esimerkiksi lisää Treasure-skriptiin alla oleva pätkä ja vedä sitten asseteista haluamasi ääni treasure-prefabin Collect Sound –kenttään.

Voit hakea ääniä esim. creative commons haulla, tai vaikkapa freesound.org:sta (äänten lataaminen vaatii kirjautumisen, ilmainen).

Huomaa, että Audio Reverb Zonella (GameObject | Audio | Audio Reverb Zone) voit luoda erilaisia 3D-äänimaisemia, eli luomasi äänet (esimerkiksi kävely) kaikuu Cave-tilassa juuri kuin luolassa. Äänitiloja on useita, kannattaa kokeilla.

7. Vihollinen ja tekoäly


Peliin pitää saada muutakin haastetta kuin putoaminen. Lisätään seuraavaksi vihamielisiä robotteja jotka hakeutuvat kohti pelaajahahmoa ja tuhoavat kosketuksesta.

Luo tyhjä peliobjekti, jonka nimeät Robot-nimiseksi. Luo robottihahmo esimerkiksi Capsulella ja lisää pääksi Sphere, josta teet Robot-objektin lapsen raahaamalla se Hierarchyssä Capsulen päälle. Numeroarvoilla Inspectorista editoiminen on tarkempaa kuin nuolilla. Voit poistaa Collider-komponentin spherestä, koska capsulella on jo sellainen. Voit lisätä muitakin osia robottiin, aseta kaikki capsulen child-objekteiksi ja poista muista kuin capsulesta Collider. Voit lisätä myös materiaaleja robottiin. Alla kuva opettajan tappajarobotista.





















Aseta robotin pään GameObjektin nimeksi"Hehku", jotta voimme löytää sen kätevästi skriptillä. Lisää Light-komponentti Robotin pää-palikkaan (löytyy Rendering-objekteista), Type=Point, Range esim. 5, Color vaikkapa vihreä. Intensityllä saat muutettua valon voimakkuutta. Muutamme värin skriptillä punaiseksi kun robotti huomaa pelaajan.

Nimeä robotti Hierarchyssä Robotiksi. Aseta sille Nav Mesh Agent komponentti, jonka avulla robotti tulee liikkumaan kentässä. Lopuksi lisää vielä RigidBody jotta törmäys pelaajahahmoon toimii.

Nav Mesh Agent liikuttaa robottia itsekseen, joten emme halua että se ja fysiikkasimulaatio ”tappelevat”. Joten klikkaa Rigidbodystä päälle kaikki akselit Constraints -> Freeze Position & Freeze Rotation. Näin fysiikka ei liikuta robottia, mutta havaitsee silti törmäykset.

Luo vielä uusi C#-skripti nimeltä Robot ja lisää se robottiin. Tallenna robotti prefabiksi ja lisää yksi ensimmäiseen kenttään testausta varten. Seuraavaksi on vuorossa robotin logiikan ohjelmointi. (koodin saa valmiina tästä: https://pastebin.com/TpEv278h)

Raycast on näkymätön säde joka lähtee alkupisteestään tiettyyn suuntaan ja havaitsee tielle osuneen lähimmän Collider-komponentin. Sitä voidaan käyttää pelaajan havaitsemiseen. Layereilla voidaan valita mitä raycast havaitsee. Muuta robotti-prefabin layeriksi Ignore Raycast, jottei raycast havaitse robottia itseään. Unity kysyy muutetaanko myös lapsiobjektien layer, vastaa kyllä.

Robot skriptistä löytyy mielenkiintoista koodia. Tutustu niihin ja yritä ymmärtää mitä eri funktiot tekevät.

Physics.Raycast ottaa parametreiksi säteen lähtöpisteen, suunnan, RaycastHit –objektin johon tulos talletetaan (siitä löytyy Collider johon osuttiin) ja bittimaski layereistä joita tarkastellaan. Default-Layerin bitti on 1. Kun pelaajahahmo havaitaan, tallennetaan sen gameObject muistiin target-muuttujaan ja vaihdetaan valon väri punaiseksi. Debug.DrawRay –funktiolla voi visualisoida säteen. Gizmot pitää olla kytketty päälle Game-näkymän oikeasta yläkulmasta.

Voisimme käyttää CharacterController –komponenttia robotin liikuttamiseen, mutta silloin kentässä suunnistaminen pitäisi tehdä itse. Tasolla liikkuvalle hahmolle saa liikkeen ja reitinhaun helposti NavMeshAgent –komponentilla. Reitti eli navigaatiomeshi täytyy rakentaa ensin sceneen.

Lisää käytössäsi olevaan ”maahan” NavMeshSurface komponentti ja klikkaa bake. Jos testatessasi Robottisi katoaa maan sisään, kokeile muuttaa Nav Mesh Agentista Base Offset arvoa. Itselläni 1 oli sopiva (default 0).

Jos robotti ei havaitse pelaajaa, joudut lisäämään pelaajalle tyhjän lapsiobjektin, jolle laitat capsule collider-komponentin (säädät sopivaksi edit collider kohdasta klikkaamalla) ja laita is trigger päälle.


Tutustu robotin skriptin alla oleviin osiin:

Näiden päivitysten jälkeen robotilla on yksi suunta johon se katsoo kentässä. Jos pelaaja kävelee siihen säteeseen, robotti lähtee jahtaamaan pelaajaa. Voit joutua tarkastelemaan robotti-prefabin asentoa kuten itse jouduin.

Robotin liikenopeutta voi muuttaa Nav Mesh Agent –komponentin Speed-kentästä.

Robotin skriptissä on onCollision –funktio, joka kutsuu Level-skriptin Kill-funktiota kun robotti koskettaa pelaajaa. Tuota funktiota ei kuitenkaan vielä ole, sinun pitää tehdä sopiva funktio Level-skriptiin.

Paikallaan saalista odottava robotti ei kuitenkaan paljoa pelkoa herätä, joten olisi syytä saada se katselemaan vähän ympärilleenkin. Sen tekevät seuraavat lihavoidut rivit:
´

Nyt robotti skannaa jonkin verran ympäristöään. Robotteja voi yksilöidä, vaikka ne ovatkin prefabista tehtyjä. Yksittäisille roboteille voi muuttaa arvoja Scenessä, esim. nopeutta ja kääntymiskulmaa. Älä kuitenkaan paina Apply-nappia etteivät customoinnit siirry prefabiin.

Lisää robottiin vielä ääni, joka ilmoittaa että se on havainnut pelaajan.

8. Kameran muokkaus ja Skybox


Lisää kameraan Skybox-komponentti toiminnolla Add Component | Rendering | Skybox. Tällä saat taivaalle näkymään ”taivaan”. Hae Unityn Asset Storesta jokin sopiva Skybox peliisi ja tutustu siihen. Raahaa sitten valitsemasi Skybox-kuva Kameran Skybox –komponentin Custom Skybox –ruutuun.

Testaa peliä.

Toista kameraa voisi käyttää esimerkiksi karttanäkymän piirtämiseen perusnäkymän päälle. Kameran voi asettaa katsomaan pelialuetta suoraan ylhäältä päin ja asettaa piirrtämään ainoastaan vaikkapa kartan seinät ja pelihahmot pieninä ympyröinä. Tällöin HUD-kartta ei veisi liikaa tehoja.

9. Maasto-kenttä


Voit tehdä jonkin kentän maaston päälle. Aloita kentän luominen tekemällä sinne Terrain-objekti. Terrain-objekti aktiivisena komponenteissa on käytössä Terrain-työkalut. Brush-työkaluilla voit säätä ”jumalan pensselin” kokoa ja muotoa.













Raise/Lower terrain –napilla voit kohottaa tai laskea maastoa. Laskeminen tapahtuu Shift pohjassa.

Paint Height on ikään kuin Photoshopin Clone Stamp –työkalu. Shift pohjassa voit valita lähteen, jonka korkeuteen pyritään. Sitten voit klikkailla vaikka jotain vuorta, joka hakeutuu samalle korkeudelle kuin lähde.

Smooth Height tasoittaa muotoja ikään kuin eroosio.

Texture paintilla voit maalata maastoa jollain tekstuurilla. Voit luoda oman tekstuurin tai hakea Asset storesta ilmaisia tekstuureita.

Place Trees –toiminnolla voit asettaa puita kartalle.

Paint Details –toiminnolla voit asetella kartalle yksityiskohtia, vaikkapa kiviä. Niitäkin löytyy asseteista.



Muista lisätä uusi kenttä Build settingsillä oikealle paikalleen.

10. Lisää muokkauksia

Jos sinulla on aikaa ja uskallusta, kokeile tehdä pelistä vielä hienompi uusilla ominaisuuksilla.

Esimerkkejä:
Uudet kontrollit ja niiden kustomoinnit
Partioiva robotti
Liikkuvat alustalaatat
Kuolinanimaatio (Ragdoll)
Lisää efektejä
Ampuminen
Taustamusiikki

Etsi netistä tutoriaaleja, esimerkiksi "Unity C# ragdoll"

No comments:

Post a Comment