Láthattuk korábban, hogy hogyan tudunk magunk mozgás szimulációt készíteni külső fizikai motor használata nélkül:
Pozíció és mozgás
Forgatás
A FixedUpdate és a gyorsuló mozgás
Elsődleges előnye a Unity fizika és a Rigidbody-k használatának a sajátmozgás szimulációval szemben az, hogy a Unity fizika kezeli az ütközésdetektálást. Ezt sokkal nehezebb lenne manuálisan implementálni, mint a szabad mozgást, legyen az egyenletes vagy gyorsuló.
Rigidbody.position
Ha van egy kódunk, ami Rigidbody-k nélkül szabályozza a mozgását egy objektumnak, azt könnyedén ki tudjuk egészíteni ütközésdetektálással úgy, hogy a GameObject-hez hozzáadunk egy dinamikus Rigidbody-t vagy Rigidbody2D-t és a kódot átalakítjuk a következő módon:
Ahol korábban trasnform.position-t használtunk, oda ehelyett rigidbody.position-t írunk.
Persze ehhez szükségünk lesz a Rigidbody vagy Rigidbody2D referenciájára, amihez több féle korábban tanult módon hozzáférhetünk. Lásd: Unity Objektumok referenciái.
transform.position = someVector; // Ehelyett
rigidbody.position = someVector; // EzEmellett fontos, hogy ne felejtsük el kikapcsolni az egyes manuálisan mozgatott Rigidbody-k gravitációját a komponens beállításainál.
A fenti megoldás önmagában nem mindig vezet azonban “szép” eredményre. Mikor közvetlenül egy objektum, mondjuk egy fal mellett áll a Rigidbody-nk, és az ezen fal objektum felé mozogna, akkor gyakorlatilag minden egyes frame-ben beletesszük kicsit a Rigidbody-t a falba és a fizika ezután minden fizikai ciklusban (FixedUpdate) kiveszi onnan. Ez az oda-vissza egy remegő mozgást fog eredményezni.
- Erre az egyik megoldási lehetőség az lehet, hogy nem
Updatemetódusban, hanemFixedUpdate-ben hajtjuk végre a mozgást. Ez a kódunk jelentősebb átalakításával is járhat. Ez azért oldja meg a remegés problémát, mert a “falba betevés” és az “újra kijjebb mozgatás” szinkronban van és így egy képfrissítés előtt biztos megtörténik az utóbbi. - A második lehetséges megoldás az, hogy a mozgó RigidBody-n az ütközésdetektálás típusát folyamatosra állítjuk: Collision Detection : Continuous
- Harmadik lehetőség, hogy a Rigidbody
MovePositionfüggvényét használjuk arra, hogy módosítunk a pozíción a következőképp.
Ekkor eleve nem történhet meg az, hogy kicsikét beletesszük a falba az objektumot. Erre figyel a fizika. Ennek hátránya, hogy valamivel több számítást igényel, cserében a kódunk nagy részt változatlan marad.
rigidbody.position = someVector; // Ehelyett
rBody.MovePosition(someVector); // EzEz a megoldás egyszerre optimális és kényelmes is. A kecsét és a káposztát is megehetjük…. Vagy valami ilyesmi.
A módszer lényege, hogy a rendszere eltárolja, hogy hova mozgatnánk az objektumot és azt megteszi ő a fizikai ciklusban.
Rigidbody sebességvektora
Ha fizikát használunk, akkor sokszor nem közvetlenül számítjuk ki a pozíciót, ezt a fizikai rendszerre hagyjuk. Mi csak azt mondjuk meg mi a 2D és 3D Rigidbody sebességvektora. Ez a velocity tulajdonsága a Rigidbody-nak. Ezen keresztül tudjuk elérni, lekérdezni vagy módosítani azt.
Rigidbody rigidbody= GetComponent<Rigidbody>(); // Valahonnan kell egy referencia
rigidbody.velocity = new Vector3(1,2,3); // Sebességvektor megadása (Irány és sebesség)
rigidbody.velocity = Vector3.zero; // Sebességvektor nullázása: Mozgás leállA korábban általunk írt sebességvektorokhoz hasonlóan ez is meghatározza az aktuális mozgás irányát és sebességét, valamint szintén egység/másodpercben értendő. Ezúttal azonban a mozgatást nem kell manuálisan végeznünk, a Unity fizika gondoskodik róla helyettünk.
A Rigidbody2D komponenseknekVector2 típusú a sebességvektora, 3D Rigidbody-knak pedig természetesen Vector3.
Ha hirtelen sebességváltozást akarunk elérni egy adott értékkel, lehet a sebességvektort szimplán növelni a += operátorral is..
Vector3 value = new Vector3(0,5,0); // Fel irányb 5 hosszú vektor
rigidbody.velocity += value; // Mintha hirtelen egy felfelé lökést adnánk a testnek.A következőkben úgy fogjuk tudni változtatni a test mozgásán, hogy ezt a sebességet módosítjuk.
Gyorsítás
Egy objektum sebesség-vektora, megadja, hogy egységnyi idő alatt mennyivel módosul a pozíciója.
Ehhez hasonlóan a gyorsulás azt adja meg, hogy egységnyi idő alatt mennyivel módosuljon a sebességvektor.
Fizikai mennyiség | Angol név | Jelölés | SI mértékegység | Hasznos képlet |
Sebesség | Velocity | (Egyenletes mozgás) | ||
Gyorsulás | Acceleration | (Egyenletes vált. m.) |
Egyenletes gyorsuláshoz Unity fizika esetén is manuálisan kell növelnünk a sebességvektort ciklikusan. Erre ajánlottan a FixedUpdate metódust használjuk.
[SerializeField] Rigidbody rigidbody;
[SerializeField] Vector3 acceleration = Vector3.right;
void FixedUpdate()
{
rigidbody.velocity += acceleration * Time.fixedDeltaTime;
}Itt olvashattok utána, hogy fizikához miért nem az Update metódus javasolt:
A FixedUpdate és a gyorsuló mozgás
A 2D és 3D Unity fizika része egy globális, egész projektet érintő általános gravitációs gyorsulásvektor. Ez hat minden testen anélkül, hogy bármit kódolnánk.
Ezt a gravitációt ki lehet kapcsolni testenként egyedileg minden Rigidbody-n, vagy lehet 0-ás értékre állítani globálisan. Ekkor helyette megvalósíthatunk egyedi gravitációs viselkedést.
Gravitációt úgy tudunk manuálisan programozni, hogy egy fix irányban folyamatosan közlünk egy gyorsulást a testtel. Ez az irány általában lefelé, de persze videojáték fejlesztésről van itt szó, az csinálunk amit csak akarunk. 🙃
Dinamika: Tömeg, Lendület, Erő
Ahogy azt már korábban tárgyaltuk a Rigidbody-knak van egy tömege. Ezt sok esetben felhasználja a fizikai szimuláció. Például egy ütközésnél a nagyobb tömegű test arányosan erősebben hat a kissebikre, mint fordítva. Ezt kódból is elérhetjük a mass tulajdonságon keresztül.
Fizikai mennyiség | Angol név | Jelölés | SI mértékegység | Hasznos képlet |
Tömeg | Mass | m | ||
Lendület | Momentum | |||
Erő | Force | (Newton II.) |
A tömeg a tehetetlenség mértéke. Ez azt jelenti, hogy ha valaminek nagyobb a tömege, akkor nagyobb erőt kell ahhoz kifejteni, hogy egységnyi gyorsulással nőjön a sebessége.
Ezt Newton II mozgástörvénye írja le:
Az egyenlet átalakítva:
Ha azt szeretnénk leprogramozni, hogy egy fix erőt közöljünk egy testtel, akkor tehát a kódban is a tömeggel kell osztani:
[SerializeField] Rigidbody rigidbody;
[SerializeField] Vector3 force = Vector3.right;
void FixedUpdate()
{
Vector3 acceleration = force / rigidbody.mass.
rigidbody.velocity += acceleration * Time.fixedDeltaTime;
}Ha ki akarjuk számolni egy Rigidbody lendületét, akkor azt könnyen meg tudjuk tenni. Emlékezzünk: A lendületet a sebesség és a tömeg szorzataként kapjuk meg:
Vector3 momentum += rigidbody.velocity / rigidbody.mass;Mikor fix erőt közlök folyamatosan egy testtel a fenti módon, akkor a test lendület-vektora fog növekedni fix sebességgel függetlenül a tömegétől. Teehát a lenület azt adja meg, hogy összesen mennyi erőt közöltünk a testtel.
Ha ezt a lendületet akarjuk változtatni, akkor azt megtehetjük szintén a sebességvektoron keresztül. Ebben az esetben azonban szintén szorozni kell a tömeggle.
Vector3 inpulse = new Vector3(1,2,3); // A lendület megváltozása
rigidbody.velocity += inpulse / rigidbody.mass; // Hirtelen pillanatszerű lökésNa vegyük ezt át még egyszer…
Foglaljuk most össze, mit tanultunk eddig! Beállíthatjuk tehát egy fizikai testre a következőket:
- Pozícióját:
rigidbody.position - Sebesség vektorát:
rigidbody.velocity - Tömegét:
rigidbody.mass
A test azzal a sebességgel automatikusan, ami a velocity értéke. (Newton I.)
Többféleképp tudjuk változtatni ezen sebességet anélkül, hogy felülírnánk azt teljesen:
- Pillanat szerűen hozzáadunk ehhez a sebességhez egy fix vektor értéket.
- Gyorsítjuk a testet egyenletesen fix gyorsulásvektorral.
- Pillanat szerűen hozzáadunk a test lendületéhez egy fix vektor értéket. Ez módosítja a sebességet, és ezen módosulás értéke függ a tömegtől.
- Erőt közlünk a testtel egyenletesen. Ez gyorsítja a testet úgy, hogy a gyorsulás mértéke függ a tömegtől.
Szóval kategorizálhatjuk a fenti lehetőségeket a következő kérdések megválaszolásával:
- A sebességváltozás pillanatszerű vagy folyamatos?
- A sebességváltozás mértéke függ a tömegtől vagy független tőle?
Ezen kategóriák alapján 4 fizikai mennyiséget tudok befolyásolni: Sebesség, Gyorsulás, Lendület, Erő
Független a tömegtől | Függ a tömegtől | |
Pillanat szerű
| Sebesség változása
value = velocity;
velocity += value; | Lendület változása
value = impulse;
velocity += value / mass; |
Folyamatos | Gyorsulás
value = accleration;
velocity += value * deltaTime; | Erő
value = force;
velocity += value / mass * deltaTime; |
Szóval nem is feltétlen kell végig gondolni mely fizikai mennyiséget használjuk csak azt, hogy:
- Ha folyamatos változást akarok akkor szorozni kell az előző frissítés óta eltelt idővel.
- Ha a test tehetetlenségét is számításba akarom venni, akkor szorozni kell a test tömegével.
Vegyünk pár valódi példát a játékfejlesztésből, hogy mikor melyik típust kéne használni.
- Tegyük fel, hogy ugrani szeretnénk egy karakterrel. Ez lehet egy teljesen pilanat szerű művelet. Az ugrásért felelő szkript beállításai közt valószínűleg azt szeretnénk megadni, mekkora sebességgel induljon felfelé a karakter függetlenül a tömegétől, azaz mekkora legye a sebesség változása.
- Egy űrhajót fejlesztünk, ami hajtóműi segítségével gyorsulni tud. Ez a gyorsulás folyamatos és egyenletes. A beállítások közt megint csak a gyorsulás mértékét akarjuk beállítani és nem akarunk a tömeggel foglalkozni.
- Egy robbanást írunk, ami egy pillanatban het a körülötte lévő testek mindegyikére, viszont fontos, hogy nem egyenlően. A kisebb tömegű testeket arányosan erősebben löki el, mint a nehezebbeket. Ekkor fix lendület változást alkalmazunk a környező tárgyak mindegyikén.
- Egy buldózert építünk, ami köveket mozgat fizikai alapon. A buldózernek nagy, fix ereje van, de nem végtelen. Minél nagyobbak és erősebbek a kövek, annál nehezebben birkózik meg velük. Az is lehet, hogy egy ponton túl nem is tudja megmozdítani a köveket.
Független a tömegtől | Függ a tömegtől | |
Pillanat szerű
| Sebesség változás:
Ugrás | Lendület változás:
Robbanás |
Folyamatos | Gyorsulás:
Űrhajó gyorsítása | Erő:
Szikla eltolása |
AddForce és AddForceAtPosition
A fentieket használhatjuk abban formában, ahogy korábban megtanultuk, de helyettesíthetjük is egy Rigidbody metódussal a következő módon:
Vector3 value = new Vector3(1,2,3);
Rigidbody rigidbody = GetComponent<Rigidbody>();
rigidbody.velocity += value;
rigidbody.AddForce(value, ForceMode.VelocityChange); // Alternatíva
rigidbody.velocity += value * Time.fixedDeltaTime;
rigidbody.AddForce(value, ForceMode.Acceleration); // Alternatíva
rigidbody.velocity += value / rigidbody.mass;
rigidbody.AddForce(value, ForceMode.Impulse); // Alternatíva
rigidbody.velocity += value / rigidbody.mass * Time.fixedDeltaTime;
rigidbody.AddForce(value, ForceMode.Force); // AlternatívaÉn személyesen nem nagyon javaslom az AddForce függvény használatát. Véleményem szerint nem segíti az átláthatóságot. Ennek ellenére érdemes ismerni, hátha találkoztok vele más kódban.
Van azonban egy hasznos változata ennek a metódusnak az AddForceAtPosition. Ezt akkor kell használni, ha nem pontosan a tömegközéppontban akarjuk előt közölni a testtel. Ennél a metódusnál egy extra paraméterben kell megadni a pozíciót.
Fontos tudni, hogy világkoordinátákban, és nem pedig a test lokális koordinátáiban várja a pozíciót az AddForceAtPosition metódus. Ha mégis a lokális pozíciót ismerjük, akkor át kell számyítanunk.
Erről az átszámításról itt volt szó bővebben: A lokális és globális tér
Például képzeljük el, hogy egy űrhajót építünk. Ha több hajtóműve is van egy a hajónknak, amik pontjain közölhetünk erőket, akkor ezeket a pontokat minden bizonnyal a lokális koordinátában ismerjük.
Vector3 localPosition = new Vector3(1,2,3);
Vector3 worldPosition = transform.TransformPoint(localPosition);
rigidbody.AddForceAtPosition(force, worldPosition);Általa megmondhatjuk, hogy melyik pozícióban szeretnénk kifejteni a gyorsítást. ha ez nem pont a tömegközéppont, akkor ennek az eredménye, hogy a kifejtett munka bizonyos része nem a test gyorsítására hanem a forgatására hat.
Képzeljük el, hogy az űrben vagyunk és egy pálca lassan forogva lebeg előttünk a súlytalanságban. A pálca közepéhez, azaz tömegközéppontjához egy madzag van kötve. Ha megrántjuk ezt a madzagot a testnek sebességet adunk, de a forgásában nem befolyásoljuk. Ha viszont a madzag a pálca széléhez van kötve és úgy rántjuk meg, akkor ezzel inkább csak megpördítjük azt.
Így működik az AddForce és AddForceAtPosition is.