En aquest article, continuem desenvolupant un controlador de personatges per a un joc de plataformes 2D a Unity, examinant a fons cada pas de configuració i optimització dels controls. A l'article anterior, " ", vam comentar detalladament com crear la base del personatge, inclòs el seu comportament físic i el seu moviment bàsic. Ara és el moment de passar a aspectes més avançats, com ara el maneig d'entrada i el seguiment dinàmic de la càmera. Com crear un controlador de personatges 2D a Unity: Part 1 En aquest article, aprofundirem en la configuració del nou sistema d'entrada d'Unity, creant accions actives per controlar el personatge, permetre saltar i garantir les respostes adequades a les ordres dels jugadors. Si voleu implementar vosaltres mateixos tots els canvis descrits en aquest article, podeu descarregar la branca del repositori " ", que conté la base d'aquest article. Alternativament, podeu descarregar la branca " " amb el resultat final. Cos del caràcter Controlador de caràcters Configuració del sistema d'entrada Abans de començar a escriure codi per controlar el nostre personatge, hem de configurar el sistema d'entrada al projecte. Per al nostre joc de plataformes, hem escollit el nou sistema d'entrada d'Unity, introduït fa uns anys, que segueix sent rellevant pels seus avantatges respecte al sistema tradicional. El sistema d'entrada ofereix un enfocament més modular i flexible per al maneig d'entrada, que permet als desenvolupadors configurar fàcilment els controls per a diversos dispositius i donar suport a escenaris d'entrada més complexos sense sobrecàrrec d'implementació addicional. Primer, instal·leu el paquet del sistema d'entrada. Obriu el Gestor de paquets des del menú principal seleccionant Finestra → Gestor de paquets. A la secció Registre Unity, cerqueu el paquet "Sistema d'entrada" i feu clic a "Instal·la". A continuació, aneu a la configuració del projecte mitjançant el menú Edita → Configuració del projecte. Seleccioneu la pestanya Reproductor, cerqueu la secció Gestió activa d'entrada i configureu-la a "Paquet del sistema d'entrada (nou). Després de completar aquests passos, Unity us demanarà que reinicieu. Un cop reiniciat, tot estarà a punt per configurar els controls del nostre capità. Creació d'accions d'entrada A la carpeta , creeu Accions d'entrada mitjançant el menú principal: . Anomena el fitxer "Controls". Configuració Actius → Crea → Accions d'entrada El sistema d'entrada d'Unity és una eina de gestió d'entrada potent i flexible que permet als desenvolupadors configurar controls per als personatges i els elements del joc. Admet diversos dispositius d'entrada. Les accions d'entrada que creeu proporcionen una gestió centralitzada d'entrada, simplificant la configuració i fent que la interfície sigui més intuïtiva. Feu doble clic al fitxer per obrir-lo per editar-lo i afegiu un per al control de caràcters anomenat "Caràcter". de controls mapa d'acció Un mapa d'acció a Unity és una col·lecció d'accions que es poden enllaçar amb diversos controladors i claus per realitzar tasques específiques del joc. És una manera eficient d'organitzar els controls, que permet als desenvolupadors assignar i ajustar les entrades sense reescriure el codi. Per obtenir més detalls, consulteu la . documentació oficial del sistema d'entrada La primera acció s'anomenarà "Mou". Aquesta acció definirà la direcció del moviment del personatge. Estableix el a "Valor" i el a "Vector2" per permetre el moviment en quatre direccions. tipus d'acció tipus de control Assigneu enllaços a aquesta acció seleccionant Afegeix composició amunt/avall/dreta/esquerra i assignant les tecles WASD conegudes a les seves direccions respectives. No us oblideu de desar la configuració fent clic a . Aquesta configuració garanteix que podeu reassignar els enllaços per a l'acció "Mou", per exemple, a les tecles de fletxa o fins i tot a un joystick del gamepad. Desa l'actiu A continuació, afegiu una acció nova: "Saltar". Mantingueu el com a "Botó", però afegiu una nova : "Premeu" i configureu a "Premeu i deixeu anar", ja que hem de capturar tant el botó com el botó. tipus d'acció interacció el comportament de l'activador Això completa l'esquema de control de caràcters. El següent pas és escriure un component per gestionar aquestes accions. Moure el personatge cap a l'esquerra i la dreta És hora d'enllaçar les accions d'entrada que hem creat per al control de personatges al component , permetent que el personatge es mogui activament per l'escena segons les nostres ordres de control. CharacterBody Per fer-ho, crearem un script responsable del control del moviment i l'anomenarem per a més claredat. En aquest script, primer definirem alguns camps bàsics. Afegirem una referència al component , , que serà controlat directament per l'script. CharacterController CharacterBody _characterBody També establirem paràmetres per a la velocitat de moviment del personatge ( ) i l'alçada de salt ( ). A més, definirem el propòsit del camp . _speed _jumpHeight _stopJumpFactor És possible que hàgiu notat que en molts jocs de plataformes 2D, l'alçada del salt es pot controlar. Com més temps estigui premut el botó de salt, més alt saltarà el personatge. Essencialment, s'aplica una velocitat inicial ascendent a l'inici del salt, i aquesta velocitat es redueix quan es deixa anar el botó. El determina quant disminueix la velocitat ascendent en deixar anar el botó de salt. _stopJumpFactor Aquí teniu un exemple del codi que escriurem: // 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; } A continuació, implementarem la capacitat de moure el personatge cap a l'esquerra i la dreta. En mantenir premut el botó de moviment, el personatge hauria de mantenir la velocitat de moviment especificada independentment dels obstacles. Per aconseguir-ho, afegirem una variable a l'script per emmagatzemar la velocitat de moviment actual al llarg de la superfície (o simplement horitzontalment quan el personatge està a l'aire): // CharacterController.cs private float _locomotionVelocity; Al component , introduirem un mètode per establir aquesta velocitat: CharacterBody // CharacterBody.cs public void SetLocomotionVelocity(float locomotionVelocity) { Velocity = new Vector2(locomotionVelocity, _velocity.y); } Com que el nostre joc no té superfícies inclinades, aquest mètode és bastant senzill. En escenaris més complexos, hauríem de tenir en compte l'estat del cos i el pendent de la superfície. De moment, simplement conservem la component vertical de la velocitat mentre modifiquem només la coordenada horitzontal. x A continuació, establirem aquest valor al mètode a cada fotograma: Update // CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); } Definirem un mètode per gestionar els senyals de l'acció d'entrada de : Move // CharacterController.cs public void OnMove(InputAction.CallbackContext context) { var value = context.ReadValue<Vector2>(); _locomotionVelocity = value.x * _speed; } Com que l'acció es defineix com a , el context proporcionarà un valor vectorial en funció de quines tecles es premeu o allibereu. Per exemple, si premeu la tecla , el mètode rebi el vector (1, 0). Si premeu i simultàniament, es produirà (1, 1). Alliberar totes les tecles activarà amb el valor (0, 0). Move Vector2 D OnMove D W OnMove Per a la clau , el vector serà (-1, 0). En el mètode , prenem la component horitzontal del vector rebut i la multipliquem per la velocitat de moviment especificada, . A OnMove _speed Ensenyar al personatge a saltar Primer, hem d'ensenyar el component a manejar els salts. Per fer-ho, afegirem un mètode responsable del salt: CharacterBody // CharacterBody.cs public void Jump(float jumpSpeed) { Velocity = new Vector2(_velocity.x, jumpSpeed); State = CharacterState.Airborne; } En el nostre cas, aquest mètode és senzill: estableix la velocitat vertical i canvia immediatament l'estat del personatge a . Airborne A continuació, hem de determinar la velocitat a la qual ha de saltar el personatge. Ja hem definit l'alçada del salt i sabem que la gravetat actua constantment sobre el cos. A partir d'això, la velocitat de salt inicial es pot calcular mitjançant la fórmula: On és l'alçada del salt i l'acceleració gravitatòria. També comptarem amb el multiplicador de gravetat present al component . Afegirem un camp nou per definir la velocitat de salt inicial i la calcularem de la següent manera: h g CharacterBody // CharacterController.cs private float _jumpSpeed; private void Awake() { _jumpSpeed = Mathf.Sqrt(2 * Physics2D.gravity.magnitude * _characterBody.GravityFactor * _jumpHeight); } Necessitarem un altre camp per fer un seguiment de si el personatge està saltant actualment, de manera que podem limitar la velocitat de salt en el moment adequat. A més, si el jugador manté premut el botó de salt fins a aterrar, hauríem de restablir aquesta bandera nosaltres mateixos. Això es farà amb el mètode : Update // CharacterController.cs private bool _isJumping; private void Update() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = false; } //... } Ara, escrivim el mètode per gestionar l'acció : Jump // CharacterController.cs public void OnJump(InputAction.CallbackContext context) { if (context.started) { Jump(); } else if (context.canceled) { StopJumping(); } } Com que l'acció és un botó, podem determinar a partir del context si la pressió del botó s'ha iniciat ( ) o finalitzada ( ). En base a això, iniciem o aturem el salt. Jump context.started context.canceled Aquest és el mètode per executar el salt: // CharacterController.cs private void Jump() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = true; _characterBody.Jump(_jumpSpeed); } } Abans de saltar, comprovem si el personatge està a terra. Si és així, posem la bandera i fem que el cos salti amb la . _isJumping _jumpSpeed Ara, implementem el comportament de parar-salt: // 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); } } Aturem el salt només si la bandera està activa. Una altra condició important és que el personatge s'hagi de moure cap amunt. Això evita limitar la velocitat de caiguda si es deixa anar el botó de salt mentre es mou cap avall. Si es compleixen totes les condicions, restablirem la bandera i reduïm la velocitat vertical en un factor de . _isJumping _isJumping _stopJumpFactor Configuració del personatge Ara que tots els components estan preparats, afegiu els components i a l'objecte de l'escena. Assegureu-vos de seleccionar el component que hem creat, no el component Unity estàndard dissenyat per controlar personatges 3D. PlayerInput CharacterController Captain CharacterController Per al , assigneu el component existent del personatge. Per a , establiu els creats anteriorment al camp . CharacterController CharacterBody PlayerInput controls Actions A continuació, configureu el component PlayerInput per cridar els mètodes adequats des de CharacterController. Amplieu les seccions Esdeveniments i Caràcter a l'editor i enllaçeu els mètodes corresponents a les accions Moure i Saltar. Ara, tot està preparat per executar el joc i provar com funcionen tots els components configurats junts. Moviment de càmera Ara, hem de fer que la càmera segueixi el personatge allà on vagi. Unity proporciona una potent eina per a la gestió de càmeres: . Cinemachine Cinemachine és una solució revolucionària per al control de càmeres a Unity que ofereix als desenvolupadors una àmplia gamma de capacitats per crear sistemes de càmeres dinàmics i ben ajustats que s'adaptin a les necessitats de joc. Aquesta eina facilita la implementació de tècniques complexes de càmera, com ara el seguiment de personatges, l'ajust automàtic de l'enfocament i molt més, afegint vitalitat i riquesa a cada escena. Primer, localitzeu l'objecte a l'escena i afegiu-hi el component . de la càmera principal CinemachineBrain A continuació, creeu un objecte nou a l'escena anomenat . Aquesta serà la càmera que segueixi el capità, com un càmera professional. Afegiu-hi el component . Establiu el camp al capità, seleccioneu per al camp i configureu el paràmetre a 4. CaptainCamera CinemachineVirtualCamera Seguiu Framing Transposer Body Lens Ortho Size A més, necessitarem un altre component per definir el desplaçament de la càmera en relació amb el personatge: . Estableix el valor a 1,5 i el valor a -15. CinemachineCameraOffset Y Z Ara, anem a provar com la càmera segueix el nostre personatge. Crec que va sortir força bé. Em vaig adonar que la càmera de tant en tant tartamudeja lleugerament. Per solucionar-ho, he definit el camp Mètode d'actualització de combinació de l'objecte de la càmera principal a FixedUpdate. Millora dels salts Anem a provar la mecànica actualitzada. Intenta córrer i saltar contínuament. Els jugadors experimentats poden notar que els salts no sempre es registren. A la majoria de jocs, això no és un problema. Resulta que és difícil predir l'hora exacta d'aterratge per tornar a prémer el botó de salt. Hem de fer que el joc sigui més indulgent permetent als jugadors que premeu el salt lleugerament abans d'aterrar i que el personatge salti immediatament després d'aterrar. Aquest comportament s'alinea amb el que els jugadors estan acostumats. Per implementar-ho, introduirem una nova variable, , que representa la finestra de temps durant la qual encara es pot activar un salt si es presenta l'oportunitat. _jumpActionTime // CharacterController.cs [Min(0)] [SerializeField] private float _jumpActionTime = 0.1f; He afegit un camp , que marca el final de la finestra d'acció de salt. En altres paraules, fins que s'arribi , el personatge saltarà si es presenta l'oportunitat. També actualitzem el controlador d'accions . _jumpActionEndTime _jumpActionEndTime 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(); } } Quan es prem el botó de salt, si el personatge està a terra, salta immediatament. En cas contrari, emmagatzemem la finestra de temps durant la qual encara es pot realitzar el salt. Eliminem la comprovació d'estat del mètode . Grounded Jump // CharacterController.cs private void Jump() { _isJumping = true; _characterBody.Jump(_jumpSpeed); } També adaptarem el mètode de stop-jumping. Si el botó es va deixar anar abans d'aterrar, no s'hauria de produir cap salt, de manera que restablirem . _jumpActionEndTime // CharacterController.cs private void StopJumping() { _jumpActionEndTime = 0; //... } Quan hem de comprovar que el personatge ha aterrat i desencadenar un salt? L'estat es processa a , mentre que el processament de l'acció es produeix més tard. Independentment de si és o , es pot produir un retard d'un fotograma entre l'aterratge i el salt, cosa que és notable. CharacterBody FixedUpdate Update FixedUpdate Afegirem un esdeveniment a per respondre a l'instant a l'aterratge. El primer argument serà l'estat anterior i el segon serà l'estat actual. StateChanged CharacterBody // CharacterBody.cs public event Action<CharacterState, CharacterState> StateChanged; Ajustarem la gestió de l'estat per activar l'esdeveniment de canvi d'estat i tornarem a escriure . 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); } } } També vaig perfeccionar com es gestiona a . surfaceHit 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; } A , ens subscriurem a l'esdeveniment i afegirem un controlador. CharacterController StateChanged // 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(); } } Eliminarem la comprovació de l'estat i la traslladarem a . Grounded Update OnGrounded // CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); } private void OnGrounded() { _isJumping = false; } Ara, afegiu el codi per comprovar si s'ha d'activar un salt. // CharacterController.cs private void OnGrounded() { _isJumping = false; if (_jumpActionEndTime > Time.unscaledTime) { _jumpActionEndTime = 0; Jump(); } } Si és més gran que l'hora actual, vol dir que el botó de salt s'ha premut recentment, de manera que restablirem i realitzem el salt. _jumpActionEndTime _jumpActionEndTime Ara, prova de saltar contínuament amb el personatge. Notareu que el botó de salt se sent més sensible i controlar el personatge es fa més suau. Tanmateix, vaig observar que en determinades situacions, com ara la cantonada que es mostra a la il·lustració següent, l'estat experimenta un lleuger retard, interrompent la cadena de salt. Grounded Per solucionar-ho, he establert el camp al component a 0,05 en lloc de 0,01. Aquest valor representa la distància mínima a una superfície perquè el cos entri a l'estat . Surface Anchor CharacterBody Grounded Salt de penya-segat És possible que hagis notat que intentar saltar mentre corres per superfícies verticals no sempre funciona. De vegades pot semblar que el botó de salt no respon. Aquesta és una de les subtileses del desenvolupament d'un per a plataformes 2D. Els jugadors necessiten la capacitat de saltar fins i tot quan arribin una mica tard en prémer el botó de salt. Tot i que aquest concepte pot semblar estrany, és com funcionen la majoria de plataformes. El resultat és un personatge que sembla expulsar l'aire, com es demostra a l'animació següent. controlador de personatges Implementem aquesta mecànica. Introduirem un camp nou per emmagatzemar la finestra de temps (en segons) durant la qual el personatge encara pot saltar després de perdre l'estat . Grounded // CharacterController.cs [Min(0)] [SerializeField] private float _rememberGroundTime = 0.1f; També afegirem un altre camp per emmagatzemar la marca de temps després del qual s'"oblida" l'estat . Grounded // CharacterController.cs private float _lostGroundTime; Aquest estat es farà un seguiment mitjançant l'esdeveniment . Ajustarem el controlador per a aquest propòsit. CharacterBody OnStateChanged // CharacterController.cs private void OnStateChanged(CharacterState previousState, CharacterState state) { if (state == CharacterState.Grounded) { OnGrounded(); } else if (previousState == CharacterState.Grounded) { _lostGroundTime = Time.unscaledTime + _rememberGroundTime; } } És important distingir si el personatge va perdre l'estat a causa d'un salt intencionat o per un altre motiu. Ja tenim el senyalador , que es desactiva cada vegada que es crida per evitar accions redundants. Grounded _isJumping StopJumping Vaig decidir no introduir una altra bandera ja que la cancel·lació de salt redundant no afecta el joc. No dubteu a experimentar. La bandera ara només s'esborrarà quan el personatge caigui després de saltar. Actualitzem el codi en conseqüència. _isJumping // 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); } } Finalment, revisarem el mètode . 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(); } } Ara, saltar de superfícies verticals ja no pertorba el ritme de joc i se sent molt més natural, malgrat el seu aparent absurd. El personatge pot, literalment, expulsar l'aire, anant més lluny del que sembla lògic. Però això és exactament el que es necessita per al nostre joc de plataformes. Flip del personatge El toc final és fer que el personatge s'enfronti a la direcció del moviment. Ho implementarem de la manera més senzilla: canviant l'escala del personatge al llarg de l'eix x. Establir un valor negatiu farà que el nostre capità s'enfronti en sentit contrari. En primer lloc, emmagatzemem l'escala original per si difereix d'1. // CharacterController.cs public class CharacterController : MonoBehaviour { //... private Vector3 _originalScale; private void Awake() { //... _originalScale = transform.localScale; } } Ara, quan ens movem cap a l'esquerra o la dreta, aplicarem una escala positiva o negativa. // 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; } } } Anem a provar el resultat. Embolcallant Aquest article va resultar bastant detallat, però vam aconseguir cobrir tots els aspectes essencials del control dels personatges en un joc de plataformes 2D. Com a recordatori, podeu consultar el resultat final a la branca " " del dipòsit. Controlador de caràcters Si us ha agradat o us ha semblat útil aquest article i l'anterior, us agrairia els m'agrada i les estrelles a GitHub. No dubteu a posar-vos en contacte si teniu cap problema o trobeu errors. Gràcies per la vostra atenció!