A következők megértéséhez mindenképp érdemes átnézni a leckét a 2D és 3D térről és a koordináta-rendszerekről: A 3D-s tér Unity-ben
A vektor
A 2D és 3D vektor nem más mint egy számpár vagy számhármas, amit a következőképp tudunk leírni:
2D: (x, y)
3D: (x, y, z)
Példák: (1, 55) (33.5, 0) (1, -6.6)
Példák: (5, 0, -22) (0, 33.5, 0) (1, -46, -2)
(Programozásban mindig tizedes pontot használunk, nem vesszőt! A vessző az elválasztás a számok közt)
Mit reprezentálhat egy vektor?
- Egy irányított szakaszt (nyíl)
- Egy pontot a térben (helyvektor)
- Egy irányt (irány-vektor, egység-vektor)
- 3D elfordulás (Euler koordináták)
- Skálázást (méretezés)
- Akár mást is reprezentálhat vektor, de ezek a legfontosabbak számunkra…
Egy vektorra általában úgy gondolunk, mint egy megfelelő irányba álló nyílra. A nyilat a síkban 2 szám írja le: mennyit halad a nyíl x tengely mentén és mennyit halad y tengely mentén. 3D-ben ez kiegészül egy harmadik, z értékkel is. Vegyük észre, hogy ez a két/három szám semmit nem mond arról, hogy hol helyezkedik el a vektor a (2 vagy 3D) térben. Ezt a nyilat forgatás nélkül bárhova eltolhatnánk.
A koordinátarendszereknél már láthattuk, hogy egy vektor egy pontot jelöl a térben.
Egy pont vagy pozíció egyértelműen megfeleltethető egy számpárnak (2D) vagy számhármasnak (3D). Ezek az adott pont Descartes koordinátái.
Ha vesszük p térbeli (vagy síkbeli) pontot, akkor az origóból ezen pontba mutató irányított szakasz a p vektor. Ekkor a p pont és a p vektor ugyanazon x, y és z értéket tartalmazza.
Ezért lehet egy pontra is úgy tekinteni mint egy vektorra. Ha egy vektort arra használunk, hogy egy pozíciót írjunk le vele, akkor azt helyvektornak nevezzük.
Ha egy vektor pontosan 1 egység hosszúságú, akkor pusztán egy irányt is reprezentálhat.
Egy egységvektor mivel definíció szerint 1 egység hosszú, ezért nem tartalmaz egyéb információt, csak egy irányt.
Az A pontból B pont felé mutató egységvektor (egy egység hosszú vektor) meghatározza milyen irányba kellene indulnunk, hogy A-ból egyenesen B-be jussunk.
Csak 3D-ben kell hozzá egy vektor, 2D-ben egy számérték is elég.
Most nem tárgyaljuk bővebben. Később itt olvashattok róla: Forgatás
3 tengely mentén történő átméretezést. Ezzel megint csak nem foglalkozunk egyelőre. Bővebben: A lokális és globális tér
A továbbiakban elsősorban a pontra és az elmozdulásra koncentrálunk.
Pár egyéb fogalom a vektorok körében
- Egységvektor
- Normálvektor
Olyan vektor, aminek hossza vagy másnéven abszolútértéke pontosan 1. (Ez nem azt jelenti, hogy a tengelyeinek összege egy. Gondoljuk végig!)
2 és 3D-ben egy egyenessel merőleges vektort az egyenes normálvektorának nevezzük.
3D-ben egy felülettel merőleges, a testből kifelé mutató vektort a felület normálvektorának nevezzük.
Vektorok létrehozása és koordinátáinak lekérdezése Unity-ben
A vektorok C# objektumok.
(Ez nem összekeverendő a GameObject-ek fogalmával. A GameObject-ek is és a vektorok is különböző típusú objektumok. Erről később bővebben)
C#-ban egy új objektumot a new kulcsszóval hozunk létre:
void Start()
{
// 2D vektorok létrehozása
Vector2 myFirst2DVector = new Vector2(); // Default Vektor2: x: 0, y: 0
Vector2 mySecond2DVector = new Vector2(1.5f,3f); // x: 1.5, y: 3
Vector2 myThird2DVector = new Vector2(0.15f,-3.35f); // x: 0.15, y: -3.35
// 3D vektorok létrehozsa
Vector3 myFirst3DVector = new Vector3(); // Default Vektor3: x: 0, y: 0
Vector3 mySecond3DVector = new Vector3(1.5f,3f); // x: 1.5, y: 3, z: 0
Vector3 myThird3DVector = new Vector3(1, -3, 2.1f); // x: 1, y: -3, z: 2.1
// Speciális 3D vektorok lekérdezése (a new kulcsszó nélkül) (2D-ben is hasonló)
Vector3 zeroVector = Vector3.zero; // x: 0, y: 0, z: 0 Nulla vektor
Vector3 oneVector = Vector3.one; // x: 1, y: 1, z: 1 Egy vektror
Vector3 rightVector = Vector3.right; // x: 1, y: 0, z: 0 Jobbra mutató v.
Vector3 leftVector = Vector3.left; // x: -1, y: 0, z: 0 Balra mutató v.
Vector3 upVector = Vector3.up; // x: 0, y: 1, z: 0 Felfelé mutató v.
Vector3 downVector = Vector3.down; // x: 0, y: -1, z: 0 Lefelé mutató v.
Vector3 forwardVector = Vector3.forward; // x: 0, y: 0, z: 1 Előre mutató v.
Vector3 backVector = Vector3.back; // x: 0, y: 0, z: -1 Hátra mutató v.
// Vektorokat implicit lehet castolni 2D és 3D közt.
Vector2 v2 = new Vector3 (1,2,3); // A z koordinátát elhagyjuk.
Vector3 v3 = new Vector2 (1,2); // A z koordináta 0 lesz.
}
A C# 9-tól kezdődően (Unity 2021.2) a new
kulcsszó után a típus kiírása elhagyható, ha az a deklaráció alapján egyértelmű a fordító számára. Tehát:
Vector3 v3_a = new Vector3 (1,2,3); // ✔️ Ez így jó
Vector3 v3_b = new (1,2,3); // ✔️ És ez is ugyanúgy
var v3_c = new Vector3(1,2,3) // ✔️ Még mindig jó
var v3_d = new (1,2,3) // 🛑 ERROR! A fordító nem tudja, milyen típus
Vektorok egyes koordinátáit a “.” operátorral, a következőképp lehet lekérni és beállítani:
Vector3 v3 = new Vector3(1,2,3); // x: 1, y: 2, z: 3
// Lekérdezés:
float fx = v3.x; // fx: 1
float fy = v3.y; // fy: 2
float fz = v3.z; // fz: 2
// Érték módosítása:
float v3.x = 4; // x: 4, y: 2, z: 3
float v3.y = 5; // x: 4, y: 5, z: 3
float v3.z = 6; // x: 4, y: 5, z: 6
Vektorok műveletei
Mielőtt megnézzük, milyen műveleteket végezhetünk vektorokon fontos végiggondolni, mi is egy művelet maga.
Egy műveletnek vannak bemenetei, (operandusai vagy paraméterei) és egy kimente, avagy visszatérése. Mind a bementetek sorrendje és típusa, mind pedig a kimenet típusa adott egy művelet esetén.
Két típust vegyünk számításba mikor vektorok műveleteiről beszélünk:
- Szám vagy más néven skalár (Tanult C# skalár típusok: int, float)
- Vektor típusok (Tanult Unity vektor típusok: Vector2, Vector3)
A most tanult műveletek bárhány dimenziós vektorra (Vektor2-re és Vector3-ra is) értelmezhetők, ezért csak 2D vektoron fogom bemutatni őket. Ha ezen műveletekben a kimenet vagy a bemenet egy vektor, akkor ez mindig az adott dimenzióban értelmezett (Vector2 vagy Vector3) és egy műveletben minden vektor azonos dimenziójú, ha a kiment vagy bement skalár akkor az dimenziótól függetlenül egy float szám.
Összeadás
Vektorok összeadása esetén két bemenetről és egy kimenetről beszélünk, amelyek mindegyike vektor típusú.
Vektorok összeadása esetén az egyes komponenseket (x, y, z…) külön összeadjuk.
Unity kód vektorok összeadására:
Vector2 v1 = new Vector2(-1, 2);
Vector2 v2 = new Vector2(3, 0.5f);
Vector3 summaVector = v1 + v2; // x: 2, y: 2.5
Ha vizuálisan képzeljük el az összeadást akkor gondolhatunk rá úgy, hogy az origóból indulva a vektorokat egymás után a legutóbb végéhez illesztjük és az utolsó vektor végének pozíciója adja az összeget.
Kivonás
Vektorok egymásból történő kivonása esetén szintén két bemenetről és egy kimenetről beszélünk, amelyek mindegyike vektor típusú.
Vektorok kivonásakor is az egyes komponenseket (x, y, z…) külön vonjuk ki egymásból.
Unity kód vektrorok kivonására:
Vector2 v1 = new Vector2(-1, 2);
Vector2 v2 = new Vector2(3, 0.5);
Vector3 differenceVector = v1 - v2; // x: -4, y: 1.5
Ha vizuálisan képzeljük el a vektorkivonást akkor gondolhatunk rá úgy, mint a második paraméter pontjától az első paraméter pontjába mutató vektor.
Szorzás és osztás skalárral
egy vektor skalárral (számmal) történő szorzása vagy osztása esetén esetén a vektor mindegyik komponensét (x, y, z…) külön szorozzuk vagy osztjuk a skalárral.
Vector2 v = new Vector2(2, 4);
float s1 = 2.5f;
float s2 = 4;
Vector3 product = v * s1; // x: 5, y: 10
Vector3 quotient = v / s2; // x: 0.5, y: 1
Ez vizuálisan úgy képzelhető el, hogy a vektor iránya nem változik, de a hossza szorzás esetén az n-szeresére nő, vagy osztás esetén az n-ed részére zsugorodik.
A skalárral való szorzás nem összekeverendő a skaláris szorzással. Azzal most nem foglalkozunk
Hossz (abszolútérték) megállapítása
Minden vektornak van egy hossza. Ez egy skalár, ami független az iránytól. Más néven ezt a vektor abszolút-értékének is nevezzük.
A vektor hosszának megállapítása egy művelet, aminek bemenete egy vektor és kimenete egy skalár. Unity-ben ezt roppant egyszerű lekérdezni:
Vector2 v = new Vector2(6, 8);
float length = v.magnitude;
Gondoljuk végig, hogy egy 2D vektor elképzelhető egy derékszögű háromszögként is. Ebben az esetben a két befogó hossza a vektor x és y komponense az átfogó meg pont a vektorral egyezik meg. Ekkor egy pitagorasz téttellel megoldható a hossz számítás.
Erre, mint előbb láttuk, Unity alatt nincs szükség, de a háttérben a játékmotor is így számolja ki egy vektor hosszát.
A gyökvonás egy sokkal lassabb művelet, mint az alapműveletek vagy a négyzetre emelés. Ha a vektor hosszának kiszámításából a gyökvonást elhagyjuk, akkor egy vektor hosszának négyzetét kapjuk meg.
Tehát a hossz kiszámolása jóval lassabb, mint a hossz négyzetének kiszámolása (ott nem kell gyököt vonni).
Vannak esetek, mikor a hossz négyzete is megteszi egy feladatnál, ekkor érdemes lehet, csak azt használni.
Ezért Unity alatt a hossz négyzete is lekérdezhető: v.sqrMagnitude
Ha például azt akarjuk megállapítani, hogy két vektor közül melyik a hosszabb, akkor elég a hosszak négyzetét összehasonlítani, mivel ha egy pozitív szám nagyobb, mint egy másik, akkor annak négyzete is nagyobb lesz.
bool v1IsLongerThanV2 = v1.sqrMagnitude > v2.sqrMagnitude;
Normalizálás
Egy vektorból normalizálással készíthetünk egy azonos irányba mutató, de pontosan 1 egység hosszú vektort. A művelet bemenete és kimenete is egy vektor.
Vector2 v = new Vector2(3, 4);
Vector2 vNormalised = v.normalized; // Elkérjük v normalizált verzióját
// Ekkor v vektor nem változik
v.Normalize(); // Magát v-t alakítjuk át a normalizált alakjára.
(A normálvektor nem összekeverendő a normalizált vektorral)
A fenti példa mutatja, hogy Unity alatt a normalizálás egyszerűen elvégezhető, de érdemes végig gondolni, hogy zajlik ez a háttérben. A normalizálás gyakorlatilag minden vektor saját hosszával történő osztása.
Vector2 v = new Vector2(3, 4);
float vLength = v.magnitude;
Vector2 vNormalised = v / vLength; // Így is kiszámolhatjuk v normalizáltját
Ez akkor lehet hasznos, ha egy vektor hosszára és normalizáltjára is szükség van. Ekkor érdemes lehet a fenti módon normalizálni, mivel a normalizálás jóval CPU igényesebb művelet, mint az osztás.
Két pontvektor távolságának kiszámítása
Két pont távolságának kiszámítása is egy művelet, aminek bemenetei két vektor és kimenet egy skalár.
float distance1 = Vector2.Distance(v1, v2); // Két vektor távolságának kiszámítása
Ha visszaemlékszünk, a kivonás műveletre, akkor tudjuk, hogy egy vektor kivonás eredményére lehet a két bemeneti helyvektor közötti vektorként is tekinteni. Ha ennek a vektornak vesszük a hosszát, akkor megkapjuk a távolságot.
float distance2 = (v1 - v2).magnitude; // Távolság kiszámítása más módon
// A vektorok sorrendje ebben az esetben mindegy.
Felbontás egységvektorra és hossz-ra
Egy vektort felbonthatjuk annak hosszára és egység hosszú irányvektorára:
Vector3 vector;
// ...
float distance = vector.magnitude; // Hossz lekérdezése
Vector3 direction = vector / distance; // Normalizálás
Ebben az esetben az irányvektor azonos irányú lesz, mint az eredeti, de pontosan 1 egység hosszú.
Ha a műveletet az ellenkező irányba kívánjuk végrehajtani, akkor az egység hosszú irányvektor és a hossz szorzásával állíthatjuk elő a teljes, hosszt és és irányt egyszerre tartalmazó teljes vektort.
float distance;
Vector3 direction;
//...
Vector3 vector = distance * direction;
Ez hasznos lehet ha valami vektormennyiséget (sebességet, gyorsulást …) szeretnénk előállítani.
Bővebben: Pozíció és mozgás, A FixedUpdate és a gyorsuló mozgás
Egyéb műveletek később: