10. Generovanie game objektov a životný cyklus

Doteraz sme game objekty vytvárali vždy len klikaním v editore – buď ako nový game objekt, alebo z prefabu. Podstatnou zručnosťou je ale vedieť vytvoriť game objekt aj v skripte – napríklad chceme náhodne generovať prichádzajúcich nepriateľov. Nebudeme ich predsa vopred vyklikávať v editore, keď nám ich môže vygenerovať skript.

Poďme si vyrobiť skript, ktorý bude na klikanie myšou generovať nový objekt z prefabu. Najprv si upraceme scénu:

  • Vyrobte z červenej kocky prefab a nazvite ho Kocka
  • Existujúce tri kocky v scéne schovajte cez inšpektor. Nemažte ich. Len ich deaktivujte.
  • Vytvorte nový prázdny game objekt a nazvite ho Kocky. Pod tento objekt budeme v hierarchii generovať nové kocky.
  • Vytvorte nový skript Generator a priraďte ho na game objekt Kocky

Aby náš skript Generator vedel, z ktorého prefabu má generovať objekty, musíme ho na nejaký prefab napojiť. Toto urobíme podobne, ako keď sme prepájali skript na iné game objekty.

  • Vytvorte v skripte Generator novú vlastnosť typu GameObject, nazvite ju objekt.
  • Nastavte cez inšpektor do tejto vlastnosti ako hodnotu prefab Kocka z assetov.

Do vlastnosti typu GameObject sa dá priradiť ako hodnota aj prefab, lebo prefab je špeciálny typ game objektu.

Nový objekt v skripte vytvoríme pomocou príkazu Instantiate(), takto:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Generator : MonoBehaviour
{
    public GameObject objekt;

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

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Instantiate(objekt);
        }
    }
}

Príkaz Instantiate berie ako parameter nejaký game objekt (alebo v našom prípade aj prefab) a vytvorí z neho klon v scéne. Keď vyskúšame spustiť tento skript a klikať v hre, uvidíme, že sa nám vytvárajú nové a nové náhodné červené rotujúce kvádre:

Čo s vyrobeným game objektom?

Možno ste si všimli, že vám novovytvorené červené kvádre zvyšujú svoju rýchlosť otáčania pri klikaní. To je pozostatok z predošlých lekcií, kde sme na klikanie myšou v skripte Ovladanie zvyšovali-znižovali rýchlosť otáčania. A tento skript je stále na kockách ešte zavesený a aktívny.

Čo keby sme chceli, aby sa na každej novej kocke deaktivoval? My by sme ho aj vedeli deaktivovať, ale nemáme túto novo-vygenerovanú kocku uloženú v žiadnej premennej. Nemáme ako na ňu v skripte pristupovať.

Našťastie, príkaz Instantiate okrem toho, že nám vygeneruje nový game objekt z prefabu, nám aj vráti ako výsledok tento nový game objekt. A my ho tým pádom vieme uložiť do nejakej premennej a ďalej s ním pracovať:

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            GameObject novy = Instantiate(objekt);
        }
    }

Tu sme si vytvorili novú premennú s názvom novy a ukladáme do nej novo-vytvorený game objekt.

A my už predsa vieme ako deaktivovať nejaký komponent na game objekte, ktorý máme uložený v premennej či vo vlastnosti:

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            GameObject novy = Instantiate(objekt);
            novy.GetComponent<Ovladanie>().enabled = false;
        }
    }

Takto upravený skript už na každej novo vytvorenej kocke deaktivuje komponent Ovladanie a kocky tým pádom prestanú reagovať na klikanie myšou a nebudú viac meniť svoju počiatočnú rýchlosť.

Skúsme teraz spraviť ešte takú modifikáciu, že každej novo vytvorenej kocke nastavíme skriptom rýchlosť otáčania rovnú 1. Nech sa pekne točia všetky rovnako.

Rýchlosť otáčania máme uloženú v komponente Rotator vo vlastnosti speedY. Takže takto nejak by to vyzeralo:

void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
        GameObject novy = Instantiate(objekt);
        novy.GetComponent<Ovladanie>().enabled = false;
        novy.GetComponent<Rotator>().speedY = 1;
    }
}

Pomohlo? Točia sa všetky kocky rovnakou rýchlosťou?

Netočia.

Ako to? Skúsme počas behu hry pozrieť do inšpektora u niektorých kociek. Akú majú hodnoty vlastnosti Speed Y? Je tam hodnota 1?

Nie je. Sú tam náhodné hodnoty. Presne ako si ich náhodne vygeneroval komponent Rotator. Prečo, keď mi explicitne povieme Rotatoru, aby si nastavil speedY na 1?

Životný cyklus game komponentov

My už vieme, že metóda Update sa vykonáva neustále dokola a že metóda Start sa vykoná raz na začiatku. Ale na začiatku čoho? Ak je game objekt v scéne už od začiatku hry, tak sa metóda Start v každom jeho komponente vykoná na začiatku celej hry. Ale čo ak game objekt vznikne až počas hry.

V takom prípade sa metóda Start vykoná na začiatku nového snímku po tom ako komponent pribudol.

Unity obhospodaruje veľa game objektov naraz. A každý z nich má viacero komponentov. Aby v tom nebol chaos a aby takýto zložitý systém fungoval predvídateľne, tak platí, že najprv sa dokončia všetky metódy Update na všetkých aktívnych komponentoch. A až potom začína nový snímok. Takže ak v jednom snímku pribudne nový komponent, tak jeho Start sa zavolá až po tom ako skončí terajší snímok. Nezavolá sa okamžite.

Postupnosť je v našom prípade takáto:

  1. Beží Update metóda v skripte Generator a my sme stlačili myš.
  2. Vygeneruje sa nová kocka cez príkaz Instantiate.
    • Unity zaregistruje, že vznikol nový game objekt a poznačí si, že pre všetky jeho aktívne komponenty má na začiatku ďalšieho snímku zavolať ich metódu Start.
  3. Tejto novej kocke nastavíme v komponente Rotator speed na 1. Stále sme ešte v metóde Update.
  4. Končí metóda Update a končí aj súčasný snímok
  5. Začína sa počítať nový snímok
  6. Unity vykoná metódy Start pre všetky novo vzniknuté komponenty, medzi nimi aj pre skript Rotator na našej novej kocke
  7. V metóde Start na komponente Rotator je však príkaz na nastavenie náhodnej rýchlosti. A ten prepíše hodnotu 1, ktorú sme do rýchlosti uložili predtým, náhodnou hodnotou. Lebo tak sme ho to naučili.

Ako z toho von?

Možností je viacero, najjednoduchšie je odstaviť v metóde Start na skripte Rotator toto náhodné generovanie rýchlosti. My sme ho tam beztak mali len preto, aby sme sa naučili robiť s náhodnosťou. Odstavíme ho jednoducho tým, že ho zakomentujeme pomocou // :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Rotator : MonoBehaviour
{
    public float speedY;

    // Start is called before the first frame update
    void Start()
    {
        //speedY = Random.Range(-2.0f, 2.0f);
    }

    // Update is called once per frame
    void Update()
    {
        transform.Rotate(0, speedY, 0);
    }
}

Upravte metódu Start v skripte Rotator, aby nemala úplne odstavené nastavenie náhodnej rýchlosti. Ponechajte nastavenie náhodnej rýchlosti ako bolo, ale podmieňte ho tým, že speedY je nulová. Tým pádom sa bude skript správať tak, že ak už niekto niekde nastavil speedY na nejakú nenulovú hodnotu (ako my v Generatore) tak ju ponechá. Ale ak nájde v speedY nulovú hodnotu, tak jej nastaví náhodnú hodnotu.

Zhrnutie

Nový game objekt vygenerujeme v skripte cez príkaz Instantiate. Jeho parametrom je game objekt, ktorý sa má naklonovať. Najlepšie ak to bude nejaký prefab.

Takto novo vytvorený game objekt vieme uložiť do premennej a pracovať s ním ďalej.

Po vytvorení nového game objektu sa najprv dokončí práve prebiehajúci snímok (a s ním aj všetky bežiace metódy Update ). Až potom, na začiatku ďalšieho snímku, sa automaticky vykoná metóda Start v komponentoch nového game objektu.

Hotový projekt po tejto lekcii: Programovanie_10.zip