Az egyenletes változás szimulálása
Láttuk, hogyan tudunk egyenletes mozgást szimulálni. Mindez valahogy így történt:
// Kell egy sebesség, ami azt adja meg,
// hogy egy másodperc alatt hány egységet teszünk meg:
[SerializeField] float speed = 1f;
void Update()
{
// Kell egy irányított vektor, ami azt adja meg merre haladunk.
// Eztt most a felhaszálói input adja meg:
float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");
Vector3 movementVector = new Vector3(x, 0, y);
// Ebből egy egységvektort csinálunk, normalizálással.
Vector3 direction = movementVector.normalized;
// Ez adja meg a mozgás irányát.
// Az irányt reprezentáló egységvektor és a skalár sebesség szorzata azt adja meg,
// mekkora utat teszünk meg 1 mássodperc alatt és milyen irányba.
Vector3 velocity = direction * speed;
// Ha egyenletes változásként akarom végrehajtani ezt a mozgást, akkor
// szorzok a Time.deltaTime-mal, ami az előző Update óta eltelt időt adja meg.
Vector3 step = velocity * Time.deltaTime;
// Ezt adom hozzá a pozícióhoz minden kéfrissítés előtt (Update metódus).
transform.position += step;
}
A fenti kódot és a Time.deltaTime
-mal való szorzás szerepét azért fontos megérteni, mert sokkal tágabb körben használható, mint pusztán az egyenletes mozgás megvalósítása.
Bármilyen egyenletes változást a játékban hasonlóan programozhatunk le:
void Update()
{
// Valami gyenletes sebességgel forog:
transform.Rotate(eulerAngles * angularSpeed * Time.deltaTime);
// Egyenletes sebességgel növeljük az életünk
health += healingSpeed * Time.deltaTime;
// Egyenletes sebességgel fogy az időnk a GameOver előtt.
timeLeft -= Time.deltaTime;
}
Általános Vektormennyiség egyenletes változására:
Új vektormennyiség
Eredeti vektormennyiség
Irányvektor (egység hosszú)
Vektor absz. értéke / hossza (skalár, mértéke: valami/másodperc)
Δt Eltelt idő
Az egyenletesen gyorsuló változás szimulálása
Ha gyorsuló mozgást szeretnénk szimulálni, még akkor is történik egyenletes változás. Ebben az esetben maga a sebesség változik egyenletes módon: Ha gyorsulunk, akkor a sebesség egyenletesen nő, ha lassulunk akkor a sebesség egyenletesen csökken.
(Persze nem minden gyorsulás és lassulás egyenletes, de mi egyelőre ezzel foglalkozunk csak.)
Nézzünk egy példát egyenletesen változó mozgásra, ahol …
- Az X tengelyen mozgunk.
- A gyorsulás mértékét egy
[SerializeField]
beállítás határozza meg. - Azt pedig, hogy gyorsulunk-e és ha igen, melyik irányba a felhasználói input adja.
// Kell egy gyorsulás érték:
[SerializeField] float acceleration;
// Kell egy sebesség, ami azt adja meg,
// hogy egy másodperc alatt hány egységet teszünk meg:
float speed = 0;
void Update()
{
// Kell egy egységvektor, ami a haladás irányát adja meg:
Vector3 direction = Vector3.right;
// Az input egy -1 és 1 közötti érték:
float input = Input.GetAxis("Horizontal");
// Ha a jobbrát nyomjuk akkor jobbra gyorsulunk.
// ha a balrát nyomjuk akkor balra gyorsulunk.
// Egyenletesen változik a sebesség a gyorsulás értékével arányosan:
speed += input * acceleration * Time.deltaTime;
// Egyenletesen változik a pozíció is a sebesség értékével arányosan:
transform.position += direction * (speed * Time.deltaTime);
}
Nem sokban különbözik a feladat, ha a gyorsulás iránya a 2D sík vagy 3D tér bármelyik iránya lehet:
Ebben az esetben a sebesség és a gyorsulás egy vektor, tehát nem egy float
értéket tárolunk el, hanem egy Vector2
vagy Vector3
típusú sebesség-vektort velocity
néven.
A lenti példában a haladás irányát az adott test lokális előre iránya adja meg.
// Kell egy gyorsulás érték:
[SerializeField] float acceleration;
// Kell egy sebességvektor, ami azt adja meg,
// hogy egy másodperc alatt hány egységet teszünk meg és milyen irányba:
Vector3 velocity = 0;
void Update()
{
// Kell egy egységvektor, ami a haladás irányát adja meg:
Vector3 direction = transform.forward; // Objektum saját (lokális) előre iránya
// Világkoordinátákban kifejezve
// Az input értéke 1 vagy 0:
float input = Input.GetKey(KeyCode.UpArrow) ? 1 : 0;
// Ha a felfelét nyomjuk akkor előre gyorsulunk.
// Egyenletesen változik a sebesség a gyorsulás értékével arányosan:
velocity += direction * input * acceleration * Time.deltaTime;
// Egyenletesen változik a pozíció is a sebesség értékével arányosan:
transform.position += velocity * Time.deltaTime);
}
A fenti példában tehát már kettő érték is változhat egyenletesen: A pozíció és a sebességvektor.
Hasonlóan működik az alábbi játék. Próbáld ki! (Irányítás: ◀️🔼▶️)
A fenti kód persze nem mindent valósít még meg. A főbb különbségek:
- Tudunk egyenletesen forogni (Lásd fent: Az egyenletes változás szimulálása)
- Automatikusan lassulunk (Lásd lentebb: Közegellenállás szimulálása)
- Át tudunk menni a falon. (Ezzel most nem foglalkozunk)
- ⚠ Nem az
Update
metódusban végzem a mozgást. Most következik, hogy hol és miért ott.
A FixedUpdate metódus
Új taggal bővül a Unity által automatikusan hívott MonoBehaviour üzenet metódusok listája.
Ahogy az Update
és a LateUpdate
, úgy a FixedUptate
metódus is Play módban fut le ciklikusan nagy gyakorisággal. Az Update
-ről és a LateUpdate
-ről azt tanultuk, hogy minden képfrissítés előtt futnak le egyszer-egyszer.
Mivel a képfrissítés sebessége a számítógép teljesítményétől függő, ami még egy adott gépen is folyamatosan változik, ezért az, hogy ezek a metódusok milyen gyakran futnak le egy másodperc alatt az változó. Ezzel szemben a FixedUpdate
garantáltan fix időközönként lesz végrehajtva.
FixedUpdate()
metódusOlyan MonoBehaviour üzenetmetódus, ami minden képernyőfrissítés előtt fut le automatikusan a play mód alatt.
Az alapbeállítás új projekt esetén két FixedUpdate
végrehajtás között eltelt időre: 0.02 másodperc.
Ez azt jelenti, hogy másodpercenként pontosan 50-szer fog lefutni a FixedUpdate
egy komponensre. Ezt az értéket a a Projekt beállítások között lehet módosítani:
Felső menüsáv / Edit / Projekt Settings / Time / Fixed Timestep (Érték másodpercben értendő)
Ugyanez az érték elérhető kódból is a Time.fixedDeltaTime
-mal.
Ha folyamatos változást szimulálunk FixedUpdate
-ben azt hasonlóan tesszük, mint ahogy az Update metódusban, csupán a Time.deltaTime
helyett Time.fixedDeltaTime
-mal kell szoroznunk.
A 3 Update függvény tehát nagyon hasonló működésű. Az, hogy az Update
és LateUpdate
közt mi a különbség, arról, itt írtunk: Az idő múlása & Update metódus - A LateUpdate metódus. A következőkben azt tárgyaljuk, hogy mikor használjunk Update-et és mikor FixedUpdate-et?
Update vagy FixedUpdate?
Ha egy grafikonon ábrázoljuk egy test mozgását, úgy, hogy a vízszintes tengelyen látjuk az időt míg a függőleges tengelyen az aktuális sebességet, akkor a függvény alatti terület mérete (azaz a függvény integráltja) adja az elmozdulás mértékét.
A lenti sebesség idő diagram egy egyenletesen gyorsuló mozgást ábrázol.
Az első eset az egyenletes gyorsulás valódi világ beli grafikonját mutatja, a második és a harmadik pedig szimulált gyorsulást, a harmadikat fele akkora frissítési sebességgel végezzük.
A színezett rész mutatja az utat,a mit megtesz a mozgó test. Jól látható, hogy a szimuláció sebessége befolyásolja a megtett út mennyiségét.
Ha a gyorsulás szimulálását az Update metódusban intéznénk akkor a különböző sebességgel futó számítógépeken különböző méretű megtett utakat kapnánk. Ezt elkerülendő használjuk a FixedUpdate-ot.
Mindez egyenletes mozgásnál nem jelent problémát, mivel ebben az esetben a sebesség-idő diagramm egy vízszintes egyenest ír le, így a megtett út különböző frissítési sebesség esetén is egyenlő lesz.
Sőt a pozíció frissítését érdemes az Update-ben intézni, hiszen…
- Ha a
DeltaTime
>FixedDeltaTime
, a magas FrameRate ellenére a mozgás simaságát le fogja korlátozni a Fixed TimeStep mértéke. - Ha a
FixedDeltaTime
>DeltaTime
, fölösleges munkát végzünk, azzal, ha a pozíciót aFixedUpdate
-ben frissítjük.
A fenti logika kiterjeszthető nem csak a mozgásra (pozíció változására), de bármilyen mennyiség változására. Vezessük be a következő fogalmakat:
- Első szintű változás: Egy mennyiség egyenletes változása.
- Második szintű változás: Egy mennyiség változásának egyenletes változása.
Pl.: Pozíció változása = Egyenletes mozgás
Pl.: Sebesség (pozíció változás) változása = Egyenletesen gyorsuló mozgás
Update
-ben kezelni, míg a második szintű változást a FixedUpdate
-ben,Inputkezelés és FixedUpdate
Kétféle Inputot kérhetünk le a Unity Input rendszerétől: Pillanatszerű és folyamatos.
- Pillanatszerű: up és down végű metódusok pl.:
Input.GetKeyDown
Input.GetMouseButtonUp
… - Folyamatos: pl.:
Input.GetKey
Input.GetMouseButton
…
Folyamatos Input-ot nyugodtan lekérhetünk bárhol Update
-ban és FixedUpdate
-ban is, ám pillanatszerű inputot nem szabad FixedUpdate
-ben lekérni, csak az Update ciklusban: tehát az Update
-ben és a LateUpdate
-ben.
A pillanatszerű események pontosan a következő Update ciklusig lekérdezhetők. A következő Update ciklus utolsó LateUpdate
metódusának lefutása után a Unity automatikusan visszaállítja az értéküket false
-ra. Emiatt nem biztos, hogy a FixedUpdate
pontosan ”eltalálja” azt.
Lehetséges hogy néha pontosan jól működik a program, mikor az Time.deltaTime
és a Time.fixedDeltaTime
körül-belül megegyezik, de az is lehet, hogy:
A - Ha a Time.fixedDeltaTime
kisebb, mint a Time.deltaTime
, akár több FixedUpdate
-en keresztül is igazzal térhet vissza a pillanatszerű lekérdezés. Ekkor tapasztalhatunk például olyasmit, hogy néha dupla akkorát ugrik a karakter.
B - Ha a Time.deltaTime
kisebb, mint a Time.fixedDeltaTime
, nem mindegyik pillanatszerű lekérdezés lesz észlelhető a FixedUpdate
-ben, mert könnyen előfordulhat, hogy elmulasztunk egyet-kettőt.
Az is előfordulhat, hogy Editor-ban fejlesztés alatt az (A) eset igaz, de mikor elkészítjük a build-et, akkor a (B). Mivel a build jóval optimálisabb, mint az Editor béli futás, ezért az Update
-ek jóval gyakrabban történnek meg. Ekkor tapasztalhatjuk azt, hogy Editorban minden a lehető legnagyobb rendben volt, build-ben pidig olyan mintha nem venné ben az inputot a játék.
Közegellenállás szimulálása
A közegellenállási erő a haladás irányával ellentétes irányba hat, ezért mindig lassító hatást fog kifejteni. A valóságban mindez különösen bonyolultan számítható. Még az egyszerűsített modellekben is a közegellenállást befolyásolja a test alakja, a haladás irányára merőleges felület mérete és a közeg sűrűsége. Ezzel szemben a közegellenállás szimulálása játékokban nem szokott pontos fizikai modellt követni. A gyakorlatban jóval egyszerűbben oldjuk ezt meg: csupán egy szorzóértékkel.
A Unity fizikában is megvalósított közegellenállást a következőképp tudnánk manuálisan szimulálni:
void FixedUpdate()
{
Vector3 dragVector = -velocity * drag;
velocity += dragVector * Time.fixedDeltaTime;
// Vagy ugyanaz valamivel gyosabban (kicsit kevesebb számítással):
velocity *= 1f - (drag * Time.fixedDeltaTime);
}
Kiemelnék pár különbséget a fenti, Unity szimulált fizika, valamint a valódi közegellenállás közt.
- A szimulációban a közegellenállás ereje egyenesen arányos a sebesség mértékével, míg a valóságban a kapcsolat négyzetes.
- A valódi fizikában a test alakja és a haladás irányával merőleges felület befolyásolja a közegellenállás mértékét. Ezeket itt nem vesszük számba, így a papírlap vékony test pont úgy fog zuhanni, mint a gömb.
- A valóságban a gravitációs erő arányos a test tömegével, de a vele ellentétes irányba ható közegellenállási erő nem, ezért a ugyanabban a szituációban a nagyobb tömegű testek jobban tudnak gyorsulni
A pingpong labda a Földön lassabban zuhan, mint az azonos méretű és alakú vas csapágygolyó.
Ezzel szemben a fenti és a Unity beépített fizikai szimulációban ez a hatás nem jelenik meg.