Developedia
Developedia
A FixedUpdate és a gyorsuló mozgás

A FixedUpdate és a gyorsuló mozgás

Az egyenletes változás szimulálása

Láttuk, hogyan tudunk egyenletes mozgást szimulálni. Mindez valahogy így történt:

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:

V1⃗=V0⃗+(V^⋅∣V∣⋅Δt)\vec{V_1} = \vec{V_0} + ( \hat{V} ⋅ |V| ⋅ Δt )V1​​=V0​​+(V^⋅∣V∣⋅Δt)

V1⃗\vec{V_1}V1​​ Új vektormennyiség

V0⃗\vec{V_0}V0​​ Eredeti vektormennyiség

V^\hat{V}V^ Irányvektor (egység hosszú)

∣V∣|V|∣V∣ 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.

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.

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.

icon
FixedUpdate() metódus

Olyan 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ódusAz 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.

image

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 a FixedUpdate-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.
  • Pl.: Pozíció változása = Egyenletes mozgás

  • Második szintű változás: Egy mennyiség változásának egyenletes változása.
  • Pl.: Sebesség (pozíció változás) változása = Egyenletesen gyorsuló mozgás

icon
Bármilyen mennyiség első szintű változását érdemes az 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.

image

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.

Logo

Főoldal

Blog

Elmélet

3D Studio

Adatvédelmi nyilatkozat

GY.I.K.

Házirend

Szerző: Marosi Csaba / marosi.csaba@3d-studio.hu

DiscordGitHubLinkedIn
// 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; 
}
// 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); 
}
// 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); 
}