05. Logika hry

Keď už máme potrebné ovládanie, je čas zapracovať do hry samotnú logiku. Dnes tu nebude nič prevratne nové, všetko sme už niekde skôr použili. Ale tréning robí majstra.

Otvorte si svoj projekt alebo si stiahnite hotový: SpaceGame_04.zip

Sandra má za úlohu prežiť, kým je príde niekto zachrániť. Za ten čas jej dochádza kyslík a musí si ho dopĺňať z voľne pohodených nádrží. Aby hra mala nejaké napätie, tak získavanie kyslíka musí niečo stáť, napríklad obmedzené palivo v tryskách alebo zdravie po zrážke s UFOm. Ale všetko postupne. Začnime s časom a kyslíkom.

Kostra hernej logiky

Aké údaje si k tomu budeme musieť uchovávať?

  • Koľko času ostáva do príchodu záchrany
  • Koľko kyslíka ešte ostáva Sandre

A ako bude fungovať základná logika hry?

  • Ak čas zostávajúci do príchodu klesne na nula – Sandra vyhrala
  • Ak kyslík klesne na nula – Sandra prehrala
  • Čas klesá neustále automaticky
  • Kyslík klesá neustále automaticky
  • Kyslík stúpa po zachytení kyslíkovej fľaše

Pre hernú logiku si vytvorte nový game objekt Hra a skript Main.cs, ktorý na priraďte na game objekt Hra.

Odpočítavanie času

Aby sme si mohli uchovávať informáciu o tom, koľko času a koľko kyslíka ostáva, vyrobme si na to vlastnosti v triede Main:

public class Main : MonoBehaviour
{
    // Kolko casu ostava do zachrany (v sekundach)
    public float time = 60;

    // Kolko kyslika ostava (v percentach)
    public float oxygen = 100;

    // Start is called before the first frame update
    void Start()
    {
        
    }

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

    }
}

Čas má klesať neustále, takže kam dáme príkaz na zmenšovanie času? Do Update(). A o koľko znížime čas v jednom snímku? O toľko, koľko času v danom snímku uplynulo (Time.deltaTime). A hneď aj skontrolujme, či už čas neklesol na nulu:

    void Update()
    {
        // Zniz cas o deltaTime
        time -= Time.deltaTime;

        // Ak uz cas klesol na nulu alebo pod nulu
        if (time <= 0)
        {
            // Tak je hra vyhrata
            // TODO
        }
    }

Možno sa pýtate, prečo kontrolujeme, či je čas menší alebo rovný nule (time <= 0) a nie iba, či je rovný nule (t == 0). Je to podobné ako s UFO, či dorazilo do cieľa. Keď sa čas znižuje po rozmanitých dielikoch, tak nemusí klesnúť presne na nulu, ale napríklad na -0.02. Keby sme sa v takom prípade pýtali, či je čas rovný nule, tak počítač odpovie: „Nie, čas nie je rovný nule“. A pokračuje so znižovaním času do mínus nekonečna.

Keď som bol malý, mama ma raz nechala samého doma s inštrukciami: „V rúre sa pečie koláč, keď bude červený, tak rúru vypni a koláč vyber“. Tak som sedel pred rúrou a pozeral na koláč. Najprv bol biely, potom bol žltý, potom oranžový, potom hnedý, potom tmavohnedý a potom už bolo neskoro. Tažký je život programátora.

TODO je univerzálna prokrastinačná formulka, že toto dorobíme neskôr

A aby sme videli, koľko času ostáva, vyrobme si do scény nejaký text, ktorý to bude ukazovať.

  • Vytvorte v scéne UI Text a pomenujte ho Casomiera.
  • Vyplňte ho nejakou povzbudzujúcou hláškou:

Teraz prepojíme tento UI Text s logikou v hre, aby ukazoval aktuálny čas. Akurát herná logika v triede Main zatiaľ nevie o tom, že vôbec existuje nejaký Casomiera. Preto jej vytvoríme novú vlastnosť typu Text a Casomieru do nej priradíme:

public class Main : MonoBehaviour
{
    // Kolko casu ostava do zachrany (v sekundach)
    public float time = 60;

    // Kolko kyslika ostava (v percentach)
    public float oxygen = 100;

    // Text, ktory zobrazuje cas
    public Text timeDisplay;

Casomiera je už teda je napojená na Main, ešte aby jej Main aj povedal, aký čas má ukazovať. Chceme, aby sa to zmenilo vždy, keď sa čas zmení, takže v každom Update():

    void Update()
    {
        // Zniz cas o deltaTime
        time -= Time.deltaTime;

        // Ak uz cas klesol na nulu alebo pod nulu
        if (time <= 0)
        {
            // Tak je hra vyhrata
            // TODO
        }

        // Aktualizuj cas na obrazovke
        timeDisplay.text = "Time to rescue: " + time.ToString("00.00");
    }

Uložiť, spustiť, potešiť sa, ako to pekne funguje:

Odpočítavanie kyslíka

Kyslík sa bude takisto ako čas odpočítavať neustále – takže v Update(). Ale je tam jeden rozdiel. Čas za 1 sekundu klesne o 1 sekundu, to dá rozum. Ale zásobu kyslíku počítame v percentách. O koľko percent klesne kyslík za 1 sekundu? Zatiaľ sa pre jednoduchosť dohodnime, že kyslík bude klesať 3x rýchlejšie ako čas. Nech má Sandra motiváciu naháňať sa za fľašami.

    void Update()
    {
        // Zniz cas o deltaTime
        time -= Time.deltaTime;

        // Zniz kyslik o 3-nasobok deltaTime
        oxygen -= 3 * Time.deltaTime;

        // Ak uz cas klesol na nulu alebo pod nulu
        if (time <= 0)
        {
            // Tak je hra vyhrata
            // TODO
        }

        // Aktualizuj cas na obrazovke
        timeDisplay.text = "Time to rescue: " + time.ToString("00.00");
    }
  • Vytvorte UI Text s nazvom Kyslikomiera a napojte ho na Main podobne ako Casomieru.
  • Pridajte do kódu triedy Main príkaz, ktorý bude aktualizovať text v Kyslikomiere.

A ešte nám chýba overenie, či už kyslík neklesol na nulu:

    void Update()
    {
        // Zniz cas o deltaTime
        time -= Time.deltaTime;

        // Zniz kyslik o 3-nasobok deltaTime
        oxygen -= 3 * Time.deltaTime;

        // Ak uz cas klesol na nulu alebo pod nulu
        if (time <= 0)
        {
            // Tak je hra vyhrata
            // TODO
        }

        // Ak uz kyslik klesol na nulu alebo pod nulu
        if (oxygen <= 0)
        {
            // Tak je hra prehrata
            // TODO
        }

        // Aktualizuj cas na obrazovke
        timeDisplay.text = "Time to rescue: " + time.ToString("00.00");

        // Aktualizuj kyslik na obrazovke
        oxygenDisplay.text = "Oxygen: " + oxygen.ToString("00") + "%";
    }

Pripočítavanie kyslíka

Kedy chceme pripočítavať kyslík? Keď Sandra narazí do poletujúcej fľaše. My sme si už minule predpripravili kus kódu, kde konzumujeme fľašu. Je to v skripte Astronaut:

    private void OnCollisionEnter2D(Collision2D collision)
    {
        Debug.Log("Kolizia!");

        // Ak sme sa zrazili s konzumovatelnym kyslikom
        if (collision.gameObject.tag == "Consumable")
        {
            // Tak skonzumuj kyslik
            // (toto zatial nemame)

            // A zlikviduj kyslikovy objekt
            Destroy(collision.gameObject);
        }
    }

Skonzumovať kyslík tu znamená, doplniť si ho z fľaše do skafandra. Objem kyslíku si evidujeme v skripte Main a skript Astronaut o ňom ale nevie, lebo každý je na inom game objekte (Main je na Hra a Astronaut je na Sandra). Tak napojíme Main na Astronauta, takisto ako sme napájali Casomieru a Kyslikomieru na Main.

Vytvorte v triede Astronaut novú vlastnosť, bude verejná (aby sme ju mohli v editore nastaviť), bude typu Main (lebo bude odkazovať na skript Main) a bude sa volať main.

Takéto prepájanie game objektov medzi sebou je podstatná časť práce a logiky v Unity, preto je dobré si to osvojiť ako rutinu.

public class Astronaut : MonoBehaviour
{
    public float speed = 1;
    public float rotationSpeed = 180;

    // Odkaz na hlavny skript, ktory budeme informovat o prijati kysliku
    public Main main;

Keď je takto Sandra prepojená na Hru, tak komponent Astronaut na Sandre vie pristupovať k verejným vlastnostiam komponentu Main na Hre, napríklad k stavu kyslíku vo vlastnosti oxygen. A vie ju zvýšiť o kyslík nabratý z fľaše, povedzme o 2 percentá:

    private void OnCollisionEnter2D(Collision2D collision)
    {
        Debug.Log("Kolizia!");

        // Ak sme sa zrazili s konzumovatelnym kyslikom
        if (collision.gameObject.tag == "Consumable")
        {
            // Tak pridaj kyslik
            main.oxygen += 2;

            // A zlikviduj kyslikovy objekt
            Destroy(collision.gameObject);
        }
    }

A to je základná kostra hry.

Máme motiváciu: Prežiť.
Máme niečo, čo nás ohrozuje: Klesajúci čas.
Máme niečo, čo nám pomáha: Kyslíkové fľaše.
A máme prekážku, ktorú musíme prekonávať: Navigáciu lietaním v priestore a otravné UFO.

Ďalšie kroky v budúcnosti by boli napríklad vyladenie konštánt v hre: koľko kyslíku ubúda, koľko pribúda, koľko času na začiatku hry ostáva do záchrany… Pre spestrenie hry je možno pridať nejaké zhoršenie stavu kyslíka pri zrážke s UFO, či zvýšená spotreba kyslíka pri otáčaní sa Sandry. Dajú sa tiež pridať nejaké ďalšie powerupy s tagom „Consumable“ – väčšie fľaše, ktoré obsahujú viac kyslíka, palivo, ktoré zvyšuje Sandre rýchlosť pohybu, vysielačka, ktorá skráti čas do záchrany…

Možností je vždy veľa a nakombinovať ich tak, aby bola hra je chytľavá a dobre hrateľná, nie je ľahká vec.

Zhrnutie

Rôzne game objekty, respektíve skripty na nich, musia v aplikácii navzájom vedieť komunikovať. Hlavný skript musel textovému game objektu nastaviť text. Skript na astronautke musel hre nahlásiť, že pribudol kyslík do skafandra. K tomu slúži prepájanie game objektov, aké sme v tejto lekcii precvičili.

Pri uvažovaní o tom, ako napísať skript, nám pomáha premyslieť si, aké údaje si potrebuje skript evidovať, s ktorými inými game objektami potrebujeme skript komunikovať.

Tiež pomôže uvedomiť si, ktoré veci sa majú kedy odohrať:

  • Ak sa to má odohrať raz na začiatku (napr. vygenerovanie fliaš), ide to do metódy Start()
  • Ak sa to deje permanentne (napríklad ubúdanie kyslíka), ide to do metódy Update()
  • Ak sa to deje, len následkom nejakej udalosti (napríklad pribudnutie kyslíka pri zachytení fľaše), ide to do niektorej z metód súvisiacich s udalosťami (napríklad OnCollisionEnter2D())

Na domácu úlohu

Doplňte do hry, čo sa má stať, keď sa minie kyslík alebo keď sa minie čas. To sú tie dve TODO, ktoré sme si nechali v skripte Main. V prvom prípade sa môže zobraziť nejaká hláška o úspešnej záchrane, v druhom nejaká o smutnom konci. V oboch prípadoch budete chcieť asi aj zastaviť čas, aby sa už hra nehýbala a aby Kyslikomiera alebo Casomiera neklesali do záporných čísel.

Ošetrite pri získavaní kyslíka to, aby kyslík nevystúpal nad 100. Predsa len, mať 105% niečoho… to už vyzerá ako volebná účasť v Bielorusku. Ako to ošetriť? Po pripočítaní kyslíka sa spýtať, či je jeho hodnota viac ako 100. A ak je, tak ju nastaviť na rovných 100.

Tu je na stiahnutie hotový projekt: SpaceGame_05.zip