BLACK FRIDAY! Slevy až 80 % jsou všude. Tak je nepropásni a přejdi do rostoucího IT oboru!
BF Sales

Lekce 12 - 3D střílečka v Unity - Přebíjení

V minulé lekci, 3D střílečka v Unity - Příprava animací přebíjení zbraně, jsme si vytvořili přechody a parametry k jednotlivým animacím pro přebíjení.

V dnešní lekci se vrhneme na zbytek věcí, které potřebujeme k přebíjení a zobrazení počtu nábojů hráči. Podobným způsobem, jako jsme dělali mířidla, si uděláme i značení nábojů. Až na to, že tentokrát přidáme na naše plátno text s aktuálním počtem nábojů ve zbrani.

Ukazatel nábojů

Klikneme na záložku Scene a podíváme se do záložky Hierarchy. Zde máme náš Canvas, který obsahuje crosshair. Klikneme pravým tlačítkem myši na objekt Canvas, zvolíme možnost UI a klikneme na Image. Na obrazovce, v záložce Game nebo Scene, se nám objeví čtverec.

Přejmenování obrázku pro náboj

Nejdříve ze všeho si musíme náš obrázek přejmenovat na něco jiného než Image, jinak by se nám to pletlo a to je zbytečné. V záložce Inspector klikneme do bílého obdélníku, kde je napsáno Image, a vepíšeme jiný název, např. nabojeObr.

Nyní klikneme na kruh s tečkou uprostřed a nápisem Source Image, tento obdélník je součástí komponenty s názvem Image. Otevře se nám okénko, kde do vyhledávacího pole vepíšeme název obrázku, který hledáme, tím je assault_rifle_01_icon. Poté na vyhledaný obrázek dvakrát klikneme:

Změna pozice obrázku pro náboj

Následně si nastavíme v komponentě Rect Transform hodnoty tak, aby se obrázek zbraně nacházel v levém dolním rohu. Já zadal hodnoty pro x, y, z -280, -191 a 0. Nyní klikneme na čtverec v záložce Inspector s nápisem center a postranním middle. Po kliknutí se nám zobrazí okno s těmito sítěmi, stejnými jako jsou na obrázku:

Tyto sítě mají vždy jednu tečku, jeden bod, který znázorňuje, kam se bude náš UI prvek zarovnávat. Celé okno se sítěmi je rozděleno na sloupce a řádky. My levým kliknutím na síť zvolíme sloupec left a řádek bottom. Pokud se vám líbí jiné zarovnání, můžete si ho zvolit.

Všimněte si, že se nám změnily souřadnice x, y, z, ale obrázek zůstal na místě. To proto, že jsme nastavili, aby se obrázek zarovnával do levého dolního rohu. Tyto souřadnice se tedy nyní vztahují k levému dolnímu rohu.

Tvorba textu značícího počet nábojů

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Nyní si vytvoříme samotný text, který nám bude ukazovat, kolik máme nábojů. Klikneme pravým tlačítkem na objekt Canvas a vybereme možnost UI a poté Text. Vyberte možnost Text a ne možnost TextMeshPro. TextMeshPro je pro naše účely až moc detailně zaměřený na úpravu textu, kterou nyní nepotřebujeme.

Klikneme na náš nově vytvořený Text a přejmenujeme si ho, já jsem zvolil název naboje. Nyní klikneme na obdélník s názvem Tag, hned pod názvem objektu, čímž se rozroluje seznam všech tagů na scéně. My zvolíme možnost Add Tag. Klikneme na tlačítko se znaménkem plus, které se nám objevilo, a napíšeme naboje:

Změňme pozici našeho textu tak, aby se nacházel vlevo od našeho obrázku zbraně. Mé souřadnice byly: -318, -194, 0 (Ještě je nastavené zarovnání na střed). Již jste si mohli všimnout, že objekt naboje má komponentu s názvem Text a tato komponenta má vlastnost Text:

Do této vlastnosti budeme vepisovat náš text s počtem nábojů. Do příslušné kolonky napíšeme 30/30 a velikost fontu si nastavte, jaká se vám osobně líbí, já zvolil 25. Jen pozor, při velikosti fontu 28 a více se vám text už nezobrazí! Font je pak totiž větší než samotné "okénko", do kterého text vykreslujeme. Řešením je zvětšit hodnoty Width a Height, které se nachází přímo pod souřadnicemi objektu v komponentě Rect Transform.

Nyní si ještě nastavíme zarovnání. Klikneme levým tlačítkem na čtverec nacházející se v komponentě Rect Transform. Tento čtverec má v sobě obrázek sítě. Po rozkliknutí zvolíme sloupec left a řádek bottom.

Nastavení škálování obrázků a textů

Klikneme na náš Canvas a v záložce Inspector se zobrazí komponenta Canvas Scaler. Tato komponenta má důležitou vlastnost UI Scale Mode, kterou potřebujeme nastavit. Klikneme na tento obdélník a zvolíme možnost Scale With Screen Size. Vše ponecháme tak, jak je, kromě položky Match. Já jsem zvolil hodnotu 0, což má za následek, že se bude náš Canvas zvětšovat jen podle velikosti šířky displeje:

Úprava skriptu shoot

Upravíme si skript na střelbu. Jako vždy si prvně uvedeme celý kód, změny si vysvětlí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() fungovat, potřebujeme vytvořit metodu s návratovou hodnotou typu IEnumerator. Skript jsme proto upravili tak, abychom měli novou metodu Reload(). Tu voláme na konci Update(), když my stiskneme klávesu R.

Tato metoda, jakmile se spustí, nastaví hodnotu bool reloading na true. Tuto hodnotu využíváme při volání metody Shoot(). Pokud je reloading true, nemůže se spustit opakované volání metody Shoot(). Toto zabraňuje výstřelům při nabíjení s neprázdným zásobníkem.

Poté, pokud je počet nábojů větší než nula, tak se aktivuje trigger Reload, tedy animace přebíjení. Pomocí objektu WaitUntil() zastavíme metodu Reload() do doby, než se začne přehrávat animace nabíjení. Jinak by ji další WaitUntil(), které čeká, než animace skončí (tedy neběží), automaticky okamžitě ukončil, protože ještě nebyla ani spuštěna.

Jakmile skončí obě WaitUntil(), náboje se nám rovnají počtu maximálních nábojů, který se stanoví po spuštění hry na stejnou hodnotu, jakou má proměnná public naboje. Poté se nastaví hodnota proměnné nabojeText.text=**, tímto získáme vlastnost této komponenty.

Nakonec se hodnota bool reloading nastaví na false, jelikož už nepřebíjíme. V případě else vše proběhne stejně, akorát s odlišným parametrem animátoru a animací.

Úprava skriptu Move

Nyní upravme ještě skript pro 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 jsme trochu upravili, jelikož obsahoval některé podmínky, které nebyly již potřeba. Time.deltaTime je doba od posledního snímku. Když tímto vynásobíme naši sílu, zaručíme tím, že na silném i slabém PC bude hráč stejně rychlý.

Můžeme se podívat na výsledek:

V příští lekci, 3D střílečka v Unity - Změna zbraní, si vytvoříme systém pro změnu zbraní.


 

Stáhnout

Staženo 2x (2.96 kB)
Aplikace je včetně zdrojových kódů v jazyce C# .NET

 

Předchozí článek
3D střílečka v Unity - Příprava animací přebíjení zbraně
Všechny články v sekci
Tvorba 3D her v Unity
Článek pro vás napsal Tomáš Brabec
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Hodně zdaru programátoři všeho druhu,doufám ,že nikdy nezažijete nudu.
Aktivity (2)

 

 

Komentáře

Avatar
Maso Masaty
Člen
Avatar
Maso Masaty:11. listopadu 17:55

Dobrý den, tyhle články jsou nejlepší a hodně mi pomohly, ale mrzí mě že jsou dál placené. Nemohl byste mi je prosím nějak dodat třeba formou PDF protože jsem se toho hodně naučil a chtěl bych se naučit víc. Jinak tohle je asi nejlepší tutorial, protože jsem i zkoušel hledat anglicky a nic jsem nikde nenašel, ale tohle je libovka. Takže předem děkuji! (Alespoň za odpověď)

Editováno 11. listopadu 17:56
 
Odpovědět
11. listopadu 17:55
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Tomáš Brabec
Brigádník
Avatar
Odpovídá na Maso Masaty
Tomáš Brabec:11. listopadu 21:22

Napište mi email pro lepší domluvu. :)

 
Odpovědět
11. listopadu 21:22
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 2 zpráv z 2.