A 2D platformer játékunk magja a karakter controller. Ez fogja vezényelni a játékosunk mozgását.
GameObject és komponensek
Mindenek előtt szükségünk van egy GameObject-re. Hozzunk létre egy üreset és nevezzük el Player-nek.
Legcélszerűbb Unity fizikát használni az ütközésdetektálásra, minden mást manuálisan programozunk le.
Adjunk tehát hozzá a GameObject-hez egy Rigidbody2D-t, valamint egy 2D Collider-t, ami a karakter alakját adja meg.
Javaslom, hogy használjunk CapsuleCollider2D-t vagy BoxColluder2D-t.
Mivel teljesen manuálisan szeretnénk szabályozni a játékos működését, hozzunk létre egy PhysicsMaterial2D fájlt. Állítsuk be, hogy a pattanóssága és a súrlódása is nulla legyen és kössük rá a Collider-re. Ezáltal sem pattogni sem a felületi súrlódástól lelassulni nem fog a játékos.
A játékos karaktert meg is kell jeleníteni valahogy a felhasználónak. Erre a célra én most SpriteRenderer-t használok, de nyugodtan lehetett volna MeshRenderer is.
A SpriteRenderer-nek beállítom a Sprite-ját:
Jelen esetben én a Ingyenes Asset-ek közül választottam egy karaktert.
Ne felejtsük el beállítani a Collider méretét a megfelelő képhez.
A működés logikáját mi programozzuk le. Erre a célra létre hozunk egy új MonoBehaviour osztályt Platformer2DController néven és hozzáadjuk a Player GameObject-hez.
A referenciák bekötése a kódban
Kezdjük azzal az osztályunkat, hogy bekötjük a szükséges referenciákat. A komponensnek ismernie kell majd:
Rigidbody2Dkomponenst: Mozgás szabályozása- Visuals
GameObjectTransformkomponensét: Irányba forgatás
Ezek [SerializeField] beállítások. Automatikusan kössük be őket az OnValidate() Unity metódusban.
using UnityEngine;
class Platformer2DController : MonoBehaviour
{
// Player komponnes referenciái:
[Header( "References" )]
[SerializeField] new Rigidbody2D rigidbody2D; // Vezényelt Rigidbody2D
[SerializeField] Transform visuals; // Transform ami a vizuális részeket tartalmazza
void OnValidate() // Autómatiks beállítások az OnValidate metódusban
{
if (rigidbody2D== null) // Ha nincs Rigidbody2D
rigidbody2D= GetComponent<Rigidbody2D>(); // Keresd meg
if (visuals == null) // Ha nincs meg visuals Transform
visuals = treansform; // Keresd meg
SetupRigidbody(); // Rigidbody paraméterek beállítása
// Később írjuk meg...
}
// Tovább...Figyeljünk arra, hogy valóban a Visuals GameObject legyen bekötve a visuals field-hez. Ha kell módosítsuk manuálisan.
A Rigidbody helyes beállítása
Ezután kódból automatikusan beállíthatjuk a Rigidbody2D-t a SetupRigidbody() metódusban, amit az OnValidate()-ből hívunk. (Ezt a beállítást lehetne manuálisan is végezni az Inspector felületen, de az alábbi megoldás kevesebb esélyt ad későbbi hibák elkövetésére.)
void SetupRigidbody() // Rigidbody paraméterek beállítása
{
if (rigidbody == null) return;
rigidbody2D.bodyType = RigidbodyType2D.Dynamic;
rigidbody2D.simulated = true;
rigidbody2D.drag = 0f;
rigidbody2D.angularDrag = 0f;
rigidbody2D.gravityScale = 0f;
rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate;
rigidbody2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
rigidbody2D.constraints = RigidbodyConstraints2D.FreezeRotation;
}
// Tovább...Az alábbiakban végig-veszem miért ezeket a beállításokat választottuk:
- rigidbody.bodyType = RigidbodyType2D.Dynamic
- rigidbody2D.simulated = true;
- rigidbody2D.drag = 0f; rigidbody2D.angularDrag = 0f;
- rigidbody2D.gravityScale = 0f;
- rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate;
- rigidbody2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
- rigidbody2D.constraints = RigidbodyConstraints2D.FreezeRotation;
Dinamikus Rigidbody szükséges az ütközésdetektáláshoz: Unity fizika és a Rigidbody
Enélkül nem is mozgatná a fizika a karaktert.
Nincs szükségünk semmilyen közegellenállásra.
A gravitációt manuálisan kódból fogjuk szabályozni, ezért kikapcsoljuk az automatikusat.
A fizikai szimuláció framerate ne befolyásolja a megjelenítés sebességét.
Ez a legpontosabb ütközésdetektálás. Lévén a fő karakterről van szó, hasznos a legjobbat használni, még ha kicsikét több erőforrást vesz is igénybe.
Nem akarjuk, hogy a karakter el tudjon forogni, ezért lelockoljuk.
Földön állunk-e?
Állapítsuk meg az aktuális állapotunkat! Éppen állunk valamin vagy a levegőben esünk?
Ezt és még néhány egyéb információ tárolására hozzunk létre field-eket:
// Current State
Collider2D _platform = null; // A platform referenciája, amin állunk
float _groundingStateChangedTime = 0f; // Az állapot megváltozásának ideje
// Egy property-t is létrehozunk, ami megmondja, hogy éppen földön állunk-e az alapján,
// hogy a platform változóban van-e valami érték a null-on kívül.
bool IsGrounded => _platform != null;Az OnCollisionEnter2D és OnCollisionExit2D metódusokat fogunk használni hogy mikor találkozunk és mikor távolodunk el platformtól.
void OnCollisionEnter2D(Collision2D collision) // Ütközéskezelés
{
_platform = collision.collider; // Eltároljuk, hogy melyik platformon vagyunk
_groundingStateChangedTime = Time.time; // Eltároljuk, hogy mikor értük el a földet
}
void OnCollisionExit2D(Collision2D collision) // Mikor elvállunk egy Collidertől
{
if(collision.collider != _platform) // Ha nem a platform-tól távolodunk el,
return; // amin épp állunk, akkor kilépünk a metódusból
// Egyébként:
_platform = null; // Töröljük az eltárolt platformot
_groundingStateChangedTime = Time.time; // Eltároljuk, hogy mikor válltunk el
}Ezt az egy egyszerű megoldást a talajdetektálásra majd később bővítjük.
Input
Ez a kontroller ugrani, és jobbra mozogni lesz képes. A megfelelő billentyűket bekötjük [SerializeField] változókba.
[Header( "Input" )]
[SerializeField] KeyCode leftKey = KeyCode.A; // Balra gomb
[SerializeField] KeyCode rightKey = KeyCode.D; // Jobbra gomb
[SerializeField] KeyCode jumpKey = KeyCode.Space; // Ugrás gombFolyamatos mozgás szimulálása
A mozgás szimulálását a FixedUpdate( ) Unity MonoBechaviour életciklus metódusban végezzük.
void FixedUpdate() // Fix időközönként: Itt kezeljük a fizikai szimulációt
{
Vector2 v = rigidbody2D.velocity; // Jelenlegi sebesség
v.x = HandleHorizontal(v.x); // Vízszintes sebesség kezelése
v.y = HandleVertical(v.y); // Függőleges sebesség kezelése (Később)
rigidbody2D.velocity = v; // Sebesség visszaállítása
}
float HandleHorizontal(float x) { /* ... */ }
float HandleVertical(float x) { /* ... */ }A fenti kódban az aktuális sebességet két komponensre bontjuk: Vízszintes és függőleges. Utána függetlenül számításokat végzünk ezen a két float számon két saját függvény segítségével majd végül visszaírjuk a rigidbody2D.velocity-be.
Vízszintes mozgás
Először is vegyük fel a vízszintes mozgáshoz szükséges beállításokat.
Ezek a mi esetünkben a következők lesznek:
[Header( "Horizontal Movement" )]
[SerializeField, Min(0)] float maxMoveSpeed = 5f; // Max vízszintes sebesség
[SerializeField, Min(0)] float groundAcceleration = 25f; // Földön való gyorsulás
[SerializeField, Min(0)] float airAcceleration = 10f; // Légben való gyorsulás
[SerializeField, Min(0)] float groundDrag = 10f; // Földön való lassulás
[SerializeField, Min(0)] float airDrag = 1f; // Légben való lassulásA földön és a levegőben a platformer játékok nagy részében máshogy viselkedik a karakter, ezért mi is különválasztjuk ezt a két esetet.
Utána megírjuk a HandleHorizontal metódust, amit FixedUpdate()-ből hívtunk
HandleHorizontal egy minden FixedUpdate()-ban újra számolja tehát a vízszintes sebességet az aktuális helyzet alapján.
float HandleHorizontal(float x) // Vízszintes sebesség kezelése
{
bool left = Input.GetKey(leftKey); // Balra gomb nyomva van-e?
bool right = Input.GetKey(rightKey); // Jobbra gomb nyomva van-e?
// Acceleration
if (left ^ right) // Ha pontosan az egyik gomb van megnyomva, akkor mozgunk
{
// Gyorsulás mértéke attól függően, hogy a milyen irányba haladnánk
float acceleration = IsGrounded ? groundAcceleration : airAcceleration;
// A sebesség változása a gyorsulás mértékével
if (right)
x += acceleration * Time.fixedDeltaTime;
else
x -= acceleration * Time.fixedDeltaTime;
// Fordulás kezelése (jobbra és balra)
visual.rotation = right ? Quaternion.identity : Quaternion.Euler(0, 180, 0);
// Maximumális vízszintes sebesség kezelése
if (Mathf.Abs(x) > maxMoveSpeed)
x = maxMoveSpeed * Mathf.Sign(x);
}
else // Ha nem nyomjuk a haladás gombot valamilyen irányba
{
// Kezeljük a lassulást függően attól, hogy a földön vagy a levegőben vagyunk
float drag = IsGrounded ? groundDrag : airDrag;
x *= 1f - (drag * Time.fixedDeltaTime);
}
return x; // Visszaadjuk a módosított vízszintes sebességet
}Gravitáció kezelése
Esés beállításai:
[Header( "Vertical Movement" )]
[SerializeField, Min(0)] float gravity = 15f; // Gravitációs gyorsulás
[SerializeField, Min(0)] float maxFallSpeed = 15f; // Max lefelé esési sebességEsés logikáját a HandleVertical metódusban írjuk meg. Emlékezzünk rá, hogy ezt a metódust a FixedUpdate()-ból hívtuk.
float HandleVertical(float y) // Függőleges sebesség kezelése
{
// Gravitáció kezelése
y -= gravity * Time.fixedDeltaTime;
// Maximumális függőleges sebesség kezelése
if (y < -maxFallSpeed)
y = -maxFallSpeed;
return y; //Visszaadjuk a módosított függőleges sebességet
}Az ugrás kezelése
Egyelőre egy beállítás fog tartozni az ugráshoz, a kezdősebesség. Ezt először is vegyük fel [SerializeField]-ként.
[Header( "Jumping" )]
[SerializeField, Min(0)] float jumpStartJumpVelocity = 5f; // Ugrás kezdő sebességeEmlékezzünk, hogy az Input osztály pillanatszerű lekérdezéseit csak az Update és LateUpdate metódusokban érdemes hívni és nem a FixedUpdate-ben. Ellenkező esetben hibás eredményt adhat: Billentyűzet és Gamepad Input
Mivel az ugrás egy pillanatszerű művelet, ezért az Update-ban kezeljük le.
void Update()
{
Vector2 velocity = rigidbody2D.velocity; // Jelenlegi sebesség
velocity.y = HandleJumpStart(velocity.y); // Ugrás kezdésének kezelése
rigidbody2D.velocity = velocity; // Sebesség beállítása
}
float HandleJumpStart(float y) // Ugrás kezdésének kezelése
{
if (!Input.GetKeyDown(jumpKey)) // Ha nincs ugrás gomb megnyomva,
return y; // akkor ne csinálj semmit
bool canJump = IsGrounded // Tudunk ugrani, ha a földön vagyunk.
if (canJump) // Ha tudunk ugrani,
y = jumpStartJumpVelocity; // akkor beállítjuk a függőleges sebessége
return y;
}A folyamatos ugrás
TODO
AirJump: Ugrás a levegőben
TODO
Kifinomultabb módja a földet érés megállapításának
TODO
Coyote Time
A kengyelfutó gyalogkakukk című népszerű Warner Bros. rajzfilm egyik főszereplője a prérifarkas vagy eredeti nevén Wile E. Coyote. Akik megfelelő figyelemmel nézték hősünk kezdettől bukásra ítélt cselszövéseit, tudják, hogy a gravitáció leggyakrabban nem akkor kezd el lefelé gyorsítani egy prérifarkast, amikor az alatt nincs talaj, hanem csak akkor amikor az állat lenéz, sőt néha még csak azután hogy felemel egy táblát és pislog kettőt. Igazság szerint a fizika eléggé inkonzisztens a prérifarkasokkal.
Ezért kapta erről az állatról a nevét a platformer játékfejlesztés egy fontos koncepciója, a Coyote Time (Prérifarkas idő)