V tomto článku pokračujeme vo vývoji ovládača postavy pre 2D plošinovku v Unity, pričom dôkladne skúmame každý krok konfigurácie a optimalizácie ovládacích prvkov.
V predchádzajúcom článku „ Ako vytvoriť 2D ovládač postavy v Unity: Časť 1 “ sme podrobne rozobrali, ako vytvoriť základy postavy, vrátane jej fyzického správania a základného pohybu. Teraz je čas prejsť k pokročilejším aspektom, ako je manipulácia so vstupmi a dynamické sledovanie kamery.
V tomto článku sa ponoríme do nastavenia nového vstupného systému Unity, vytvárania aktívnych akcií na ovládanie postavy, umožňuje skákanie a zabezpečuje správne reakcie na príkazy hráča.
Ak chcete sami implementovať všetky zmeny opísané v tomto článku, môžete si stiahnuť vetvu úložiska „ Character Body “, ktorá obsahuje základ pre tento článok. Prípadne si môžete stiahnuť vetvu „ Character Controller “ s konečným výsledkom.
Predtým, než začneme písať kód na ovládanie našej postavy, musíme v projekte nakonfigurovať vstupný systém. Pre našu plošinovku sme si vybrali nový vstupný systém Unity, ktorý bol predstavený pred niekoľkými rokmi, ktorý zostáva relevantný vďaka svojim výhodám oproti tradičnému systému.
Vstupný systém ponúka modulárnejší a flexibilnejší prístup k manipulácii so vstupmi, čo umožňuje vývojárom jednoducho nastaviť ovládacie prvky pre rôzne zariadenia a podporovať zložitejšie vstupné scenáre bez dodatočnej réžie implementácie.
Najprv nainštalujte balík Input System. Otvorte Správcu balíkov z hlavnej ponuky výberom položky Okno → Správca balíkov. V sekcii Unity Registry nájdite balík "Vstupný systém" a kliknite na "Inštalovať".
Ďalej prejdite do nastavení projektu cez ponuku Upraviť → Nastavenia projektu. Vyberte kartu Player, nájdite časť Active Input Handling a nastavte ju na „Input System Package (New).
Po dokončení týchto krokov vás Unity vyzve na reštart. Po reštarte bude všetko pripravené na konfiguráciu ovládacích prvkov pre nášho kapitána.
V priečinku Nastavenia vytvorte Vstupné akcie cez hlavné menu: Majetok → Vytvoriť → Vstupné akcie . Pomenujte súbor „Ovládacie prvky“.
Unity's Input System je výkonný a flexibilný nástroj na správu vstupov, ktorý umožňuje vývojárom konfigurovať ovládacie prvky pre postavy a herné prvky. Podporuje rôzne vstupné zariadenia. Akcie vstupu, ktoré vytvoríte, poskytujú centralizovanú správu vstupov, zjednodušujú nastavenie a robia rozhranie intuitívnejším.
Dvakrát kliknite na súbor Controls , aby ste ho otvorili na úpravy, a pridajte mapu akcií pre ovládanie postavy s názvom „Character“.
Akčná mapa v Unity je súbor akcií, ktoré možno prepojiť s rôznymi ovládačmi a klávesmi na vykonávanie konkrétnych úloh v hre. Je to efektívny spôsob organizácie ovládacích prvkov, ktorý umožňuje vývojárom prideľovať a upravovať vstupy bez prepisovania kódu. Ďalšie podrobnosti nájdete v oficiálnej dokumentácii vstupného systému .
Prvá akcia sa bude nazývať „Presunúť“. Táto akcia určí smer pohybu postavy. Nastavte Action Type na "Value" a Control Type na "Vector2", aby ste umožnili pohyb v štyroch smeroch.
Priraďte k tejto akcii väzby výberom Pridať hore/nadol/vpravo/vľavo kompozit a priraďte známe klávesy WASD k ich príslušným smerom.
Nezabudnite uložiť nastavenia kliknutím na položku Uložiť aktívum . Toto nastavenie zaisťuje, že môžete zmeniť priradenie väzieb pre akciu „Presunúť“, napríklad klávesám so šípkami alebo dokonca joysticku gamepadu.
Potom pridajte novú akciu – „Skočiť“. Typ akcie ponechajte ako „Tlačidlo“, ale pridajte novú interakciu – „Stlačte“ a nastavte Správanie spúšťača na „Stlačte a uvoľnite“, pretože potrebujeme zachytiť stlačenie aj uvoľnenie tlačidla.
Tým je schéma ovládania postavy hotová. Ďalším krokom je napísať komponent, ktorý tieto akcie zvládne.
Nastal čas prepojiť vstupné akcie, ktoré sme vytvorili na ovládanie postavy, s komponentom CharacterBody
, čo postave umožní aktívne sa pohybovať po scéne podľa našich ovládacích príkazov.
Aby sme to dosiahli, vytvoríme skript zodpovedný za ovládanie pohybu a pre prehľadnosť ho pomenujeme CharacterController
. V tomto skripte najskôr zadefinujeme niektoré základné polia. Pridáme odkaz na komponent CharacterBody
, _characterBody
, ktorý bude priamo riadený skriptom.
Taktiež nastavíme parametre pre rýchlosť pohybu postavy ( _speed
) a výšku skoku ( _jumpHeight
). Okrem toho definujeme účel poľa _stopJumpFactor
.
Možno ste si všimli, že v mnohých 2D plošinovkách je možné ovládať výšku skoku. Čím dlhšie tlačidlo skoku držíte, tým vyššie postava vyskočí. V podstate sa na začiatku skoku použije počiatočná rýchlosť smerom nahor a táto rýchlosť sa zníži, keď sa tlačidlo uvoľní. _stopJumpFactor
určuje, o koľko sa zníži rýchlosť nahor po uvoľnení tlačidla skoku.
Tu je príklad kódu, ktorý napíšeme:
// CharacterController.cs public class CharacterController : MonoBehaviour { [SerializeField] private CharacterBody _characterBody; [Min(0)] [SerializeField] private float _speed = 5; [Min(0)] [SerializeField] private float _jumpHeight = 2.5f; [Min(1)] [SerializeField] private float _stopJumpFactor = 2.5f; }
Ďalej implementujeme možnosť pohybu postavy doľava a doprava. Pri podržaní tlačidla pohybu by si postava mala udržiavať určenú rýchlosť pohybu bez ohľadu na prekážky. Aby sme to dosiahli, pridáme do skriptu premennú na uloženie aktuálnej rýchlosti pohybu pozdĺž povrchu (alebo jednoducho horizontálne, keď je postava vo vzduchu):
// CharacterController.cs private float _locomotionVelocity;
V komponente CharacterBody
predstavíme metódu na nastavenie tejto rýchlosti:
// CharacterBody.cs public void SetLocomotionVelocity(float locomotionVelocity) { Velocity = new Vector2(locomotionVelocity, _velocity.y); }
Keďže naša hra nemá naklonené povrchy, táto metóda je celkom jednoduchá. V zložitejších scenároch by sme museli brať do úvahy stav telesa a sklon povrchu. Zatiaľ jednoducho zachováme vertikálnu zložku rýchlosti a zároveň upravíme iba horizontálnu súradnicu x
.
Ďalej túto hodnotu nastavíme v metóde Update
na každom snímku:
// CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); }
Definujeme metódu spracovania signálov z akcie Move
Input:
// CharacterController.cs public void OnMove(InputAction.CallbackContext context) { var value = context.ReadValue<Vector2>(); _locomotionVelocity = value.x * _speed; }
Keďže akcia Move
je definovaná ako Vector2
, kontext poskytne vektorovú hodnotu v závislosti od toho, ktoré klávesy sú stlačené alebo uvoľnené. Napríklad stlačenie klávesu D
spôsobí, že metóda OnMove
prijme vektor (1, 0). Súčasné stlačenie oboch D
a W
spôsobí (1, 1). Uvoľnením všetkých kláves sa spustí OnMove
s hodnotou (0, 0).
Pre kláves A
bude vektor (-1, 0). V metóde OnMove
vezmeme horizontálnu zložku prijatého vektora a vynásobíme ju zadanou rýchlosťou pohybu, _speed
.
Najprv musíme naučiť komponent CharacterBody
zvládať skákanie. Aby sme to dosiahli, pridáme metódu zodpovednú za skok:
// CharacterBody.cs public void Jump(float jumpSpeed) { Velocity = new Vector2(_velocity.x, jumpSpeed); State = CharacterState.Airborne; }
V našom prípade je táto metóda jednoduchá: nastaví vertikálnu rýchlosť a okamžite zmení stav postavy na Airborne
.
Ďalej musíme určiť rýchlosť, akou má postava skákať. Už sme definovali výšku skoku a vieme, že gravitácia neustále pôsobí na telo. Na základe toho je možné vypočítať počiatočnú rýchlosť skoku pomocou vzorca:
kde h je výška skoku a g je gravitačné zrýchlenie. Zohľadníme aj multiplikátor gravitácie prítomný v komponente CharacterBody
. Pridáme nové pole na definovanie počiatočnej rýchlosti skoku a vypočítame ju takto:
// CharacterController.cs private float _jumpSpeed; private void Awake() { _jumpSpeed = Mathf.Sqrt(2 * Physics2D.gravity.magnitude * _characterBody.GravityFactor * _jumpHeight); }
Budeme potrebovať ďalšie pole na sledovanie toho, či postava práve skáče, aby sme vo vhodnom čase mohli obmedziť rýchlosť skoku.
Navyše, ak hráč drží tlačidlo skoku až do pristátia, mali by sme túto vlajku resetovať sami. Toto sa vykoná v metóde Update
:
// CharacterController.cs private bool _isJumping; private void Update() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = false; } //... }
Teraz napíšme metódu na spracovanie akcie Jump
:
// CharacterController.cs public void OnJump(InputAction.CallbackContext context) { if (context.started) { Jump(); } else if (context.canceled) { StopJumping(); } }
Keďže akcia Jump
je tlačidlo, môžeme z kontextu určiť, či stlačenie tlačidla začalo ( context.started
) alebo skončilo ( context.canceled
). Na základe toho skok buď iniciujeme alebo zastavíme.
Tu je spôsob vykonania skoku:
// CharacterController.cs private void Jump() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = true; _characterBody.Jump(_jumpSpeed); } }
Pred skokom skontrolujeme, či je postava na zemi. Ak áno, nastavíme príznak _isJumping
a prinútime telo skákať pomocou _jumpSpeed
.
Teraz implementujme správanie preskakovania:
// CharacterController.cs private void StopJumping() { var velocity = _characterBody.Velocity; if (_isJumping && velocity.y > 0) { _isJumping = false; _characterBody.Velocity = new Vector2( velocity.x, velocity.y / _stopJumpFactor); } }
Skok zastavíme, len ak je aktívny príznak _isJumping
. Ďalšou dôležitou podmienkou je, že postava sa musí pohybovať nahor. To zabraňuje obmedzeniu rýchlosti pádu, ak sa tlačidlo skoku uvoľní pri pohybe nadol. Ak sú splnené všetky podmienky, resetujeme príznak _isJumping
a znížime vertikálnu rýchlosť o faktor _stopJumpFactor
.
Teraz, keď sú všetky komponenty pripravené, pridajte komponenty PlayerInput
a CharacterController
k objektu Captain na scéne. Uistite sa, že ste vybrali komponent CharacterController
, ktorý sme vytvorili, nie štandardný komponent Unity určený na ovládanie 3D postáv.
Pre CharacterController
priraďte existujúci komponent CharacterBody
k postave. Pre PlayerInput
nastavte predtým vytvorené ovládacie prvky v poli Actions
.
Ďalej nakonfigurujte komponent PlayerInput tak, aby volal príslušné metódy z CharacterController. Rozbaľte sekcie Udalosti a Postavy v editore a prepojte zodpovedajúce metódy s akciami Presun a Skok.
Teraz je všetko pripravené na spustenie hry a testovanie toho, ako všetky nakonfigurované komponenty spolupracujú.
Teraz musíme prinútiť kameru, aby sledovala postavu, kamkoľvek idú. Unity poskytuje výkonný nástroj na správu kamier — Cinemachine .
Cinemachine je revolučné riešenie pre ovládanie kamery v Unity, ktoré ponúka vývojárom širokú škálu možností na vytváranie dynamických, dobre vyladených kamerových systémov, ktoré sa prispôsobia herným potrebám. Tento nástroj uľahčuje implementáciu zložitých kamerových techník, ako je sledovanie postavy, automatické nastavenie zaostrenia a mnoho ďalších, čím dodáva každej scéne vitalitu a bohatosť.
Najprv nájdite na scéne objekt Main Camera a pridajte k nemu komponent CinemachineBrain
.
Ďalej vytvorte nový objekt v scéne s názvom CaptainCamera . Toto bude kamera, ktorá bude nasledovať kapitána, rovnako ako profesionálny kameraman. Pridajte k nemu komponent CinemachineVirtualCamera
. Nastavte pole Sledovať na kapitána, v poli Telo vyberte Rámovací transponér a nastavte parameter Lens Ortho Size na 4.
Okrem toho budeme potrebovať ďalší komponent na definovanie posunu kamery vzhľadom na postavu — CinemachineCameraOffset
. Nastavte hodnotu Y na 1,5 a hodnotu Z na -15.
Teraz si otestujme, ako kamera sleduje našu postavu.
Myslím, že to dopadlo celkom dobre. Všimol som si, že fotoaparát občas mierne zadrhá. Aby som to vyriešil, nastavil som pole Metóda aktualizácie zmesi v objekte hlavného fotoaparátu na FixedUpdate.
Poďme otestovať aktualizovanú mechaniku. Skúste behať a neustále skákať. Skúsení hráči si môžu všimnúť, že skoky nie vždy registrujú. Vo väčšine hier to nie je problém.
Ukazuje sa, že je ťažké predpovedať presný čas pristátia na opätovné stlačenie tlačidla skoku. Musíme urobiť hru viac odpúšťajúcou tým, že umožníme hráčom mierne stlačiť skok pred pristátím a postave tak okamžite po pristátí vyskočia. Toto správanie je v súlade s tým, na čo sú hráči zvyknutí.
Aby sme to mohli implementovať, zavedieme novú premennú _jumpActionTime
, ktorá predstavuje časové okno, počas ktorého sa môže skok spustiť, ak sa naskytne príležitosť.
// CharacterController.cs [Min(0)] [SerializeField] private float _jumpActionTime = 0.1f;
Pridal som pole _jumpActionEndTime
, ktoré označuje koniec okna akcie skoku. Inými slovami, kým sa nedosiahne _jumpActionEndTime
, postava bude skákať, ak sa naskytne príležitosť. Aktualizujme tiež obsluhu akcie Jump
.
// CharacterController.cs private float _jumpActionEndTime; public void OnJump(InputAction.CallbackContext context) { if (context.started) { if (_characterBody.State == CharacterState.Grounded) { Jump(); } else { _jumpActionEndTime = Time.unscaledTime + _jumpActionTime; } } else if (context.canceled) { StopJumping(); } }
Po stlačení tlačidla skoku, ak je postava na zemi, okamžite skočí. V opačnom prípade uložíme časové okno, počas ktorého je možné skok ešte vykonať.
Odstránime kontrolu Grounded
state zo samotnej metódy Jump
.
// CharacterController.cs private void Jump() { _isJumping = true; _characterBody.Jump(_jumpSpeed); }
Prispôsobíme aj metódu doskoku. Ak bolo tlačidlo uvoľnené pred pristátím, nemalo by dôjsť k skoku, takže resetujeme _jumpActionEndTime
.
// CharacterController.cs private void StopJumping() { _jumpActionEndTime = 0; //... }
Kedy by sme mali skontrolovať, či postava pristála a spustiť skok? Stav CharacterBody
sa spracuje v FixedUpdate
, zatiaľ čo spracovanie akcie nastane neskôr. Bez ohľadu na to, či ide o Update
alebo FixedUpdate
, medzi pristátím a skokom môže nastať jednosnímkové oneskorenie, čo je citeľné.
Do CharacterBody
pridáme udalosť StateChanged
, aby sme okamžite reagovali na pristátie. Prvý argument bude predchádzajúci stav a druhý bude aktuálny stav.
// CharacterBody.cs public event Action<CharacterState, CharacterState> StateChanged;
Upravíme správu stavu tak, aby spustila udalosť zmeny stavu a prepíšeme FixedUpdate
.
// CharacterBody.cs [field: SerializeField] private CharacterState _state; public CharacterState State { get => _state; private set { if (_state != value) { var previousState = _state; _state = value; StateChanged?.Invoke(previousState, value); } } }
Tiež som spresnil, ako sa s surfaceHit
pracuje v FixedUpdate
.
// CharacterBody.cs private void FixedUpdate() { //... if (_velocity.y <= 0 && slideResults.surfaceHit) { var surfaceHit = slideResults.surfaceHit; Velocity = ClipVector(_velocity, surfaceHit.normal); if (surfaceHit.normal.y >= _minGroundVertical) { State = CharacterState.Grounded; return; } } State = CharacterState.Airborne; }
V CharacterController
sa prihlásime na odber udalosti StateChanged
a pridáme obslužný program.
// CharacterController.cs private void OnEnable() { _characterBody.StateChanged += OnStateChanged; } private void OnDisable() { _characterBody.StateChanged -= OnStateChanged; } private void OnStateChanged(CharacterState previousState, CharacterState state) { if (state == CharacterState.Grounded) { OnGrounded(); } }
Odstránime kontrolu stavu Grounded
z Update
a presunieme ju do OnGrounded
.
// CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); } private void OnGrounded() { _isJumping = false; }
Teraz pridajte kód a skontrolujte, či sa má spustiť skok.
// CharacterController.cs private void OnGrounded() { _isJumping = false; if (_jumpActionEndTime > Time.unscaledTime) { _jumpActionEndTime = 0; Jump(); } }
Ak je _jumpActionEndTime
väčší ako aktuálny čas, znamená to, že tlačidlo skoku bolo stlačené nedávno, takže resetujeme _jumpActionEndTime
a vykonáme skok.
Teraz skúste neustále skákať s postavou. Všimnete si, že tlačidlo skoku je citlivejšie a ovládanie postavy je plynulejšie. Všimol som si však, že v určitých situáciách, ako je napríklad roh zobrazený na obrázku nižšie, zaznamená stav Grounded
mierne oneskorenie, čím sa preruší reťaz skokov.
Aby som to vyriešil, nastavil som pole Surface Anchor
v komponente CharacterBody
na 0,05 namiesto 0,01. Táto hodnota predstavuje minimálnu vzdialenosť od povrchu, aby telo vstúpilo do Grounded
stavu.
Možno ste si všimli, že pokus o skok pri behu z vertikálnych plôch nie vždy funguje. Niekedy sa môže zdať, že tlačidlo skoku nereaguje.
Toto je jedna z jemností vývoja ovládača znakov pre 2D plošinovky. Hráči potrebujú schopnosť skákať, aj keď sa stláčaním tlačidla skoku mierne oneskoria. Aj keď sa tento koncept môže zdať čudný, väčšina plošinoviek takto funguje. Výsledkom je postava, ktorá akoby vytláčala vzduch, ako ukazuje animácia nižšie.
Poďme implementovať túto mechaniku. Zavedieme nové pole na uloženie časového okna (v sekundách), počas ktorého môže postava stále skákať po strate stavu Grounded
.
// CharacterController.cs [Min(0)] [SerializeField] private float _rememberGroundTime = 0.1f;
Pridáme tiež ďalšie pole na uloženie časovej pečiatky, po ktorej sa Grounded
stav „zabudne“.
// CharacterController.cs private float _lostGroundTime;
Tento stav sa bude sledovať pomocou udalosti CharacterBody
. Na tento účel upravíme handler OnStateChanged
.
// CharacterController.cs private void OnStateChanged(CharacterState previousState, CharacterState state) { if (state == CharacterState.Grounded) { OnGrounded(); } else if (previousState == CharacterState.Grounded) { _lostGroundTime = Time.unscaledTime + _rememberGroundTime; } }
Je dôležité rozlíšiť, či postava stratila Grounded
stav kvôli úmyselnému skoku alebo z iného dôvodu. Už máme príznak _isJumping
, ktorý sa deaktivuje pri každom volaní StopJumping
, aby sa zabránilo nadbytočným akciám.
Rozhodol som sa nezaviesť ďalšiu vlajku, pretože zrušenie nadbytočného skoku neovplyvňuje hrateľnosť. Nebojte sa experimentovať. Príznak _isJumping
bude teraz vymazaný len vtedy, keď postava pristane po skoku. Podľa toho aktualizujme kód.
// CharacterController.cs private void StopJumping() { _jumpActionEndTime = 0; var velocity = _characterBody.Velocity; if (_isJumping && velocity.y > 0) { _characterBody.Velocity = new Vector2( velocity.x, velocity.y / _stopJumpFactor); } }
Nakoniec zrevidujeme metódu OnJump
.
// CharacterController.cs public void OnJump(InputAction.CallbackContext context) { if (context.started) { if (_characterBody.State == CharacterState.Grounded || (!_isJumping && _lostGroundTime > Time.unscaledTime)) { Jump(); } else { _jumpActionEndTime = Time.unscaledTime + _jumpActionTime; } } else if (context.canceled) { StopJumping(); } }
Teraz už skákanie z vertikálnych plôch nenarúša herný rytmus a pôsobí oveľa prirodzenejšie, napriek svojej zjavnej absurdnosti. Postava dokáže doslova vytlačiť vzduch a zájde ďalej, než sa zdá logické. Ale to je presne to, čo naša plošinovka potrebuje.
Posledným dotykom je, aby postava stála v smere pohybu. Implementujeme to najjednoduchším spôsobom — zmenou mierky postavy pozdĺž osi x. Nastavenie zápornej hodnoty spôsobí, že náš kapitán bude stáť opačným smerom.
Najprv si uložme pôvodnú stupnicu v prípade, že sa líši od 1.
// CharacterController.cs public class CharacterController : MonoBehaviour { //... private Vector3 _originalScale; private void Awake() { //... _originalScale = transform.localScale; } }
Teraz pri pohybe doľava alebo doprava použijeme kladnú alebo zápornú stupnicu.
// CharacterController.cs public class CharacterController : MonoBehaviour { public void OnMove(InputAction.CallbackContext context) { //... // Change character's direction. if (value.x != 0) { var scale = _originalScale; scale.x = value.x > 0 ? _originalScale.x : -_originalScale.x; transform.localScale = scale; } } }
Výsledok otestujeme.
Tento článok sa ukázal byť dosť podrobný, no podarilo sa nám pokryť všetky podstatné aspekty ovládania postavy v 2D plošinovke. Pripomíname, že konečný výsledok si môžete pozrieť vo vetve „ Character Controller “ v úložisku.
Ak sa vám páčil alebo vám tento a predchádzajúci článok pomohol, ocenil by som lajky a hviezdičky na GitHub. Neváhajte nás kontaktovať, ak narazíte na nejaké problémy alebo nájdete chyby. Ďakujem za pozornosť!