En este artículo, continuamos desarrollando un controlador de personaje para un juego de plataformas 2D en Unity, examinando exhaustivamente cada paso de configuración y optimización de los controles. En el artículo anterior, “ ”, analizamos en detalle cómo crear la base del personaje, incluido su comportamiento físico y movimiento básico. Ahora, es momento de pasar a aspectos más avanzados, como el manejo de entradas y el seguimiento dinámico de la cámara. Cómo crear un controlador de personajes 2D en Unity: Parte 1 En este artículo, profundizaremos en la configuración del nuevo sistema de entrada de Unity, la creación de acciones activas para controlar el personaje, la habilitación de saltos y la garantía de respuestas adecuadas a los comandos del jugador. Si quieres implementar tú mismo todos los cambios descritos en este artículo, puedes descargar la rama del repositorio “ ”, que contiene la base de este artículo. Alternativamente, puedes descargar la rama “ ” con el resultado final. Character Body Character Controller Configuración del sistema de entrada Antes de empezar a escribir el código para controlar a nuestro personaje, debemos configurar el sistema de entrada en el proyecto. Para nuestro juego de plataformas, hemos elegido el nuevo sistema de entrada de Unity, introducido hace unos años, que sigue siendo relevante debido a sus ventajas sobre el sistema tradicional. El sistema de entrada ofrece un enfoque más modular y flexible para el manejo de entrada, lo que permite a los desarrolladores configurar fácilmente controles para varios dispositivos y admitir escenarios de entrada más complejos sin sobrecarga de implementación adicional. En primer lugar, instale el paquete Input System. Abra el Administrador de paquetes desde el menú principal seleccionando Ventana → Administrador de paquetes. En la sección Registro de Unity, busque el paquete "Input System" y haga clic en "Instalar". A continuación, acceda a la configuración del proyecto a través del menú Editar → Configuración del proyecto. Seleccione la pestaña Reproductor, busque la sección Manejo de entrada activa y configúrela en "Paquete del sistema de entrada (nuevo)". Luego de completar estos pasos, Unity te pedirá que reinicies. Una vez reiniciado, todo estará listo para configurar los controles de nuestro capitán. Creación de acciones de entrada En la carpeta , cree acciones de entrada a través del menú principal: . Nombre el archivo "Controles". Configuración Activos → Crear → Acciones de entrada El sistema de entrada de Unity es una herramienta de gestión de entrada potente y flexible que permite a los desarrolladores configurar controles para personajes y elementos del juego. Admite varios dispositivos de entrada. Las acciones de entrada que crea proporcionan una gestión de entrada centralizada, lo que simplifica la configuración y hace que la interfaz sea más intuitiva. Haga doble clic en el archivo para abrirlo y editarlo y agregue un para el control del personaje llamado "Personaje". Controles Mapa de acción Un mapa de acciones en Unity es una colección de acciones que se pueden vincular a varios controladores y teclas para realizar tareas específicas en el juego. Es una forma eficiente de organizar los controles, lo que permite a los desarrolladores asignar y ajustar las entradas sin tener que reescribir el código. Para obtener más detalles, consulte la . documentación oficial del sistema de entrada La primera acción se llamará "Mover". Esta acción definirá la dirección del movimiento del personaje. Establezca el en "Valor" y el en "Vector2" para habilitar el movimiento en cuatro direcciones. tipo de acción tipo de control Asigne enlaces a esta acción seleccionando Agregar compuesto arriba/abajo/derecha/izquierda y asignando las conocidas teclas WASD a sus respectivas direcciones. No olvides guardar tu configuración haciendo clic en . Esta configuración garantiza que puedas reasignar asignaciones para la acción "Mover", por ejemplo, a las teclas de flecha o incluso a un joystick de gamepad. Guardar activo A continuación, agregue una nueva acción: "Saltar". Mantenga el como "Botón", pero agregue una nueva : "Presionar", y configure como "Presionar y soltar", ya que necesitamos capturar tanto la presión como la liberación del botón. tipo de acción interacción el comportamiento del disparador Con esto se completa el esquema de control de caracteres. El siguiente paso es escribir un componente para gestionar estas acciones. Mover el personaje hacia la izquierda y hacia la derecha Es hora de vincular las acciones de entrada que creamos para el control del personaje al componente , permitiendo que el personaje se mueva activamente por la escena de acuerdo con nuestros comandos de control. CharacterBody Para ello, crearemos un script responsable del control de movimiento y lo llamaremos para mayor claridad. En este script, primero definiremos algunos campos básicos. Agregaremos una referencia al componente , , que será controlado directamente por el script. CharacterController CharacterBody _characterBody También estableceremos parámetros para la velocidad de movimiento del personaje ( ) y la altura del salto ( ). Además, definiremos el propósito del campo . _speed _jumpHeight _stopJumpFactor Es posible que hayas notado que en muchos juegos de plataformas en 2D se puede controlar la altura del salto. Cuanto más tiempo se mantenga presionado el botón de salto, más alto saltará el personaje. Básicamente, se aplica una velocidad ascendente inicial al comienzo del salto, y esta velocidad se reduce cuando se suelta el botón. El determina cuánto disminuye la velocidad ascendente al soltar el botón de salto. _stopJumpFactor Aquí hay un ejemplo del código que escribiremos: // 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ón, implementaremos la capacidad de mover al personaje hacia la izquierda y hacia la derecha. Al mantener presionado el botón de movimiento, el personaje debe mantener la velocidad de movimiento especificada independientemente de los obstáculos. Para lograr esto, agregaremos una variable en el script para almacenar la velocidad de movimiento actual a lo largo de la superficie (o simplemente horizontalmente cuando el personaje está en el aire): // CharacterController.cs private float _locomotionVelocity; En el componente , presentaremos un método para establecer esta velocidad: CharacterBody // CharacterBody.cs public void SetLocomotionVelocity(float locomotionVelocity) { Velocity = new Vector2(locomotionVelocity, _velocity.y); } Dado que nuestro juego no tiene superficies inclinadas, este método es bastante simple. En escenarios más complejos, tendríamos que tener en cuenta el estado del cuerpo y la pendiente de la superficie. Por ahora, simplemente conservamos el componente vertical de la velocidad y modificamos solo la coordenada horizontal. x A continuación, estableceremos este valor en el método en cada cuadro: Update // CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); } Definiremos un método para manejar las señales de la acción de entrada : Move // CharacterController.cs public void OnMove(InputAction.CallbackContext context) { var value = context.ReadValue<Vector2>(); _locomotionVelocity = value.x * _speed; } Dado que la acción se define como un , el contexto proporcionará un valor vectorial según las teclas que se presionen o suelten. Por ejemplo, si se presiona la tecla , el método recibirá el vector (1, 0). Si se presionan y simultáneamente, se obtendrá (1, 1). Si se sueltan todas las teclas, se activará con el valor (0, 0). Move Vector2 D OnMove D W OnMove Para la tecla , el vector será (-1, 0). En el método , tomamos el componente horizontal del vector recibido y lo multiplicamos por la velocidad de movimiento especificada, . A OnMove _speed Enseñar al personaje a saltar Primero, debemos enseñarle al componente a manejar los saltos. Para ello, agregaremos un método responsable del salto: CharacterBody // CharacterBody.cs public void Jump(float jumpSpeed) { Velocity = new Vector2(_velocity.x, jumpSpeed); State = CharacterState.Airborne; } En nuestro caso, este método es sencillo: establece la velocidad vertical y cambia inmediatamente el estado del personaje a . Airborne A continuación, debemos determinar la velocidad a la que debe saltar el personaje. Ya hemos definido la altura del salto y sabemos que la gravedad actúa constantemente sobre el cuerpo. En base a esto, la velocidad inicial del salto se puede calcular mediante la fórmula: Donde es la altura del salto y es la aceleración gravitacional. También tendremos en cuenta el multiplicador de gravedad presente en el componente . Agregaremos un nuevo campo para definir la velocidad de salto inicial y la calcularemos de la siguiente manera: h g CharacterBody // CharacterController.cs private float _jumpSpeed; private void Awake() { _jumpSpeed = Mathf.Sqrt(2 * Physics2D.gravity.magnitude * _characterBody.GravityFactor * _jumpHeight); } Necesitaremos otro campo para rastrear si el personaje está saltando actualmente, para que podamos limitar la velocidad del salto en el momento apropiado. Además, si el jugador mantiene presionado el botón de salto hasta aterrizar, nosotros mismos deberíamos restablecer esta bandera. Esto se hará en el método : Update // CharacterController.cs private bool _isJumping; private void Update() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = false; } //... } Ahora, escribamos el método para manejar la acción : Jump // CharacterController.cs public void OnJump(InputAction.CallbackContext context) { if (context.started) { Jump(); } else if (context.canceled) { StopJumping(); } } Como la acción es un botón, podemos determinar a partir del contexto si la pulsación del botón ha comenzado ( ) o finalizado ( ). En función de esto, iniciamos o detenemos el salto. Jump context.started context.canceled Aquí está el método para ejecutar el salto: // CharacterController.cs private void Jump() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = true; _characterBody.Jump(_jumpSpeed); } } Antes de saltar, comprobamos si el personaje está en el suelo. Si es así, activamos el indicador y hacemos que el cuerpo salte con . _isJumping _jumpSpeed Ahora, implementemos el comportamiento de dejar de saltar: // 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); } } Detenemos el salto solo si el indicador está activo. Otra condición importante es que el personaje debe estar moviéndose hacia arriba. Esto evita limitar la velocidad de caída si se suelta el botón de salto mientras se mueve hacia abajo. Si se cumplen todas las condiciones, reiniciamos el indicador y reducimos la velocidad vertical por un factor de . _isJumping _isJumping _stopJumpFactor Configuración del personaje Ahora que todos los componentes están listos, agrega los componentes y al objeto en la escena. Asegúrate de seleccionar el componente que creamos, no el componente estándar de Unity diseñado para controlar personajes 3D. PlayerInput CharacterController Captain CharacterController Para , asigne el componente existente del personaje. Para , configure los creados previamente en el campo . CharacterController CharacterBody PlayerInput controles Actions A continuación, configure el componente PlayerInput para llamar a los métodos adecuados desde CharacterController. Expanda las secciones Eventos y Personaje en el editor y vincule los métodos correspondientes a las acciones Mover y Saltar. Ahora, todo está listo para ejecutar el juego y probar cómo funcionan juntos todos los componentes configurados. Movimiento de cámara Ahora, necesitamos que la cámara siga al personaje a donde quiera que vaya. Unity ofrece una herramienta poderosa para la gestión de cámaras: . Cinemachine Cinemachine es una solución revolucionaria para el control de cámaras en Unity que ofrece a los desarrolladores una amplia gama de capacidades para crear sistemas de cámara dinámicos y bien ajustados que se adaptan a las necesidades del juego. Esta herramienta facilita la implementación de técnicas de cámara complejas, como el seguimiento de personajes, el ajuste automático del enfoque y mucho más, agregando vitalidad y riqueza a cada escena. Primero, ubique el objeto en la escena y agréguele el componente . Cámara principal CinemachineBrain A continuación, crea un nuevo objeto en la escena llamado . Esta será la cámara que seguirá al capitán, como un camarógrafo profesional. Agrega el componente . Establece el campo en el capitán, elige para el campo y establece el parámetro en 4. CaptainCamera CinemachineVirtualCamera Follow Framing Transposer Body Lens Ortho Size Además, necesitaremos otro componente para definir el desplazamiento de la cámara en relación con el personaje: . Establezca el valor en 1,5 y el valor en -15. CinemachineCameraOffset Y Z Ahora, probemos cómo la cámara sigue a nuestro personaje. Creo que quedó bastante bien. Noté que la cámara a veces se traba un poco. Para solucionarlo, establecí el campo Método de actualización de mezcla del objeto Cámara principal en Actualización fija. Mejorando los saltos Probemos la mecánica actualizada. Prueba a correr y saltar continuamente. Los jugadores experimentados pueden notar que los saltos no siempre se registran. En la mayoría de los juegos, esto no es un problema. Resulta que es difícil predecir el momento exacto en el que se debe presionar el botón de salto nuevamente. Necesitamos hacer que el juego sea más indulgente y permitir que los jugadores presionen el botón de salto ligeramente antes de aterrizar y que el personaje salte inmediatamente después de aterrizar. Este comportamiento se alinea con lo que los jugadores están acostumbrados. Para implementar esto, introduciremos una nueva variable, , que representa la ventana de tiempo durante la cual aún se puede activar un salto si surge la oportunidad. _jumpActionTime // CharacterController.cs [Min(0)] [SerializeField] private float _jumpActionTime = 0.1f; Agregué un campo , que marca el final de la ventana de acción de salto. En otras palabras, hasta que se alcance , el personaje saltará si surge la oportunidad. Actualicemos también el controlador de acción . _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(); } } Al pulsar el botón de salto, si el personaje está en el suelo, salta inmediatamente. De lo contrario, almacenamos el intervalo de tiempo durante el cual aún puede realizar el salto. Eliminaremos la comprobación del estado del propio método . Grounded Jump // CharacterController.cs private void Jump() { _isJumping = true; _characterBody.Jump(_jumpSpeed); } También adaptaremos el método de parada de salto. Si se soltó el botón antes de aterrizar, no debería producirse ningún salto, por lo que reiniciamos . _jumpActionEndTime // CharacterController.cs private void StopJumping() { _jumpActionEndTime = 0; //... } ¿Cuándo debemos comprobar que el personaje ha aterrizado y activar un salto? El estado se procesa en , mientras que el procesamiento de la acción se produce más tarde. Independientemente de si se trata de o , puede producirse un retraso de un fotograma entre el aterrizaje y el salto, lo cual es notable. CharacterBody FixedUpdate Update FixedUpdate Agregaremos un evento a para responder instantáneamente al aterrizaje. El primer argumento será el estado anterior y el segundo será el estado actual. StateChanged CharacterBody // CharacterBody.cs public event Action<CharacterState, CharacterState> StateChanged; Ajustaremos la gestión del estado para activar el evento de cambio de estado y reescribiremos . 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); } } } También perfeccioné cómo se maneja en . 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; } En , nos suscribiremos al evento y agregaremos 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(); } } Eliminaremos la verificación del estado de y la moveremos a . Grounded Update OnGrounded // CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); } private void OnGrounded() { _isJumping = false; } Ahora, agregue el código para verificar si se debe activar un salto. // CharacterController.cs private void OnGrounded() { _isJumping = false; if (_jumpActionEndTime > Time.unscaledTime) { _jumpActionEndTime = 0; Jump(); } } Si es mayor que la hora actual, significa que el botón de salto se presionó recientemente, por lo que reiniciamos y realizamos el salto. _jumpActionEndTime _jumpActionEndTime Ahora, intenta saltar continuamente con el personaje. Notarás que el botón de salto responde mejor y que controlar al personaje se vuelve más fluido. Sin embargo, observé que en ciertas situaciones, como la esquina que se muestra en la ilustración a continuación, el estado experimenta un ligero retraso, lo que interrumpe la cadena de saltos. Grounded Para solucionar este problema, establecí el campo en el componente en 0,05 en lugar de 0,01. Este valor representa la distancia mínima a una superficie para que el cuerpo entre en el estado . Surface Anchor CharacterBody Grounded Salto desde el acantilado Es posible que hayas notado que intentar saltar mientras corres por superficies verticales no siempre funciona. A veces, puedes sentir que el botón de salto no responde. Esta es una de las sutilezas del desarrollo de un para plataformas 2D. Los jugadores necesitan la capacidad de saltar incluso cuando se demoran un poco en presionar el botón de salto. Si bien este concepto puede parecer extraño, es así como funcionan la mayoría de los juegos de plataformas. El resultado es un personaje que parece impulsarse en el aire, como se muestra en la animación a continuación. controlador de personajes Implementaremos esta mecánica. Introduciremos un nuevo campo para almacenar la ventana de tiempo (en segundos) durante la cual el personaje aún puede saltar después de perder el estado . Grounded // CharacterController.cs [Min(0)] [SerializeField] private float _rememberGroundTime = 0.1f; También agregaremos otro campo para almacenar la marca de tiempo después de la cual se "olvida" el estado . Grounded // CharacterController.cs private float _lostGroundTime; Este estado se rastreará mediante el evento . Ajustaremos el controlador para este propósito. 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; } } Es importante distinguir si el personaje perdió el estado debido a un salto intencional o por otra razón. Ya tenemos el indicador , que se desactiva cada vez que se llama para evitar acciones redundantes. Grounded _isJumping StopJumping Decidí no introducir otra bandera, ya que la cancelación redundante de saltos no afecta la jugabilidad. Siéntete libre de experimentar. La bandera ahora solo se borrará cuando el personaje aterrice después de saltar. Actualicemos el código en consecuencia. _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); } } Por último, revisaremos el método . 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(); } } Ahora, saltar desde superficies verticales ya no interrumpe el ritmo del juego y resulta mucho más natural, a pesar de su aparente absurdo. El personaje puede literalmente impulsarse desde el aire, yendo más lejos de lo que parece lógico. Pero esto es exactamente lo que se necesita para nuestro juego de plataformas. Cambio de personaje El toque final es hacer que el personaje mire en la dirección del movimiento. Lo haremos de la forma más sencilla: cambiando la escala del personaje a lo largo del eje x. Si se establece un valor negativo, nuestro capitán mirará en la dirección opuesta. Primero, almacenemos la escala original en caso de que difiera de 1. // CharacterController.cs public class CharacterController : MonoBehaviour { //... private Vector3 _originalScale; private void Awake() { //... _originalScale = transform.localScale; } } Ahora, al movernos hacia la izquierda o hacia la derecha, aplicaremos 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; } } } Probemos el resultado. Terminando Este artículo resultó ser bastante detallado, pero logramos cubrir todos los aspectos esenciales del control de personajes en un juego de plataformas en 2D. Como recordatorio, puedes consultar el resultado final en la rama “ ” del repositorio. Controlador de personajes Si disfrutaste o te resultó útil este artículo y el anterior, agradecería que me dieras "Me gusta" y que le dieras estrellas en GitHub. No dudes en comunicarte conmigo si tienes algún problema o encuentras errores. ¡Gracias por tu atención!