IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

11. diel - 3D strieľačka v Unity - Prebíjanie

V minulej lekcii, 3D strieľačka v Unity - Vloženie modelu nepriateľa do scény , sme si importovali model nepriateľa a vložili ju do scény.

V dnešnej lekcii sa vrhneme na zvyšok vecí, ktoré potrebujeme k prebíjaniu a zobrazenie počtu nábojov hráčmi. Podobným spôsobom, ako sme robili mieridlá, si urobíme aj značenie nábojov. Až na to, že tentoraz pridáme na naše plátno text s aktuálnym počtom nábojov v zbrani.

Ukazovateľ nábojov

Klikneme na záložku Scene a pozrieme sa do záložky Hierarchy. Tu máme náš Canvas, ktorý obsahuje crosshair. Klikneme pravým tlačidlom myši na objekt Canvas, zvolíme možnosť UI a klikneme na Image. Na obrazovke, v záložke Game alebo Scene, sa nám objaví štvorec.

Premenovanie obrázka pre náboj

Najskôr zo všetkého si musíme náš obrázok premenovať na niečo iné ako Image, inak by sa nám to plietlo a to je zbytočné. V záložke Inspector klikneme do bieleho obdĺžnika, kde je napísané Image, a vpíšeme iný názov, napr. nabojeObr.

Teraz klikneme na kruh s bodkou uprostred a nápisom Source Image, tento obdĺžnik je súčasťou komponenty s názvom Image. Otvorí sa nám okienko, kde do vyhľadávacieho poľa vpíšeme názov obrázku, ktorý hľadáme, tým je assault_rifle_01_icon. Potom na vyhľadaný obrázok dvakrát klikneme:

Tvorba 3D hier v Unity

Zmena pozície obrázka pre náboj

Následne si nastavíme v komponente Rect Transform hodnoty tak, aby sa obrázok zbrane nachádzal v ľavom dolnom rohu. Ja zadal hodnoty pre x, y, z -280, -191 a 0. Teraz klikneme na štvorec v záložke Inspector s nápisom centier a postranným middle. Po kliknutí sa nám zobrazí okno s týmito sieťami, rovnakými ako sú na obrázku:

Tvorba 3D hier v Unity
Tieto siete majú vždy jednu bodku, jeden bod, ktorý znázorňuje, kam sa bude náš UI prvok zarovnávať. Celé okno so sieťami je rozdelené na stĺpce a riadky. My ľavým kliknutím na sieť zvolíme stĺpec left a riadok bottom. Ak sa vám páči iné zarovnanie, môžete si ho zvoliť.

Všimnite si, že sa nám zmenili súradnice x, y, z, ale obrázok zostal na mieste. To preto, že sme nastavili, aby sa obrázok zarovnával do ľavého dolného rohu. Tieto súradnice sa teda teraz vzťahujú k ľavému dolnému rohu.

Tvorba textu značiaceho počet nábojov

Teraz si vytvoríme samotný text, ktorý nám bude ukazovať, koľko máme nábojov. Klikneme pravým tlačidlom na objekt Canvas a vyberieme možnosť UI a potom Text. Vyberte možnosť Text a nie možnosť TextMeshPro. TextMeshPro je pre naše účely až moc detailne zameraný na úpravu textu, ktorú teraz nepotrebujeme.

Klikneme na náš novo vytvorený Text a premenujeme si ho, ja som zvolil názov naboje. Teraz klikneme na obdĺžnik s názvom Tag, hneď pod názvom objektu, čím sa rozroluje zoznam všetkých tagov na scéne. My zvolíme možnosť Add Tag. Klikneme na tlačidlo so znamienkom plus, ktoré sa nám objavilo, a napíšeme naboje:

Tvorba 3D hier v Unity
Zmeňme pozíciu nášho textu tak, aby sa nachádzal vľavo od nášho obrázku zbrane. Moje súradnice boli: -318, -194, 0 (Ešte je nastavené zarovnanie na stred). Už ste si mohli všimnúť, že objekt naboje má komponentu s názvom Text a tento komponent má vlastnosť Text: .<> Tvorba 3D hier v Unity
Do tejto vlastnosti budeme vpisovať náš text s počtom nábojov. Do príslušnej kolónky napíšeme 30/30 a veľkosť fontu si nastavte, aká sa vám osobne páči, ja zvolil 25. Len pozor, pri veľkosti fontu 28 a viac sa vám text už nezobrazí! Font je potom totiž väčší ako samotné "okienko", do ktorého text vykresľujú. Riešením je zväčšiť hodnoty Width a Height, ktoré sa nachádza priamo pod súradnicami objektu v komponente Rect Transform.

Teraz si ešte nastavíme zarovnanie. Klikneme ľavým tlačidlom na štvorec nachádzajúce sa v komponente Rect Transform. Tento štvorec má v sebe obrázok siete. Po rozkliknutí zvolíme stĺpec left a riadok bottom.

Nastavenie škálovanie obrázkov a textov

Klikneme na náš Canvas a v záložke Inspector sa zobrazí komponent Canvas Scaler. Tento komponent má dôležitú vlastnosť UI Scale Mode, ktorú potrebujeme nastaviť. Klikneme na tento obdĺžnik a zvolíme možnosť Scale With Screen Size. Všetko ponecháme tak, ako je, okrem položky Match. Ja som zvolil hodnotu 0, čo má za následok, že sa bude náš Canvas zväčšovať len podľa veľkosti šírky displeja:

Tvorba 3D hier v Unity

Úprava skriptu shoot

Upravíme si skript na streľbu. Ako vždy si prvýkrát uvedieme celý kód, zmeny si vysvetlíme pod ním:

using System.Collections;
using System.Collections.Generic;
using Unity.Collections.LowLevel.Unsafe;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class shoot : MonoBehaviour
{
    RaycastHit hitInfo;
    Animator objectwithAnim; // hráč
    public float dostrel;
    public float sila; // udělovaná síla Rigidbody
    public float poskozeni;
    public GameObject efekttrefy; // impact
    Image crosshair;
    ParticleSystem effekt; // výstřel
    Text nabojeText;
    public int naboje;
    int maxnaboje;
    bool reloading;
    void Start()
    {
        objectwithAnim = GameObject.FindGameObjectWithTag("Animobject").GetComponent<Animator>();
        crosshair = GameObject.FindGameObjectWithTag("crosshair").GetComponent<Image>();
        effekt = GameObject.FindGameObjectWithTag("effekt").GetComponent<ParticleSystem>();
        nabojeText = GameObject.FindGameObjectWithTag("naboje").GetComponent<Text>();
        maxnaboje = naboje;
        nabojeText.text = naboje.ToString() + "/" + maxnaboje.ToString();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0) && !objectwithAnim.GetBool("Run") && !objectwithAnim.GetBool("Holster") && !objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Inspect") && naboje > 0&&!reloading) // pokud stiskneme levé tlačítko a neběžíme
        {
            InvokeRepeating("Shoot", 0, 0.25f); // opakuje metodu Shoot(), začíná okamžitě bez prodlevy, opakuje se každých 0.25 sekundy
        }
        if (objectwithAnim.GetBool("Aim")|| objectwithAnim.GetBool("Holster"))
        {
            crosshair.enabled = false; // změní hodnotu na false
        }
        else
        {
            crosshair.enabled = true;
        }
        if (naboje <= 0|| reloading)
        {
            CancelInvoke("Shoot");
            objectwithAnim.SetBool("Shoot", false);
        }
        if (Input.GetMouseButtonUp(0))
        {
            CancelInvoke("Shoot");// přestává se volat metoda Shoot()
            objectwithAnim.SetBool("Shoot", false);
        }
        if (effekt.isPlaying && effekt.time >= 0.15f)
        {
            effekt.Stop();
        }
        if (objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Ammo Left") || objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Out Of Ammo"))
        {
            CancelInvoke("Shoot");
        }
        if (Input.GetKeyDown(KeyCode.R))
        {
            StartCoroutine(Reload());
        }
    }

    void Shoot() // námi nově vytvořená metoda
    {
        effekt.Stop();
        effekt.Play();
        transform.GetComponent<AudioSource>().Stop(); // kdyby byl spuštěn ještě předchozí výstřel, tak se zastaví, aby nedošlo k tomu, že uslyšíme 15 výstřelů najednou
        transform.GetComponent<AudioSource>().Play(); // spustí zvuk, který už je
        naboje -= 1;
        nabojeText.text = naboje.ToString() +"/"+maxnaboje.ToString();
        if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hitInfo, dostrel)) // pokud něco trefíme, parametry jsou: místo, odkud půjde polopřímka, směr, kam má ukládat informace o tom, co se zasáhlo, dálka, kam až povede
        {
            GameObject trefa = Instantiate(efekttrefy, hitInfo.point, Quaternion.LookRotation(hitInfo.normal));
            Destroy(trefa, 1);
            if (hitInfo.transform.GetComponent<Rigidbody>()) // pokud objekt, který jsme trefili, má komponentu Rigidbody
            {
                hitInfo.transform.GetComponent<Rigidbody>().AddForce(Camera.main.transform.forward * sila); // přidáme sílu ve směru, kterým se díváme
            }
            if (hitInfo.transform.GetComponent<EnemyHealth>()) // pokud objekt, který jsme trefili, má komponentu EnemyHealth
            {
                hitInfo.transform.GetComponent<EnemyHealth>().GetDammage(poskozeni); // spouštíme metodu v jiném skriptu, parametrem jsme si nastavili množství poškození
            }
        }

    }

    IEnumerator Reload() // typ metody, ve které můžeme používat WaitUntil(zastaví metodu, dokud něco neplatí)
    {
        reloading = true;
        if (naboje > 0)
        {
            objectwithAnim.SetTrigger("Reload");
            yield return new WaitUntil(() => objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Ammo Left")); // čeká, dokud nedostane hodnotu true od animátoru, že se přehrává animace
            yield return new WaitUntil(() => !objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Ammo Left")); // čeká, dokud nedostane hodnotu false od animátoru, že se už nepřehrává animace
            naboje = maxnaboje;

        }
        else
        {
            objectwithAnim.SetTrigger("ReloadNoAmmo");
            yield return new WaitUntil(() => objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Out Of Ammo"));
            yield return new WaitUntil(() => !objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Out Of Ammo"));
            naboje = maxnaboje;
        }
        nabojeText.text = naboje.ToString() + "/"+maxnaboje.ToString();
        reloading=false;
    }

}

Aby nám mohlo WaitUntil() fungovať, potrebujeme vytvoriť metódu s návratovou hodnotou typu IEnumerator. Skript sme preto upravili tak, aby sme mali novú metódu Reload(). Tú voláme na konci Update(), keď my stlačíme kláves R.

Táto metóda, akonáhle sa spustí, nastaví hodnotu bool reloading na true. Túto hodnotu využívame pri volanie metódy Shoot(). Ak je reloading true, nemôže sa spustiť opakované volanie metódy Shoot(). Toto zabraňuje výstrelom pri nabíjaní s neprázdným zásobníkom.

Potom, ak je počet nábojov väčšia ako nula, tak sa aktivuje trigger Reload, teda animácie prebíjanie. Pomocou objektu WaitUntil() zastavíme metódu Reload() do doby, než sa začne prehrávať animácia nabíjania. Inak by ju ďalšie WaitUntil(), ktoré čaká, než animácie skončí (teda nebeží), automaticky okamžite ukončil, pretože ešte nebola ani spustená.

Akonáhle skončí obe WaitUntil(), náboje sa nám rovnajú počtu maximálnych nábojov, ktorý sa stanoví po spustení hry na rovnakú hodnotu, akú má premenná public naboje. Potom sa nastaví hodnota premennej nabojeText.text=**, týmto získame vlastnosť tohto komponentu.

Nakoniec sa hodnota bool reloading nastaví na false, pretože už nepřebíjíme. V prípade else všetko prebehne rovnako, akurát s odlišným parametrom animátore a animácií.

Úprava skriptu Move

Teraz upravme ešte skript pre pohyb:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEditor;
using System.Linq;

public class Move : MonoBehaviour
{
    Rigidbody rb; // proměnná, která zastupuje naši komponentu Rigidbody
    Animator objectwithAnim;
    bool running; // hodnota running je rovna true tehdy, když běžíme a nemůžeme dělat nic jiného
    bool nicnedelani;
    void Start()
    {
        rb = transform.GetComponent<Rigidbody>(); // získáme komponentu Rigidbody objektu se skriptem
        objectwithAnim = GameObject.FindGameObjectWithTag("Animobject").GetComponent<Animator>(); // metoda FindGameObjectWithTag() vyhledá herní objekt, který má tag = Animobject. Tag našemu objektu nastavíme později v editoru.

        Cursor.visible = false;
    }

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

        if (Input.GetKey(KeyCode.W) && Input.GetKey(KeyCode.LeftShift) && !objectwithAnim.GetBool("Aim") && !objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Inspect")) // pokud držíme W a zároveň levý Shift, nemíříme a není aktivní animace Inspect
        {
            rb.AddRelativeForce(new Vector3(0, 0, 280 * Time.deltaTime));
            objectwithAnim.SetBool("Run", true); // spustíme animaci běhu změněním bool parametru
            running = true; // náš bool, pomocí kterého pozná náš program, že hráč běží
        }
else // // pokud se nespustí podmínka výše, nastaví se bool hodnota na false a program ví, že hráč zrovna neběží
        {
            objectwithAnim.SetBool("Run", false); // zastaví se animace
            running = false;
        }
        if (!running) // pokud hráč neběží, tak se spustí podmínky níže
        {
            if (Input.GetMouseButtonDown(0) && !objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Inspect") && !objectwithAnim.GetBool("Holster")) // pokud se stisklo levé tlačítko (označení 0), nepřehrává se animace Inspect a zároveň není zbraň schována
            {
                objectwithAnim.SetBool("Run", false); // zastavujeme animaci běhu a chůze, aby se mohla spustit animace střelby
                objectwithAnim.SetBool("Shoot", true); // aktivujeme parametry trigger, jako jsou Shoot a Inspect v našem případě
            }

            if (objectwithAnim.GetBool("Shoot"))
            {
                objectwithAnim.SetBool("Walk", false); // použijeme objekt, který jsme si definovali, a získáme z něj komponentu Animator, tím získáme možnost upravovat animace, které jsou k tomuto objektu přichycené
            }
            if (Input.GetKey(KeyCode.D)) // pokud někdo stiskne klávesu W, spustí se tato podmínka, dokud bude klávesa držena
            {
                rb.AddRelativeForce(new Vector3(200 * Time.deltaTime, 0, 0)); // na proměnnou přešly vlastnosti a metody komponenty Rigidbody.
                                                            // metoda AddForce() přidá sílu do určitého směru, který nastavujeme pomocí os x y z.
                if (!objectwithAnim.GetBool("Shoot"))
                {
                    objectwithAnim.SetBool("Walk", true); // pomocí animátoru získáme možnost upravovat animace, které jsou k tomuto objektu přichycené.
                }                                         // jednou z funkcí komponenty Animator je změnit hodnotu parametru
            }
            if (Input.GetKey(KeyCode.A))
            {
                rb.AddRelativeForce(new Vector3(-200 * Time.deltaTime, 0, 0));

                if (!objectwithAnim.GetBool("Shoot"))
                {
                    objectwithAnim.SetBool("Walk", true);
                }
            }
            if (Input.GetKey(KeyCode.W))
            {
                rb.AddRelativeForce(new Vector3(0, 0, 200*Time.deltaTime));

                if (!objectwithAnim.GetBool("Shoot"))
                {
                    objectwithAnim.SetBool("Walk", true);
                }
            }
            if (Input.GetKey(KeyCode.S))
            {
                rb.AddRelativeForce(new Vector3(0, 0, -200 * Time.deltaTime));
                if (!objectwithAnim.GetBool("Shoot"))
                {
                    objectwithAnim.SetBool("Walk", true);
                }
            }
            if (Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.A) || Input.GetKeyUp(KeyCode.S) || Input.GetKeyUp(KeyCode.D))// pokud pustíme W a S nebo D, tak se spustí tato podmínka
            {
                objectwithAnim.SetBool("Walk", false);
            }
            if (Input.GetMouseButton(1)&& !objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Ammo Left") && !objectwithAnim.GetCurrentAnimatorStateInfo(0).IsName("Reload Out Of Ammo")) // pokud dostane Input signál, že se stisklo pravé tlačítko (označení číslo 1)
            {
                objectwithAnim.SetBool("Aim", true); // nastaví hodnotu parametru Aim typu bool na true
            }
            else
            {
                objectwithAnim.SetBool("Aim", false);
            }
            if (Input.GetKeyDown(KeyCode.T))
            {
                objectwithAnim.SetTrigger("Inspect");

            }
            if (Input.GetKeyDown(KeyCode.Z))
            {
                objectwithAnim.SetBool("Holster", !objectwithAnim.GetBool("Holster"));
            }
        }
    }
}

Kód Move sme trochu upravili, pretože obsahoval niektoré podmienky, ktoré neboli už potrebné. Time.deltaTime je doba od posledného snímky. Keď týmto vynásobíme našu silu, zaručíme tým, že na silnom aj slabom PC bude hráč rovnako rýchly.

Môžeme sa pozrieť na výsledok:

Tvorba 3D hier v Unity

V budúcej lekcii, 3D strieľačka v Unity - Zmena zbraní , si vytvoríme systém pre zmenu zbraní.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 35x (4.08 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

 

Predchádzajúci článok
3D strieľačka v Unity - Vloženie modelu nepriateľa do scény
Všetky články v sekcii
Tvorba 3D hier v Unity
Preskočiť článok
(neodporúčame)
3D strieľačka v Unity - Zmena zbraní
Článok pre vás napísal Tomáš Brabec
Avatar
Užívateľské hodnotenie:
2 hlasov
Hodně zdaru programátoři všeho druhu,doufám ,že nikdy nezažijete nudu.
Aktivity