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:
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; // Ez
Emellett 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
Update
metó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
MovePosition
fü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); // Ez
Ez 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áll
A 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és
Na 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.