02. Náhodný nepriateľ

V tejto lekcii pridáme pohyb na lietajúci tanier. Budeme generovať náhodné pozície a tanier sa na ne bude sám presúvať. Nie je to veľmi inteligentný pohyb, ale nikto nepovedal, že mimozemský život musí byť inteligentný.

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

Vytvorte nový skript UfoController a priraďte ho na game objekt UFO.

Vytvorte v triede UfoController vlastnosť speed na regulovanie rýchlosti pohybu (podobne ako pri astronautke)

Náhodný cieľ

Aby tanier mal nejaký cieľ, za ktorým sa bude hýbať, musíme mu nejaký vytvoriť. Cieľ je nejaké miesto v priestore a na miesta v priestore máme v Unity vektory. Vytvoríme preto novú vlastnosť target, bude typu Vector3. A bude privátna, lebo ju nepotrebujeme nastavovať v editore ani k nej pristupovať z iných skriptov:

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()
    {
        
    }

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

Prečo sme vlastnosť target vytvorili ako Vector3, keď potrebujeme len Vector2? Zatiaľ to neriešme, vrátime sa k tomu o pár odstavcov nižšie.

Pri štarte hry vygenerujeme náhodnú pozíciu v priestore. Na náhodné čísla (ale napríklad aj náhodné farby) existuje v Unity pomocná trieda Random. Tá má veľa užitočných metód a my použijeme metódu Range, ktorá vygeneruje náhodné číslo so zadaného intervalu.

V scéne si vyskúšajme, že aké minimálne a maximálne X,Y pre UFO sú prijateľné. Tu to vychádza na -7.5 až +7.5 pre X a -4 až +4 pre Y.

Už vieme z akého rozsahu chceme X, vieme z akého rozsahu chceme Y. Poďme vytvoriť nový vektor.

Vytvárame nový vektor

Zatiaľ sme používali vektory v podobe Vector2.left, Vector2.top a podobne. To sú preddefinované vektory, my ale teraz chceme svoj vlastný vektor. Nový vektor (a aj iné nové objekty iných tried v jazyku C#) sa vytvára pomocou slova new. Takto:

    void Start()
    {
        target = new Vector3(0, 0, 0);
    }

Tento príkaz vytvorí nový vektor a nastaví mu x na 0, y na 0 a z na 0. Samozrejme, toto ešte nie je náš želaný cieľ. My chceme, aby náš cieľ bol náhodný z rozsahu -7.5 až +7.5 pre X a -4 až +4 pre Y. Takže takto:

    void Start()
    {
        target = new Vector2(Random.Range(-7.5f, 7.5f), Random.Range(-4, 4), 0);
    }

Tým by sme mali vytvorený náhodný cieľ, ktorý sa vygeneruje na začiatku hry a bude ležať v rámci obrazovky. Poďme naučiť UFO presunúť sa do tohto cieľa

Vektorový počet pre začiatočníkov

Spomeňme si, akým spôsobom sme hýbali astronautku:

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

Astronautku sme hýbali v smere vektora Vector.right a násobíme tento posun rýchlosťou a časom.

UFO budeme tiež chcieť podobne, akurát vektor posunu, v ktorom smere budeme UFO hýbať, nebude Vector2.right. Čo bude teda vektor posunu?

V minulej lekcii sme si ukázali, že:
Stará pozícia + posun = nová pozícia

To sa dá prehodiť aj takto:
Posun = nová pozícia – stará pozícia.

Stará pozícia je miesto, kde je UFO teraz (čiže transform.position) Nová pozícia je cieľ, kam chceme UFO presunúť, čiže target.

Poďme teda do triedy UfoController dopísať príkazy na posun smerom do cieľa. Budeme ich písať do Update(), lebo je to pohyb a ten sa má diať v každom frejme.

Najprv si vypočítame vektor posunu. Na tento účel si vytvoríme novú premennú direction. A do nej uložíme „nová pozícia mínus stará pozícia“:

    void Update()
    {
        Vector3 direction = target - transform.position;
    }

Tu sa vrátim k tomu, prečo sme target zadefinovali ako Vector3 a nie Vector2. Komponent transform uchováva pozíciu ako Vector3 aj keď sme v 2D projekte. Takže transform.position je typu Vector3. A keby bol target typu Vector2, tak pre počítač sú to dva rôzne dátové typy a nevie ako ich skombinovať. Vyvstáva potom otázka, že načo vôbec používať Vector2, keď Vector3 vie to isté aj viac. Pre jednoduchosť, ak je to možné. V tomto momente to možné nie je, tak používame Vector3.

V premennej direction teraz máme uložený smer posunu z terajšej pozície k cieľu. Je to smer posunu podobne ako sme pri astronautke použili smer posunu Vector2.right. Takže podobne ako pri astronautke aj tu použijeme metódu transform.Translate() :

    void Update()
    {
        Vector3 direction = target - transform.position;

        transform.Translate(direction * speed * Time.deltaTime);
    }

Keď teraz spustíme hru, UFO sa samo premiestni na náhodnú pozíciu:

Jeden problém ostáva, a síce, že UFO sa nehýbe rovnako rýchlo. Začne rýchlo a postupne – ako sa blíži k cieľu – spomaľuje. Prečo to?

Keď pohyb začína, šípka direction od aktuálnej pozície do novej je dlhá dlhočizná. Takúto šípku keď vynásobíme krát rýchlosť krát čas, dostaneme dlhý úsek. V prvom frejme teda posunieme UFO o dlhý úsek. V ďalšom frejme už rozdiel medzi aktuálnou pozíciou a cieľom bude o trochu kratšia šípka. Preto v ďalšom frejme sa UFO posunie o menší úsek. A tak ďalej a tak ďalej. A ku koncu, kedy rozdiel medzi aktuálnou pozíciou a cieľom je už len kratučká šípka, tak sa UFO hýbe pomaličky.

My chceme, aby sa UFO hýbalo konštantou rýchlosťou. Mimozemšťania predsa nespomaľujú, otáčajú sa na mieste a smer pohybu menia bez brzdenia. To vie každý.

GIF 80s vintage lol - animated GIF on GIFER - by Gall

Vektorový počet pre pokročilých

Aby sme napravili pribrzdené správanie nášho UFA, potrebujeme zachovať jednotnú dĺžku šípky počas celej cesty. Keby tak bola nejaká funkcia, ktorá vie konkrétny vektor dať na nejakú jednotnú dĺžku, napríklad 1.

A ona aj je, volá sa Normalize():

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

        // Znormalizuj tento posun
        direction.Normalize();

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

Príkaz direction.Normalize() znormalizuje vektor direction. Normalizácia znamená, že smer šípky sa zachová, ale jej dĺžka sa skráti (resp. predĺži) tak aby bola dlhá presne 1. V každom frejme je takto znormalizovaná šípka rovnako dlhá, a tak sa UFO posúva o rovnaký úsek a nespomaľuje.

Zhrnutie

  • Pozície objektov sa uchovávajú vo vlastnostiach a v premenných typu vektor. (Vector2 alebo Vector3, podľa potreby)
  • Takisto sa uchovávajú aj smery posunu.
  • Aktuálna pozícia + vektor posunu = nová pozícia
  • A naopak: Vektor posunu = cieľová pozícia – aktuálna pozícia.
  • Rôzne dlhé šípky vieme znormalizovať na jednotnú dĺžku a ich smer sa zachová.
  • Vo vesmíre vás nikto nepočuje kričať.

Tu je na stiahnutie hotový projekt: SpaceGame_02.zip