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ů
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 5x (2.96 kB)
Aplikace je včetně zdrojových kódů v jazyce C# .NET
Komentáře


Zobrazeno 6 zpráv z 6.