3D-Shooter

1.2.2019
Tässä luodaan äärimmäisen yksinkertainen ensimmäisen persoonan ammuskelu (FPS). Koska tässä liikutetaan vain kameraa avaruudessa, voidaan peli nimetä vaikkapa Space Shooteriksi. Alla esimerkkituotos:

http://dattari.fi/opettaja/Unity/Shooter/

Tässä harjoituksessa opittavia uusia asioita:

  • Objektin liikuttaminen nuolinäppäimillä ja hiirellä (fps perustoiminnot)
  • Objektien tuhoaminen
  • Debug.Log käyttö
  • Törmäyksien tarkistaminen
  • Ampuminen (Prefab spawning)
  • Pisteenlasku 
  • Räjähdys partikkeliefektin avulla
  • Tapahtuman ajastaminen Invoke -toiminnolla
  • Tagien hyödyntäminen
  • Toisen peliobjektin funktion kutsuminen
Aloita uusi 3D-projekti Unityllä. Anna nimeksi vaikkapa Space Shooter.

Osa 1 - Kameran liike

Asetetaan kamera liikkumaan WASD-nappien / nuolinäppäinten mukaisesti. Näppäinkomennot luetaan Input -luokan avulla, ja liike toteutetaan Update-funktiossa.

Luodaan uusi C#-skripti nimeltä MoveCamera ja lisätään se Main Camera -peliobjektin komponentiksi.

Lisää johonkin kameran eteen vaikkapa kuutio (GameObject | 3D Object | Cube) jotta kameran liike näkyisi jossain.

Kaksoisklikkaa MoveCamera -skriptitiedostoa, jolloin sinun pitäisi päästä muokkaamaan koodia oletuseditorillasi (todennäköisesti Visual Studio, joka asentuu oletuksena Unityn mukana).

Lisää seuraavat rivit koodia Update() -funktioon:

Vector3 movement = new Vector3();
movement.x = Input.GetAxis("Horizontal");
movement.y = 0.0f;
movement.z = Input.GetAxis("Vertical");
transform.Translate(movement * Time.deltaTime);

Ensimmäisellä rivillä luotava vektori movement on tarkoitettu kuvaamaan kameran (koska tämä skripti on kamerassa) liikettä kolmiulotteisessa avaruudessa. Seuraavilla rivellä määritetään vektorin suunta X-akselilla oletus-Inputeista tulevaksi (oletuksena nuolinäppäimet sivuille tai A ja D näppäimet), Z-akseli tulee pystynuolista tai W&S napeista. Inputteja voi säätää Unityn asetuksista, ottamaan sivuttaisliike esimerkiksi mobiililaitteen kallistamisesta.

Vector3 on kolmiulotteinen liikevektori, joka luodaan akselien asennon perusteella. Skripti pääsee käsiksi omaan peliobjektiinsa transfor.Translate -komponentin avulla. Koska framejen määrä sekunnissa (FPS) vaihtelee koneen tehon mukaan, "askel" täytyy skaalata nykyisen framen kestolla, joka saadaan muuttujasta Time.deltaTime.

Nyt Play-tilassa kameraa voi liikuttaa WASD-napeilla, mutta sen voi havaita ainoastaan seuraamalla aiemmin luotua peliobjektia.

Kameran liike on melko hidasta, eikä kameraa voi kääntää. Muokataan sen vuoksi Update-funktiota hieman, ja lisätään sen yläpuolelle pari muuttujaa seuraavalla tavalla:

Lisättävät muuttujat heti luokkamäärittelyn jälkeen ennen Start-funktiota:

    public float moveSpeed = 6.0f;
    public float rotateSpeed = 3.0f;

Nuo liukulukumuuttujat selittävät todennäköisesti itse itsensä.

Päivitä nyt Update-funktio alla olevan kaltaiseksi:


Tuossa lisätään kameraan pyörittäminen akselien ympäri (transform.rotation) euler-kulmien avulla. Kulmat saadaan hiiren liikkeestä. Koska muuttujat ovat julkisia (public), niitä voidaan muuttaa suoraan Unityssä kameran Inspector-paneelista.

Osa 2 - Ampuminen

Luodaan uusi C#-skripti nimeltä Shooter, ja lisätään se Main Cameraan. Ampumisen olisi voinut kirjoittaa myös MoveCamera -skriptiin, mutta hyvä komponenttiajattelu sanoo että jokainen skripti toteuttaa yhden erillisen toiminnon.

Luodaan ammuksille prefabi, eli vaikkapa pieni kuutio (skaala 0.1 kaikilla ulottuvuuksilla) jossa on RigidBody & BoxCollider -komponentit. Vedetään se Hierarchystä Assets -näkymään, jotta siitä tulee Prefabi, ja se muuttuu Hierarchyssä sinertäväksi. Annetaan prefabille nimi Shot, ja poistetaan alkuperäinen hierarkiasta.

Tehdään Shooter.cs -skriptistä seuraavaksi alla olevan mukainen:

https://pastebin.com/yjYngHJt

Tallenna skripti, ja "raahaa" aiemmin luotu prefabi kameran Shooter-komponentin shotPrefab -kohtaan.

Jos olet tehnyt kaiken oikein, kameran pitäisi pystyä ampumaan hiiren näppäintä painamalla.

Instantiate luo peliobjektin annetulla prefabilla, sijainnilla ja kulmalla (hakee sijainnin ja kulman isäntäobjetista eli kamerasta). Koska luodeilla on rigidbody, fysiikan lait vaikuttavat niihin, ja painovoima vetää niitä siksi alaspäin.

Luo seuraavaksi kenttään useita isoja värikkäitä 3D peliobjekteja, joita pelissä on tarkoitus ampua. Peliobjektiin saa värin liittämällä siihen materiaalin. Luo uusi materiaali (nimeltään vaikkapa punkku) ja säätämällä sen väri materiaalin Inspectorin Albedosta. Raahaa materiaali luomasi 3D-objektin komponentiksi, ja niin objekti saa värin. Ammuttaville kohteille pitää lisätä myös BoxCollider -komponentti, jolloin ne reagoivat törmäyksiin. Tee ammuttavasta kohteesta ensin prefab, ja raahaa niitä sitten scenelle.

Lisätään Shot-prefabille Inspectorissa tagi "shot", jotta ammuttavat kohteet tunnistavat törmäyksissä laukaukset ja reagoivat niihin oikein. Lisää uusi tagi "shot" alla olevan giffin mukaisesti:


Tämän jälkeen shot-prefabille pitää vielä lisätä tagilistalle lisätty shot-tagi. Kun Collider-komponentin omaavien objektien törmäys havaitaan, kutsutaan automaattisesti peliobjektin OnCollisionEnter() -funktiota.

Luo nyt Target-niminen C#-skripti, ja lisää se kohdelaatikon prefabin komponentiksi. Tee siihen aiemmin mainittu OnCollisionEnter() -funktio, ja siitä allaolevan kaltainen:

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "shot")
        {
            //tuhoaa itsensä ja törmääjän
            Destroy(gameObject);
            Destroy(collision.gameObject);
        }
    }

Huomaa, että tämä funktio ottaa sisään viittauksen törmäävään objektin nimellä "collision".

Nyt kohteiden pitäisi tuhoutua kun ammus osuu niihin. Myös osuva ammus tuhoutuu.

Osa 3 - Räjähdysefekti

Luodaan seuraavaksi efekti tuhoutumiselle. Luodaan prefab nimeltään Explosion (eli luo tyhjä GameObject ja raahaa se asseteihin. Luo siihen ParticleSystem -komponentti. Aseta efektillä muodoksi (Shape) Sphere ja kestoksi (Duration) 1 sekunti. Poista alkuperäinen Explosion Hierarchystä häiritsemästä.

Lisää Target-skriptin alkuun seuraava muuttujamäärittely:

public GameObject explosionPrefab;

Tuolla luodaan viittaus räjähdys-prefabiin. Sitten lisää aiemmin luotuun OnCollisionEnter()-funktioon heti peliobjektien tuhoamisen jälkeen räjähdysefektin luomis-skripti:

GameObject.Instantiate(explosionPrefab, transform.position, transform.rotation);

Ylläolevassa koodissa instantioidaan räjähdysefekti. Raahaa vielä Target-prefabin kenttään "Explosion Prefab" aiemmin luomasi Explosion-prefab.

Nyt osuman sattuessa peliin pitäisi ilmestyä jonkinmoinen räjähdysefekti. Ongelma on, että se on ikuinen. Sen vuoksi luomme siihen muutaman sekunnin itsetuhomekanismin.

Luo uusi skripti nimeltä TimedDestroy, aseta se Explosionin komponentiksi, ja tee siihen seuraava sisältö:

public class TimedDestroy : MonoBehaviour {

    public float destroyDelay = 4.0f;

void Start () {
        Invoke("DestroySelf", destroyDelay);
}

    void DestroySelf()
    {
        Destroy(gameObject);
    }
}

Start-funktiota kutsutaan kun räjähdys luodaan, ja siinä kutsutaan Invoke-funktiota. Invoke kutsuu DestroySelf -nimistä funktiota destroyDelay -määrän sekunteja kuluttua. Nyt räjähdysefekti kestää neljä sekuntia.

Osa 4 - Pisteet


Luodaan peliin tyhjä peliobjekti nimeltä Game, ja tehdään siihen komponentiksi skripti nimeltä Game. Ideana on, että Game-skriptin TargetHit() -funktiota kutsutaan kun kohteisiin osuu. Tehdään Game-skriptistä seuraavanlainen:


https://pastebin.com/9ZUb45Ug

Tuossa määritellään aluksi kokonaislukumuuttuja score, Start-funktiossa (kun peli siis alkaa) se määritetään nollaksi, ja TargetHit -funktiossa siihen lisätään 100. Lisäksi TargetHit näyttää pisteet consolessa (Assets-paneelin vieressä).

OnGUI() piirtää pisteet ruudulle.

Lisää vielä Target-skriptiin samaan kohtaan missä peliobjektit tuhotaan, seuraava rivi:

GameObject.Find("Game").GetComponent<Game>().TargetHit();

Tuo kutsuu toisen objektin (Game) komponenttia (Game), ja sen funktiota (TargetHit). Tässä esimerkki siitä, että skriptit voivat kutsua toisiaan ja tehdä yhteistyötä. Tuo ei kuitenkaan nyt toimi, koska TargetHit() -funktio on merkitty private -tyyppiseksi. Tee siitä public, jolloin sitä voidaan kutsua luokan ulkopuolelta.

Nyt pelin pitäisi toimia siten, että tuhotuista peliobjekteista tulee pisteitä.

Lisämuokkausta jos sinulla on aikaa ja energiaa


  • Hienompi räjähdysefekti partikkelisysteemin säädöillä
  • Liikkuvia kohteita (kts Hello World -tutoriaali)
  • Haulikko-tyyppinen pyssy, joka ampuu useita kuteja yhdellä laukauksella
  • Luotien poistaminen 10 sekunnin päästä ampumisesta
  • Hienompia kohteita yhdistelemällä 3D-objekteja, tai tuomalla niitä Blenderistä.

No comments:

Post a Comment