03. Cesta je cieľ

V minulej lekcii sme naučili UFO prejsť z aktuálnej pozície do náhodne vygenerovaného cieľa. Akurát, že keď tam príde, ostane tam a začne nuda. Teraz ho naučíme, že keď príde k cieľu, vygeneruje si nový a tak bude v pohybe navždy.

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

A ideme rovno do skriptu na ovládanie lietajúceho taniera – UfoController. Zatiaľ vyzerá takto:

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

public class UfoController : MonoBehaviour
{
    public float speed = 1;

    private Vector3 target;

    // Start is called before the first frame update
    void Start()
    {
        // Vygeneruj novy nahodny ciel
        target = new Vector3(Random.Range(-7.5f, 7.5f), Random.Range(-4, 4), 0);
    }

    // Update is called once per frame
    void Update()
    {
        // Vypocitaj posun od aktualnej pozicie do ciela
        Vector3 direction = target - transform.position;

        // Znormalizuj tento posun
        direction.Normalize();

        // A posun sa o vektor posunu krat rychlost krat cas
        transform.Translate(direction * speed * Time.deltaTime);
    }
}

Ak chceme, aby sa po príchode do cieľa vygeneroval nový náhodný cieľ, budeme potrebovať vedieť jednu vec: Či sme už dorazili do cieľa.

V Unity sme zatiaľ na zistenie, či niečo niekam dorazilo (napríklad guľa do otvoru v stole) používali collider (nastavený do režimu trigger). Išlo by to aj teraz, museli by sme mať vygenerovaný collider a posúvať ho na pozíciu target. Ale museli by sme k nemu ešte dopísať skript, ktorý ho bude obsluhovať a celé by to bolo krkolomné.

Jednoduchšie bude vyrátať si vzdialenosť od cieľa a keď táto vzdialenosť bude dostatočne malá, vyhlásime, že už sme v cieli.

Prečo dostatočne malá a nie presne nulová? Veď ked dorazíme do cieľa presne, tak naša vzdialenosť od cieľa je nulová, nie? Problém je, že tým spôsobom ako máme rozpohybované UFO, sa presne do cieľa nedostaneme. Vždy sa totiž hýbeme o nejaký malý kúsok. A keď tieto malé kúsky vyskladáme za seba do súvislej cesty, tak posledný kúsok nemusí padnúť presne na cieľ, ale kúsok pred cieľ alebo kúsok za cieľ.

Keď sa posúvame v každom frejme o jeden krôčik, môže nastať situácia, že sme buď ešte nedosiahli do cieľa, alebo ďalší krok bude už až za cieľom. Preto nebudeme testovať, či sme presne v cieli, ale iba, či sme dostatočne blízko.

Vzdialenosť

Ako zistíme vzdialenosť medzi aktuálnou pozíciou a targetom? V kóde už počítame šípku posunu od aktuálnej pozície do targetu : direction. No a dĺžka tejto šípky je predsa dĺžka vzdialenosti. Dĺžka vektora (alebo teda jeho veľkosť) je uložená v jeho vlastnosti magnitude.

Ale pozor! Keď už raz vektor v kóde znormalizujeme, jeho veľkosť bude vždy 1. To je normalizácia. Preto sa budeme na veľkosť vektora direction pýtať ešte pred znormalizovaním.

A čo urobíme, keď ak je vzdialenosť už dostatočne malá? Vygenerujeme nový náhodný target. Tak, ako to robíme v Start(). A čo znamená dostatočne malá? Ak je celý rozmer nášho vesmíru od -7.5 do 7.5, tak 0.1 by mohlo stačiť. Teraz už máme všetky kamienky do skladačky:

  • Vieme ako zistiť, aká je vzdialenosť od cieľa
  • Vieme čo urobiť, keď je dostatočne malá – vygenerujeme nový cieľ.
  • Vieme k tomu napísať podmienku
  • A vieme, že ak tá podmienka nebude splnená, ide sa podľa doterajšieho plánu, čiže znormalizujeme vektora a posunieme tanier o kúsok.
    void Update()
    {
        // Vypocitaj posun od aktualnej pozicie do ciela
        Vector3 direction = target - transform.position;

        // Ak je vzdialenost do ciela dostatocne mala
        if (direction.magnitude < 0.1f)
        {
            // Vygeneruj novy nahodny ciel
            target = new Vector3(Random.Range(-7.5f, 7.5f), Random.Range(-4, 4), 0);
        }
        else // A ak nie je vzdialenost dostatocne mala
        {
            // Znormalizuj tento posun
            direction.Normalize();

            // A posun sa o vektor posunu krat rychlost krat cas
            transform.Translate(direction * speed * Time.deltaTime);
        }
    }

Teraz môžeme uložiť skript a otestovať v hre. A nech to má šťavu, tak ešte v inšpektore zvýšme game objektu UFO v komponente UfoController rýchlosť na 5:

A UFO už po vesmíre poletuje rýchlo a nebezpečne:

Až tak nebezpečne, že keby sa zrazilo s Sandrou, asi by sa malo niečo stať. Základom každej hry je nejaký konflikt. Prekážky alebo ohrozenie. Teraz keď UFO preletí cez Sandru, tak sa nič nedeje. Vyrobme teda konflikt.

Kolízie v 2D

Zisťovanie kto do koho narazil v 2D je veľmi podobné ako v 3D, len fyzikálny komponent Rigidbody a collidery majú v 2D svoje vlastné obdoby.

  • Pridajte na game objekty UFO a Sandra komponent Capsule Collider 2D
  • Napasujte tvar kapsule približne na tvar sprajtu. Využite v inšpektore možnosti Edit Collider, Direction, Size, Offset.

Ďalej, aby sa vôbec začali kolízie vypočítavať, musí aspoň jeden z objektov mať nejaký Rigidbody komponent. Samotné collidery ešte nestačia, lebo tie len hovoria kde sa počíta kolízia. To, či sa vôbec má počítať kolízia, to povie až Rigidbody (resp tu konkrétne Rigidbody 2D).

Pridajte na Sandru komponent Rigidbody 2D.

Spustite hru a čo sa stane? Sandra padá dole. Pretože ju ťahá dole čo? Gravitácia. Takže vypneme gravitáciu v nastavení Rigidbody 2D:

Teraz už na Sandru nepôsobí gravitácia a ona môže nerušene poletovať ako chceme. No.. zas až tak nerušene poletovať nebude, lebo keď do nej narazí UFO, tak ju vytočí. To by asi vytočilo každého. Ale ju to vytočí tak, že potom lieta nakrivo:

Keď je takto vytočená, tak si môžeme všimnúť, že pohyb klávesami už funguje akosi inak. Šípka doprava už nehýbe polohou doprava, ale v nahor či nadol, alebo šikmo. Podľa toho ako je Sandra otočená. A pri tom sa pristavíme.

Lokálne súradnice

Vypnime teraz na chvíľu hru a pozrime sa na Sandru do editora scény. Nasimulujeme si tu v kľude otočenie. Nastavme Sandre v inšpektore rotáciu okolo Z na 90 stupňov a uistime sa, že v editore máme zapnutý režim Global:

Všimnite si, že nech otočíme Sandru akokoľvek, jej šípky ukazujú červená doprava a zelená hore. Červená šípka ukazuje smer right a zelená ukazuje smer up.

Takto v skripte Astronaut posúvame Sandru doprava:

        if (Input.GetKey(KeyCode.RightArrow))
        {
            // Pohni sa kusok smerom doprava
            transform.Translate(Vector2.right * speed * Time.deltaTime);
        }

Používame pri tom vektor Vector2.right, ktorý znamená posun o (+1, 0) a zodpovedá červenej šípke. Tak prečo sa neposúva o 1 doprava ale nahor? Lebo vo vesmíre nie je žiadne nahor alebo nadol. No ale teraz vážne.

Metóda transform.Translate() posúva objekt nie vzhľadom na celý svet, ale vzhľadom na takzvané lokálne súradnice. Vyskúšajte si prepnúť editor z Global do Local režimu a pozrite ako sa otáčajú súradnice v jednom a druhom režime:

Lokálne súradnice sú smery, ktoré si so sebou nosí každý objekt. Keď ho otáčame, otáčajú sa s ním. Keby mala Sandra na chrbte raketové trysky ako pohon, tie ju nebudú poháňať vždy smerom nahor, ale vždy v smere hlavy (zelená lokálna šípka). A takisto funguje aj transform.Translate(), posúva game objekt, ale v smere jeho lokálnych súradníc.

Metóda transform.Translate() sa dá prepnúť aj do režimu posúvania v globálnych súradniciach. Pozrite si dokumentáciu.

Keď sme si už otestovali lokálne súradnice, tak vráťme v editore Sandru naspäť na nulovú rotáciu.

Z núdze cnosť

Tento „nedostatok“ spôsobený pohybom v lokálnych súradniciach nám práveže vyhovuje. Vystihuje to spôsob pohybu vo vesmíre. Astronauti majú nejaké trysky a tie ich posúvajú. Ale vždy len v smere hlavy. A potom majú trysky, ktoré ich vedia otáčať.

Zrušte z triedy Astronaut možnosť pohybu šípkami doprava, doľava a dole. Nechajte len pohyb šípkou nahor.

A využijeme funkciu transform.Rotate(), ktorej dávame 3 parametre: Uhol pootočenia okolo X, okolo Y a okolo Z.

Pridajte do triedy Astronaut príkazy, aby pri držaní klávesy Q sa Sandra otáčala o 1 stupeň okolo Z a pri držaní klávesy Q o -1 stupeň.

Takto vyzerá Sandra s tryskovým pohonom skafandra:

Zhrnutie

  • Vektory majú svoju dĺžku a podľa nej počítame vzdialenosti
  • Pri interaktívnom pohybe nemôžeme vždy spoliehať na to, že objekt dosiahne presnú polohu, počítame s odchýlkami
  • V 2D projekte vieme využívať kolízie a rigidbody fyziku podobne ako v 3D, ale použité komponenty aj ich metódy sa volajú inak.
  • Poloha či smer sa môžu udávať v lokálnych ale aj v globálnych súradniciach. Každé sa správajú trochu inak

Na domácu úlohu:

Otáčanie Sandry máme zatiaľ len po +/-1 stupni. Nemá rýchlosť ani nie je závislé od času. Napravme to.

  • Pridajte triede Astronaut vlastnosť rotationSpeed
  • Prerobte rotáciu v kóde v Update() tak aby používala rotationSpeed a Time.deltaTime.
  • Pre rýchlosť pohybu speed je vhodná hodnota okolo 1 až 5. Aká je vhodná hodnota rotationSpeed?

Tu je hotový projekt na stiahnutie: SpaceGame_03.zip