Table of contents
Este artículo fue publicado originalmente como un tutorial en la Epic Developer Community.
Tutoriales
En el siguiente vídeo muestro cómo funciona el Custom Movement Mode del componente CharacterMovementComponent
en C++ y como podemos usarlo para añadir a nuestros personajes un nuevo movimiento. Para ilustrar su uso, implementamos un sencillo prototipo del movimiento de correr por la pared (Wall Run).
Mientras que en este, terminamos el prototipo añadiendo algunas mejoras que faltaban, como el uso de animaciones adecuadas o curvas para ajustar algunos parámetros del movimiento.
Al final de este artículo hay una lista de mejoras pendientes que podrían ser interesantes de implementar.
Modos de movimiento
Como es bien sabido, los actores de la clase Character
incluyen un componente CharacterMovementComponent
que se encarga del movimiento del actor por la escena, según el modo de movimiento activo y la entrada recibida del jugador o —en el caso de los Personajes No Jugador (PNJ)— las indicaciones de la IA.
El CharacterMovementComponent
soporta por defecto múltiples modos de movimiento, permitiendo al componente cambiar su comportamiento, procesando de manera diferente la entrada del jugador y considerando, según el caso, diferentes fenómenos físicos:
Walking, utilizado cuando el personaje camina o corre por una superficie.
Falling, empleado cuando el personaje está cayendo o saltando. En este modo se simula el efecto de la gravedad, que hace que el personaje caiga al vacío.
Swimminng, utilizado cuando el personaje tiene que nadar a través de un líquido, de manera que el componente tiene que simular los efectos de la gravedad y la flotabilidad en el medio donde nada.
Flying, usado cuando el personaje vuela, por lo que puede desplazarse con total libertad en 3D, ignorando los efectos de la gravedad.
Además de estos modos, el CharacterMovementComponent
soporta varios tipos de movimiento adicionales que se pueden utilizar sin cambiar de modo. Por ejemplo, en el modo Walking, el personaje puede agacharse y caminar agachado o puede saltar.
Todo esto convierte al actor Character
y a CharacterMovementComponent
en herramientas muy potentes, que nos permiten comenzar a prototipar y probar cualquier idea rápidamente, sin tener que consumir tiempo y recursos en implementar un controlador propio.
Añadir nuevos movimientos
Sin embargo, en muchas ocasiones necesitamos un tipo de movimiento no incluido por defecto en CharacterMovementComponent
. Esto ocurre, por ejemplo, si queremos que nuestro personaje pueda escalar, esquivar, usar escaleras de mano o hacer algún tipo de parkour.
El CharacterMovementComponent
ofrece diversas formas de añadir un nuevo tipo del movimiento, siendo mejor una u otra según el caso concreto:
Launch. El método
CharcterMovementComponent::Launch()
permite lanzar al personaje con la dirección y velocidad indicada. Suele ser muy útil para implementar ciertos movimientos sencillos, como la esquiva (dash).Root Motion Source. Utilizando root motion, el movimiento del personaje en la escena viene determinado por la animación que se reproduce en la malla esqueletal. Sin embargo, este mecanismo ha sido modificado en el
CharacterMovementComponent
para que la información del movimiento también pueda ser generada mediante código. Este código se tiene que implementar en un Root Motion Source y este se activa en elCharacterMovementComponent
cuando se quiere iniciar ese tipo de movimiento.Este sistema es muy versátil y permite crear todo tipo de movimientos. Es especialmente útil para movimientos que solo deben ejecutarse durante un breve periodo de tiempo, para que luego el personaje vuelva al comportamiento normal determinado por el modo de movimiento activo.
Custom Movement Mode. Cuando el personaje puede permanecer durante bastante tiempo moviéndose de cierta forma, puede ser más interesante tener un modo de movimiento propio para ese tipo de movimiento.
El modo Custom es uno de los modos de movimiento soportados por el
CharacterMovementComponent
, junto a Walking, Falling y otros modos mencionados anteriormente. La diferencia es que en este modo el personaje no se mueve, pues es responsabilidad de los programadores implementar el código correspondiente según cómo desean que se mueva el personaje.
En el vídeo que acompaña a este tutorial se muestra como hacer uso del Custom Movement Mode en C++.
Custom Movement Mode
El modo Custom permite a los desarrolladores implementar varios modos nuevos de movimiento. Para ello, en el momento de activar el modo Custom, se debe especificar un identificador del submodo deseado. Esto se puede observar en nodo Set Movement Mode, con el que se puede cambiar el modo de movimiento activo de un componente CharacterMovementComponent
:
Nodo Set Movement Mode en Blueprint |
El valor indicado en New Custom Mode al activar el modo Custom es entregado al código desarrollado a medida por los programadores para este modo de movimiento. De esta forma se puede saber el submodo activo y, por tanto, se pueden hacer cosas diferentes en cada caso.
Sobre el tutorial
Comentemos algunos detalles adicionales sobre el vídeo con el que empezamos este tutorial.
Proyecto inicial
El proyecto está basado en la plantilla Third Person en Blueprint. Solo se han importado previamente:
Animaciones Wall Run Left y Wall Run Right, extraidas del paquete All-IN-ONE PARKOUR.
Una clase
AWRCharacterBase
en C++ derivada deACharacter
y de la que, a su vez deriva el Blueprint del personaje de la plantillaBP_ThirdPersonCharacter
.Una malla estática de 300x300 cm. para construir las paredes sobre las que correrá el personaje.
Posibles mejoras
El modo desarrollado solo pretende ilustrar la creación de un Custom Movement Mode, por lo que está lejos de mostrar una implementación completa. Estas podrían ser algunas mejoras al código mostrado.
Tiempo de recuperación (cool down)
Como condición para activar el nuevo modo Wall Running se podría comprobar que haya pasado cierto tiempo configurable de recuperación. Esto evitaría que el jugador pueda volver a utilizar el nuevo modo de movimiento demasiado pronto.
Por ejemplo, evitaría que si mientras está en este modo el personaje cae, pueda volver a iniciar el modo Wall Running un poco más abajo en la misma pared, evitando realmente la caída.
Añadir condición para que velocidad y forward ten la misma dirección y sentido
En CanStartWallRunning()
se puede añadir la comprobación de que la velocidad del personaje Velocity
y el forward del actor tengan la misma dirección y sentido para entrar en el modo Wall Running
.
const bool bAreVelocityAndForwardNearlyEqual = FVector::DotProduct(Velocity.GetSafeNormal2D(),
GetOwner()->GetActorForwardVector()) > 0.7f;
// ...
return bIsWallRunnable && bIsFalling && bIsNearMaxSpeed && bIsNearMaxInput
&& bAreVelocityAndForwardNearlyEqual && bIsMovementParallelToWall;
Así obligamos al jugador a que tenga que mirar y mover al personaje en la dirección del muro para comenzar a correr sobre él. Si en el último momento el jugador cambia la dirección de la cámara, el personaje no entrará en el modo Wall Running y caerá.
Integración con el modo Falling
Actualmente, para entrar en modo Wall Running el personaje debe estar primero en modo Falling. Cuando el CharacterMovementMode
mueve al personaje en este modo puede ocurrir una colisión, que detectamos mediante el evento OnComponentHit
de la cápsula para comprobar si el personaje ha llegado aún muro por el que puede correr.
Sin embargo, si examinamos el código de CharacterMovementMode
veremos que no es así como se interrelacionan los diferentes modos implementados. Por lo general, están mucho más acoplados, siendo muy común añadir nuevos modos modificando directamente la clase CharacterMovementMode
.
Por ejemplo, podemos ir a PhysFalling()
—donde se implementa el modo Falling— y buscar dónde se mueve al personaje:
// ...
FHitResult Hit(1.f);
SafeMoveUpdatedComponent( Adjusted, PawnRotation, true, Hit);
// ...
float LastMoveTimeSlice = timeTick;
float subTimeTickRemaining = timeTick * (1.f - Hit.Time);
if ( IsSwimming() ) //just entered water
{
remainingTime += subTimeTickRemaining;
StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations);
return;
}
//...
La función encargada de mover el personaje es SafeMoveUpdatedComponent()
, que devuelve una estructura FHitResult
con información en caso de colisión. Por tanto, en lugar de usar el evento OnComponentHit
, se podría comprobar directamente la colisión a la salida de SafeMoveUpdatedComponent()
y llamar a nuestro CanContinueWallRunning()
para comprobar si debemos entrar en el modo Wall Running.
En ese caso, si se activa el modo Wall Running, se puede llamar después a StartNewPhysics()
indicando el tiempo restante del DeltaTime
—es decir, usando el resultado DeltaTime - Hit.Time
como nuevo DeltaTime
—. De esta forma, se llamaría a nuestro nuevo PhysWallRunning()
inmediatamente, para que el personaje comience a moverse en el nuevo modo de movimiento sin esperar al siguiente frame.
Se puede ver un ejemplo de esto en el código anterior cuando se comprueba IsSwimming()
y, en caso afirmativo, se llama a StartSwimming()
. De esta forma, el personaje pueden comenzar a nada inmediatamente.
Animaciones
El resultado podría mejorarse si dispusiéramos de alguna animación de transición entre la que se usa en bucle mientras se está cayendo y las animaciones de Wall Run. También convendría ajustar el Control Rig del personaje para que los pies se posicionen correctamente tanto al correr en el suelo como a lo largo de una pared.
Recursos adicionales
Estos son algunos recursos que pueden resultar útiles para seguir profundizando en el tema*