Tutorial: ¿Cómo hacer un juego side-scroller 3D con UE4 ?

Introducción

En el tutorial pasado hicimos una introducción general al Unreal Engine 4. Vimos varios aspectos importantes como la jerarquía de clases que sigue el framework, el visual scripting, la configuración de las animaciones para el personaje, definimos temporalmente una cámara fija a nuestro juego, entre otras cosas. Si no lo has visto, debieras darle un vistazo antes de seguir con este.

Hoy vamos a crear la base del estilo de nuestro juego: un side-scroller 3D. Actualmente estoy trabajando con mi equipo (www.spissa.com) en un Runner automático, side-scroller 2D para iOS y Android. Puedes darte una vuelta por nuestro sitio en Facebook para que estés al tanto de la salida. Si te gusta este estilo de juego, te aseguro que te encantara el nuestro 😉

Como te decía, en este tutorial vamos a configurar la cámara del juego para lograr una vista side-scroller. Vamos a agregar unas monedas al escenario y usar un mecanismo simple de colisiones para que el personaje las pueda recolectar. Vamos a “enseñarle“ a nuestro personaje a correr y saltar :). Vamos a ver varios macros de variables y funciones de clase para la integración entre el código C++ y el Editor . . . y muchas cosas más. Listo ?! .. pues “manos a la obra“ !!

Modificando el nivel desde el editor

Vamos a comenzar modificando un poco el nivel actual desde el editor para que se acople más al estilo de juego que queremos implementar. Como ya decía, vamos a trabajar en un juego 3D side-scroller. La base de este estilo de juego es tener la cámara paralela al personaje protagónico siguiéndolo contantemente en la misma posición. El personaje, por su parte, se mueve solamente en dos direcciones: hacia arriba/abajo cuando salta y hacia izquierda/derecha cuando se desplaza.

Abre el editor con el proyecto UE4Demo que dejamos del tutorial pasado y comienza eliminando los objetos visibles que no vamos a usar (las sillas, la mesa, la estatua). Solo quédate con un objeto floor (piso). Después, modifica el objeto floor usando las herramienta de transformación y escalado que tienes en la esquina superior derecha del viewport. Después crea copias del objeto, modifícalas indistintamente y repártelas por el nivel. Asegúrate de dejar el Actor Play Start sobre una de las plataformas, para evitar que el personaje cuando inicie el juego se caiga al abismo. En mi caso quedó así ( tu puedes usar mucho más tu imaginación para lograr algo mejor :) ):

Nivel modificado en el Editor para que se acople más al estilo de nuestro juego

Nivel modificado en el Editor para que se acople más al estilo de nuestro juego

 

Como notarás, al lanzar el juego se muestra un cartel rojo que dice: LIGHTING NEEDS TO BE REBUILT. Esto pasa porque modificamos la geometría en el nivel y el motor necesita recompilar las luces para que la iluminación se adapte a la nueva geometría. Puedes recompilar las luces desde el Toolbar/Build/Build Lighting Only. Vuelve a correr, verás que todo volvió a la normalidad.

Bien, ya tenemos un nivel, muy básico, pero suficiente para implementar y probar todo lo que haremos hoy.

Configurando la cámara para un juego side-scroller.

En este punto es importante aclarar que el UE4 ya trae por defecto una plantilla para comenzar un juego de este estilo. De hecho usaremos prácticamente lo mismo que usa esta plantilla, pero la idea es hacerlo desde cero en este tutorial para entender bien el porqué de las cosas.

Lo primero será cambiar la cámara del juego. En el tutorial pasado configuramos una cámara fija bastante simple, pero que nos sirvió para introducirnos tanto en el visual scripting con el Blueprint Editor, como en la programación en C++. Ya no usaremos más esta cámara. Elimina la actual implementación que tengas de la cámara, si te quedaste con la solución por código, comenta el código dentro del Begin Play y si te quedaste con la solución en el Blueprint, elimina todos los nodos o elimina la conexión que sale del Nodo BeginPlay con esto último se rompe el algoritmo porque no continua la ejecución al lanzarse el BeginPlay pero se mantienen el resto de las conexiones y los nodos en el Editor, por si los quieres para una referencia.

Vamos ahora a configurar un nuevo estilo de cámara. Esta vez lo haremos desde C++ en nuestra clase Character. Siempre ten en cuenta que esto mismo lo puedes hacer también desde el Editor, recuerda que tenemos una clase Blueprint que hereda de nuestra clase HeroCharacter.h. Una buena práctica sería que te aventuraras una vez que veas lo que haremos aquí, ha hacerlo por tu parte desde el Editor modificando el HeroCharacterBlueprint.

Abre la clase HeroCharacter.h y y después del macro GENERATED_UCLASS_BODY() agrega las siguientes declaraciones:


/** Brazo para apoyar fijar la cámara al Character al estilo side-scroller */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
TSubobjectPtr<USpringArmComponent> SpringArm;
    
/** Cámara del juego, es adjuntada al socket del brazo para lograr el estilo de cámara de un side-scroller */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
TSubobjectPtr<UCameraComponent> SideViewCamera;

Lo que hicimos fue agregar dos variables de clase a nuestro HeroCharacter, pero lo que seguro más te llama la atención es el macro UPROPERTY que incluimos en cada variable. Este macro nos sirve para definir si queremos reflejar o no estos atributos en el Editor y que opciones tendremos. Como vimos, una de las cosas geniales que tiene Unreal Engine 4 es la perfecta integración entre el código y el editor y este macro es uno de los que nos ayuda a esto. A este macro le podemos pasar parámetros:

VisibleAnywhere: Indica que esta propiedad será visible en el panel de propiedades en el Editor.

BlueprintReadOnly: Esta propiedad podrá ser leída desde un VisualScript en el Blueprint, pero no modificada.

Category: Permite especificar un nombre de categoría bajo la que se mostrará esta propiedad en el Editor

Puedes ver una referencia detallada de todos los parámetros que podemos indicar para UPROPERTY en la clase Runtime/CoreUObject/Public/UObject/ObjectBase.h dentro del namespace UP. Para llegar rápido puedes dar clic sobre cualquiera de las categorías con la tecla cmd presionada.

Después que implementemos el constructor donde inicialicemos estas variables, vamos a ver en el editor el resultado que tiene definirlas con este macro.

Estas dos variables de clase que creamos para HeroCharacter son de un nuevo tipo de datos que no hemos usado antes. La primera variable que creamos la nombramos SpringArm y es de tipo USpringArmComponent, pero fíjate que usamos para definirle el tipo, la clase parametrisada TSubobjectPtr. Con TSubobjectPtr podemos usar el método CreateDefaultSubobject del objeto PCIP que recibimos en el constructor para crear una instancia de cualquier tipo de dato. Lo haremos en un segundo.
USpringArmComponent nos permite fijar un componente a su padre a una distancia fija. Usaremos este componente para fijar la cámara del juego a una distancia fija del personaje.

Además, creamos la variable de clase SideViewCamera de tipo UCameraComponent. El nombre de la clase es bastante descriptivo, esta será la cámara de nuestro juego :)

Hecho esto vamos a inicializar estas variables en el constructor de la clase. Abre el archivo HeroCharacter.cpp y agrega antes de terminar la implementación del constructor el siguiente bloque:


//Inicializando la instancia del USpringArmComponent
SpringArm = PCIP.CreateDefaultSubobject<USpringArmComponent>(this, TEXT("CameraBoom"));
    
//Agregando el springArm al RootComponent del Character (la capsula de colisión)
SpringArm->AttachTo(RootComponent);
    
//bAbsoluteRotation nos permite definir si este apoyo para la cámara rotará junto con el player.
//En este caso no queremos que rote junto al character
SpringArm->bAbsoluteRotation = true;
    
//La distancia entre el brazo y su objetivo. Este valor es el que define la distancia de la cámara.
//Prueba con distintos valores para que veas el resultado.
SpringArm->TargetArmLength = 500.f;
    
//Offset que tendrá el Socket.
//Un Socket es un punto de anclaje para otros componentes.
//Por ejemplo, para el caso de un personaje podemos definir que tenga un socket en la zona de la mano
//de esta forma le podemos agregar otro componente (como un arma, por ejemplo) en la mano
//En nuestro SpringArm, en este socket es donde se agregará la cámara
SpringArm->SocketOffset = FVector(0.f,0.f,75.f);
    
//La rotación relativa que tendrá este brazo con respecto al padre.
//En este caso queremos que este rotada en el eje Y 180 grados para que quede paralela al character a su mismo nivel.
//De esta forma logramos el clásico estilo de cámara en los side-scrollers
SpringArm->RelativeRotation = FRotator(0.f,180.f,0.f);
    
// Creando la intancia del tipo UCameraComponent
SideViewCamera = PCIP.CreateDefaultSubobject<UCameraComponent>(this, TEXT("SideViewCamera"));
    
//El método AttachTo nos permite agregar un objeto a otro objeto en un socket determinado. Recibe dos parámetros.
//el primero, el objeto al que vamos a anclarnos, en este  caso el springArm y el nombre del socket donde lo vamos a anclar
//SocketName de USpringArmComponent retorna el nombre del Socket de este componente.
SideViewCamera->AttachTo(SpringArm, USpringArmComponent::SocketName);

Préstale atención a los comentarios de cada línea para que puedas entender cada una lo que hace. En general creamos y configuramos el objeto USpringArmComponent y lo agregamos al Character. Después, creamos y configuramos el objeto UCameraComponent (la cámara del juego) y la agregamos al USpringArmComponent para fijarla a una distancia del Character. Notar que las instancias de los objetos las creamos a partir del objeto que recibimos en el constructor, con PCIP.CreateDefaultSubobject.

Listo, compila y ejecuta el juego, ahora tendrás la vista del juego al estilo side-scroller :). Puedes probar moverte por el nivel que creaste para que veas como la cámara te sigue en todo momento a la misma distancia.

Nueva vista del juego con la cámara configurada al estilo de un side-scroller

Nueva vista del juego con la cámara configurada al estilo de un side-scroller

 

No cierres el editor, vamos a ver ahora a HeroCharacterBlueprint. En el tutorial pasado este blueprint lo creamos dentro de la carpeta Character. Ábrela y activa el modo Components (esquina superior derecha). Fíjate que ahora en el panel de Componentes del Character tienes un nuevo objeto SpringArm y este tiene como hijo a SideViewCamera. Puedes revisar para que veas que tiene las propiedades que definimos desde programación. Desde aquí también puedes variar todas las propiedades por defecto. Juega un poco con ellas para que veas todo lo que puedes lograr.

Para que veas en la practica el efecto de los atributos que le pasamos al UPROPERTY: Regresa al código y elimina el atributo VisibleAnywhere que le pusimos al macro UPROPERTY y compila de nuevo. Cuando abras el HeroCharacterBlueprint en el editor a pesar de poder ver los componentes, al seleccionarlo no se muestran las propiedades en el panel de detalles.

Modificando los controles del juego para un side-scroller

Ya nuestro juego tiene el estilo de cámara que queremos, pero te habrás dado cuenta de que en este estilo de juego no sirve mucho el control actual, ya que el personaje puede moverse tanto en el eje X como en el Y, cosa que es muy poco común en los side-scroller. Por tanto, vamos a modificar un poco los controles para ajustarlos más a este estilo de juego. Además agregaremos dos nuevos controles y acciones nuevas para nuestro personaje: correr y saltar.

Supongo que ya tengas ideas de como hacer esto si seguiste el tutorial anterior. Entra desde el Editor en Edit/ProjectSettings/Input. Deja solamente MoveRigth. Ahora vamos a agregar otro tipo de input, ActionBinding. Estas entradas, a diferencia de los AxisBinding, son usadas para ejecutar acciones determinadas, saltar por ejemplo o abrir una puerta. Crea una nueva entrada de este tipo, dale de nombre Jump y selecciona la barra espaciadora. Además si te fijas puedes definirle que para que se ejecute tenga que estar presionada otra tecla simultáneamente. En este caso no marques ninguna, solamente con tocar la barra espaciadora el personaje saltará.

Nueva configuración de los controles del juego. Ahora con un control para saltar

Nueva configuración de los controles del juego. Ahora con un control para saltar

 

Ahora vamos al código. Primero, ya no necesitamos el método MoveForward de HeroCharacter, elimina la declaración de este método en el .h y la implementación del .cpp. Además, dentro del método SetupPlayerInputComponent elimina el BindAxis del MoveForward. Por último, tenemos que modificar el código del método MoveRight para cambiar su implementación, ya que actualmente lo que hace el personaje cuando tocamos las teclas A o D es rotar su posición. En este caso lo que queremos es que se mueve hacia delante y atrás respectivamente. Modifica el método MoveForward para que quede así:


/**
 *  Se llama cuando se detecta la entrada de tipo MoveForward (Cuando el usuario toca las teclas A o D).
 *  @param Value Value es igual a 1 cuando se detecta D y -1 cuando se detecta A
 */
void AHeroCharacter::MoveRight(float Value)
{
	if ( (Controller != NULL) && (Value != 0.0f) )
	{
        // Agrega un vector de movimiento hacia la derecha o la izquierda segun el valor de Value
        AddMovementInput(FVector(0.f,-1.f,0.f), Value);
	}
}

Como puedes notar, el método quedó mucho mas simple que la anterior implementación y ya debes entender sin problema que hace. Simplemente aplicamos un vector de movimiento que afecta solo un eje con el valor de Value. Por lo que cuando el usuario toque la tecla D el personaje se moverá hacia delante (hacia la derecha) y cuando toque la tecla A se moverá hacia la izquierda.

Ahora tenemos que implementar el método que se llamará cuando el usuario toque la barra espaciadora para hacer saltar a nuestro héroe. Muy simple, agrega la siguiente línea al método SetupPlayerInputComponent:

InputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);

Fíjate en una cosa, el método que se llamará es ACharacter::Jump. O sea, Jump es un método implementado en la clase base de nuestra clase, no en nuestra propia clase HeroCharacter. Este método ya tiene implementado todo lo necesario para hacer saltar al personaje. No es poca la ayuda que nos da el Framework ehh ?? :)

Listo, compila y ejecuta el juego y toca las teclas A y D para que veas como el personaje se mueve correctamente por el escenario, y aunque toques las teclas W o S no pasará nada. Ahora toca la barra espaciadora, eehhh !!! nuestro personaje ya sabe saltar también :) . . . pero algo está mal aquí. Sí, el personaje salta, pero a pesar de estar saltando sigue reproduciendo la animación de caminar. Pues claro, recuerda que solo le tenemos dos animaciones, y no hemos hecho nada para el caso en el que está saltando. Vamos a solucionar esto.

Configurando las animaciones para el salto del personaje

Del tutorial pasado tenemos los recursos que supuestamente nos entregó nuestro equipo de diseño para el personaje principal. Entre ellos tenemos 3 FBX de animaciones Jump_End.FBX, Jump_Loop.FBX, Jump_Start.FBX y Run.FBX. Impórtalas dentro de la carpeta Animations. Puedes abrirlas en el Persona Editor para que les des un vistazo.

Como notarás tenemos tres fbx distintos, básicamente tres animaciones separadas para el salto. Y te preguntarás: ¿Esto por qué?… te cuento. Cuando comenzamos a desarrollar nuestro actual proyecto (puedes darle un vistazo aquí :)) en este juego el personaje salta mucho y a distintas alturas, lo mismo hace un salto simple chiquito, que un salto desde una gran altura y está más tiempo en el aire. El problema que teníamos con esto es que cuando el salto era de gran altura, donde el personaje estaba mucho tiempo en el aire, cuando iniciaba el salto reproducíamos la animación de saltando, que la teníamos en una sola parte, y cuando la animación terminaba su tiempo, como el personaje aun no había caído en el suelo, se quedaba con el ultimo frame de la animación y totalmente tieso, hasta que caía en el suelto y entonces seguía animándose normalmente. Por este motivo es que en la mayoría de los casos es necesario separar las animaciones del salto. Tendremos el pedazo de la animación cuando inicia el salto, después una animación que reproduciremos en repetición (en loop) mientras el personaje este en el aire, y por ultimo el trozo de animación cuando llega al suelo. De esta forma tendremos todo el ciclo del salto animándose perfectamente independientemente de la altura.

Vamos a configurar esto en el Blueprint de las animaciones del personaje. Como ya sabes, para el sistema de animación se usa una maquina de estado que nos permite separar perfectamente todos los estados en los que estará el personaje y configurar la animación para cada estado. Actualmente la maquina de estado de nuestro personaje es sumamente simple. Solamente tiene un estado Idle/Walk. Vamos a modificarla para agregar los estados del salto.

Dentro del HeroAnimBlueprint/AnimGraph, entra en la maquina de estado. Arrastra desde el nodo Idle/Walk para crear un nuevo nodo, ponle el nombre de JumpStart, arrastra desde JumpStart para crear otro nodo y ponle JumpLoop. Repite el procedimiento desde JumpLoop para crear un tercer estado JumpEnd y por ultimo conecta el estado JumpEnd con Idle/Walk para cerrar el ciclo. Te quedará así:

Nueva maquina de estado del Character, con los estados del salto

Nueva maquina de estado del Character, con los estados del salto

 

Lo que acabamos de hacer aquí es definirle 3 nuevos estados que va a tener el personaje y el orden en los que podrá alcanzarlos. O sea, el personaje podrá estar en reposo/corriendo y de ahí puede pasar al estado de JumpStart (inicio del salto). De este estado JumpStart puede pasar a JumpLoop (ciclo del salto). De JumpLoop puede pasar a JumpEnd y por ultimo de JumpEnd regresa nuevamente a Indle/Walk. Fíjate que la conexión entre los estados es una flecha que marca la dirección, o sea, cual es el estado origen y cual el destino. Es bastante lógico, si lo imaginas un poco o te paras un segundito y saltas :) reproducirás estos 4 estados y la transición por cada uno de ellos.

Cada estado tiene sobre la flecha de la transición un iconito arriba que representa la condición que define cuando es que se pasará de un estado a otro. Esta condición las tenemos que programar nosotros (mediante visual scripting con el Blueprint Editor). El primer caso sería cuando el personaje está en Idle/Walk y comienza un salto. La condición que usaremos para pasar a este estado será muy simple, crearemos una variable BOOL que nos permita saber si el personaje está en el aire o no y si está en el aire va a pasar al estado de JumpStart.

Da doble clic en el icono de la transición y entrarás en el modo edición de este elemento, Por defecto tiene un nodo Result con la descripción Can Enter Transition, este nodo espera un parámetro bool (true/false) que le dirá si la maquina activa este estado o no, o sea, si se efectúa la transición. Crea y agrega una nueva variable, como mismo hicimos en el tutorial pasado, pero de tipo bool. Ponle de nombre IsInAir o cualquier otro nombre identificativo que se te ocurra. Agrégala al blueprint en modo GET. Ahora conecta el Puerto de salida de esta variable al Puerto de entrada del nodo Result.

Lo que acabamos de programar aquí mediante visualscripting es: Si la variable IsInAir tiene valor true, entonces ejecuta esta transición. Lo que quiere decir, que el personaje pasara para el estado de JumpStart.

VisualScript de la transición entre Idle/Walk y JumpStart

VisualScript de la transición entre Idle/Walk y JumpStart

 

Guarda y sale del modo de edición de la transición de Idle/Walk a JumpStart y entra en el modo de edición del nodo JumpStart para definir que animación se va a reproducir aquí. Arrastra hacia al blueprint la animación JumpStart que tienes en los recursos y conéctala al nodo final. Para el caso de las animaciones JumpStart y JumpEnd, queremos que se reproduzcan solo una vez, o sea que no se reproduzcan en ciclo como hemos hecho hasta ahora con las animaciones de reposo y caminando, ya que la idea es reproducir JumpStart, al terminar, reproducir JumpLoop (esta sí en ciclo) hasta que el personaje llegue al suelo y por ultimo reproducir JumpEnd, una sola vez. Selecciona el nodo de la animación JumpStart y en el panel de propiedades desmarca la opción Loop.

VisualScript del nodo JumpStart

VisualScript del nodo JumpStart

 

Ahora, ¿cual sería la condición para pasar de JumpStart a JumpLoop?. Pues para este caso usaremos un nuevo nodo. La idea es determinar cuando se esté a punto de acabar la animación de JumpStart para comenzar a reproducir el loop. Da doble clic en el icono de transición de JumpStart a JumpLoop, como ya vimos, por defecto tenemos el nodo Result al que hay que conectar la salida del algoritmo que preparemos aquí y que determina cuando pasa el personaje a este estado. Agrega un nuevo nodo de tipo Time Remaining (Ratio) para Jump_Start Asset. Este nodo nos permite tener en todo momento el tiempo que le va quedando a la animación para que termine. Agrega ahora otro nodo de tipo float < float. Este nodo es un método que nos permite saber si un parámetro A es menor que otro B. Conecta el puerto de salida del nodo TimeRemaining al puerto de arriba de entrada de la comparación. Para el segundo parámetro de la función menor que, no vamos a conectar ningún nuevo nodo, sino que vamos a definir un valor a mano. Como queremos que se comiese a reproducir Jumploop ya cuando esté a punto de terminar el JumpStart, pon en el campo del segundo parámetro de la función menor que: 0.1. Por ultimo conecta la salida de esta funcione al nodo Result. Te quedaría de la siguiente forma: [caption id="" align="alignnone" width="630"]VisualScript de la transición de JumpStart a JumpLoop VisualScript de la transición de JumpStart a JumpLoop[/caption]

 

Básicamente lo que programamos aquí fue: Si el tiempo que le queda a la animación por terminar es menor que 0.1 pasa al siguiente estado.

Guarda estos cambios, sale del modo de edición de la transición y entra en el modo edición del estado JumpLoop, agrega la animación JumpLoop y conéctala al Result. A diferencia de JumpStart, JumpLoop si queremos que se reproduzca en ciclo ya que es esta la animación que se estará reproduciendo mientras el personaje esté en el aire. Para esto, rectifica que en el panel de propiedades del nodo que representa la animación, tenga marcado el atributo Loop.

VisualScript del nodo JumpLoop

VisualScript del nodo JumpLoop

 

Vamos ahora a configurar la transición de JumpLoop a JumpEnd. JumpEnd es la animación cuando termina el salto, para pasar al estado JumpEnd simplemente sería cuando el personaje deje de estar en el aire. Para esto ya tenemos la variable bool isInAir así que vamos a usarla. Da doble clic en el icono que representa la transición entre JumpLoop y JumpEnd, agrega la variable isInAir como GET… un segundo !! isInAir nos dice si el personaje está en el aire, pero, cómo saber si el personaje NO está en el aire ?. Sería la negación de isInAir verdad ?. Entonces, agrega al blueprint un nodo de tipo NOT. Este nodo retorna la negación de su entrada. Conecta isInAir a NOT y NOT a Result. Listo ¡!, en cuanto el personaje deje de estar en el aire pasará a este estado.

VisualScript de la transición de JumpLoop a JumpEnd

VisualScript de la transición de JumpLoop a JumpEnd

 

Sale del modo de edición de esta transición, entra en el modo de edición del estado JumpEnd. Arrastra y conecta al Final Pose la animación JumpEnd y desmárcale la opción de loop.

VisualScript del estado JumpEnd

VisualScript del estado JumpEnd

 

Por último, tenemos que definir las condiciones para pasar de JumpEnd de nuevo a Idle/Walk. JumpEnd es la animación que termina el salto, ya el personaje está en el suelo pero recuperándose de la caída. Por lo que simplemente para pasar a Idle/Walk es esperar a que la animación JumpEnd esté a punto de terminar, como mismo hicimos de JumpStart a JumpLoop. Esto lo debes poder hacer por tu cuenta, así que inténtalo … 😉 te tiene que quedar así:

VisualScript de la transición de JumpEnd a Idle/Walk

VisualScript de la transición de JumpEnd a Idle/Walk

 

Muy bien, ya tenemos la maquina de estado del personaje lista, pero nos falta una cosa. En estos estados estamos usando una nueva variable IsInAir solo en modo GET, pero recuerda, como mismo hicimos para Speed, en algún punto esta variable tiene que tomar valor.

Cierra el AnimGraph y abre el EventGraph, agrega un nodo GetMovementComponent conecta el puerto de salida del TryGetPawnOwner que tenemos desde el tutorial pasado al puerto de entrada del GetMovementComponent, agrega otro nuevo nodo Is Falling, conecta el puerto de salida de GetMovementComponent al de entrada del IsFalling. Agrega la variable IsInAir en modo SET y conecta la salida de IsFalling a la entrada de IsInAir. Por último, conecta el puerto blanco de salida de SET Speed al de entrada de SET IsInAir para la continuidad del algoritmo.

EventGraph del HeroAnimBlueprint modificado para darle valor a la variable IsInAir cuando el personaje esté en el aire

EventGraph del HeroAnimBlueprint modificado para darle valor a la variable IsInAir cuando el personaje esté en el aire

 

Si has llegado hasta aquí desde el tutorial anterior no debes tener problema en entender que acabamos de hacer aquí. Obtenemos el MovementComponent del Character, este tiene un método IsFalling que retorna true/false si el usuario está en el aire o no. Este método es el que usamos para settear el valor de la variable IsInAir.

Listo, ya tenemos la nueva maquina de estado para nuestro personaje. Compila el AnimationBlueprint y corre el juego. Toca la barra espaciadora . . . ya nuestro héroe sabe saltar también :)

Personaje en el medio de un salto animándose correctamente

Personaje en el medio de un salto animándose correctamente

 

Implementando mecanismo para que el personaje corra !!

Ya tenemos a nuestro héroe que sabe caminar, reposar y saltar, pero algo que nos va faltando y es clásico en un side-scroller, la posibilidad de correr, para obtener más impulso, saltos más largos etc. ¿Quién no recuerda en uno de los últimos niveles del primer Super Mario el hueco grandísimo que solo podíamos saltar si lo hacíamos con mucho impulso ehh? :) . . . Pues bien, vamos a darle esta misma habilidad a nuestro héroe. Vamos a implementar que cuando se esté caminando con la tecla Shift presionada el personaje corra.

Primero, abre el editor y agrega en los controles una nueva entrada de tipo ActionBinding ponle el nombre de Run y selecciona al tecla LeftShift. Cierra el editor y abre la clase HeroCharacter.cpp agrega dentro del método SetupPlayerInputComponent las dos siguientes líneas:


//Le dice al motor que cuando detecte la entrada de tipo Run (Shift) estando presionada la tecla, llame al metodo ToggleRunState.
    InputComponent->BindAction("Run", IE_Pressed, this, &AHeroCharacter::ToggleRunState);
    
//Le dice al motor que cuando detecte la entrada de tipo Run (Shift) al soltar la tecla, llame al metodo ToggleRunState.
InputComponent->BindAction("Run", IE_Released, this, &AHeroCharacter::ToggleRunState);

Fíjate en un detalle, estamos llamando al BindAction para la entrada Run dos veces y pasándole el mismo método ToggleRunState (que vamos a implementar ahora) pero la diferencia entre uno y otro es que el segundo parámetro especifica exactamente cuando es que se va a llamar al método. IE_Pressed cuando se presione la tecla Shift y IE_Release cuando se suelte. Lo que queremos hacer es que si se está tocando el shift el personaje corre pero si se suelta deja de correr, algo parecido a la misma combinación que teníamos que hacer en el Super Mario para el super salto !! :)

Bien, ahora vamos a implementar el método ToggleRunState. Agrega en el .h de HeroCharacyer.cpp la declaración del método:


/** 
* Se llama cuando el motor detecta la entrada Run 
* Intercambia el estado de correr del personaje
*/
void ToggleRunState();

Pasa a la .cpp y agrega la implementación:


/**
 * Se llama cuando el motor detecta la entrada Run
 * Intercambia el estado de correr del personaje
 */
void AHeroCharacter::ToggleRunState()
{
//Si el atributo MaxWalkSpeed del CharacterMovement está en 400.f lo aumentamos a 900.f para que el personaje se mueva mas rápido
//De lo contrario lo volvemos a poner en 400.f para que regrese a su velocidad de caminar.
    if(CharacterMovement->MaxWalkSpeed == 400.0f)
        CharacterMovement->MaxWalkSpeed = 900.0f;
    else
        CharacterMovement->MaxWalkSpeed = 400.0f;
}

Muy simple, por defecto la velocidad de desplazamiento del personaje es 400 cuando este método se llama por primera vez (cuando se presiona shift) el MaxWalkSpeed está en 400 y se pasa a 900, lo que hará que el personaje de desplace más rápido, y cuando se suelte el Shift se va a llamar de nuevo y se volverá a poner la velocidad en 400, disminuyendo el desplazamiento.

Una buena tarea sería que intentes implementar este mecanismo de correr en el Blueprint del HeroCharacter. Recuerda eliminar el código C++ si vas a usar el Blueprint. Te tendría que quedar así:

Variante en el Blueprint de la funcionalidad del correr

Variante en el Blueprint de la funcionalidad del correr

 

Yo en lo personal prefiero siempre mantener toda la lógica del personaje desde C++, pero ese ejercicio te puede servir para tomar más soltura en el Blueprint Editor que tanto llama la atención :).

Otra buena tarea sería que expongas los valores máximo y mínimo del MaxWalkSpeed en el Editor para que se puedan modificar fácilmente sin necesidad de llegar al código. Ya sabes que tendrás que usar el macro UPROPERTY, pero este si no te diré como tiene que quedarte, inténtalo por tu cuenta 😉

Compila y ejecuta el juego, a medida que estas caminando deja presionada la tecla shift, verás que el personaje se desplaza mucho más rápido, pero muy feo ya que solamente se afecta su desplazamiento pero no se afecta la animación.

Agregando la animación de correr al personaje

Vamos a agregar otra animación a nuestro personaje, para que cuando esté corriendo se anime correctamente. Para esto usaremos el mismo estado Idle/Walk que ya tenemos, pero más aun, dentro de este usaremos el mismo nodo con el que hacemos blend entre las animaciones de Idle y Walk. Una súper potencialidad de este nodo es que podemos agregarle más animaciones, no solo dos. La idea será modificarlo para configurarle tres puntos de control y no dos como tenemos ahora. Uno al inicio del grafico con la animación Idle, otro en el medio con la animación Walk y otro al final con la animación Run.

Importa Run.FBX de los recursos para el proyecto. Abre el IdleWalkBlendSpace1D que creamos en el tutorial pasado. Cámbiale la propiedad X Axis Range a 900 (que es el valor que toma el personaje al correr) y da clic en el botón Apply Parameter Changes. Ahora agrega al inicio del gráfico la animación de Idle, en el medio la animación de Walk y al final la animación de Run. Asegúrate de tener marcada la opción de Enable Preview BlendSpace y mueve el cursor sobre el gráfico para que veas como se van haciendo los blend, según el valor de speed, entre reposo/caminando/corriendo. Súper genial y fácil ehh ??.

Nueva configuración del IdleWalkBlendSpace1D para Idle/Walk/Run

Nueva configuración del IdleWalkBlendSpace1D para Idle/Walk/Run

 

Guarda, ejecuta el juego y prueba correr. Ya nuestro personaje camina y corre perfectamente.

Personaje corriendo con las teclas D+Shift presionadas

Personaje corriendo con las teclas D+Shift presionadas

 

Agregando monedas para recolectar en el nivel

Ya tenemos nuestro personaje moviéndose por el escenario caminando, corriendo y saltando al estilo side-scroller. Pero ir caminando por ahí sin nada que hacer es algo aburrido, no ? Vamos a tratar de mejorar esto un poco :) . . . vamos a implementar la posibilidad de recolectar monedas por todo el escenario. En futuros tutoriales veremos que gana nuestro héroe con estas monedas.

Primero, necesitamos el modelo de la moneda. En el MarketPlace de seguro que podrás encontrarte muchísimos StaticMesh que puedas usar como moneda que nuestro personaje pueda recolectar. Te recomiendo que te tomes un tiempo para recorrer el MarketPlace, de seguro te encontrarás cosas que te encantarán… y muchas FREE ¡! 😀

De cualquier forma, puedes bajar de aquí el FBX de una moneda MUY SIMPLE Y FEA :S porque ya te he comentado que se me da muy mal el modelado 3D, pero perfecta para nuestro tutorial. Importa este FBX al proyecto (yo creé una carpeta Coin). En la ventana de importar verás que Unreal detecta que este es un StatickMesh, expande la opción avanzada y marca las opciones material y texture. Esto para que importes también el material que tiene aplicado el modelo, que igual, es extremadamente simple pero le da el look de moneda, por su color amarillo.

Bien, ya con el recurso importado vamos a crear la clase C++ que encapsulará toda la lógica de la moneda. Crea una nueva clase desde el editor de nombre Coin. En este caso que herede de Actor. Cuando el Editor te pregunte si quieres abrir la clase en el IDE dile que sí. Modifica el .h de la clase para que te quede de la siguiente forma:


/** Representa una moneda. El personaje puede capturarla colisionando con ella */
UCLASS()
class ACoin : public AActor
{
	GENERATED_UCLASS_BODY()
    
    /** 
     * USphereComponent es un componente en forma de esfera generalmente usado para detectar colisiones simples 
     * Este será el Root de la moneda y con él detectaremos las colisiones entre el personaje y la moneda
     */
     UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Coin)
    TSubobjectPtr<USphereComponent> BaseCollisionComponent;
    
    /** StaticMesh de la moneda, ya lo usamos anteriormente con el Character. En él tendrémos la instancia de el StaticMesh de la moneda */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Coin)
    TSubobjectPtr<UStaticMeshComponent> CoinMesh;
    
    /** Simple variable booleana para activar o no la moneda */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Coin)
    bool bIsActive;
};

Préstale atención a los comentarios, como siempre, para los detalles. Básicamente lo que hacemos es definir el atributo BaseCollisionComponent que será el componente raíz de nuestra moneda, y el que usaremos para detectar colisiones con ella y un UStaticMeshComponent para poder definir el StaticMesh que representará a la moneda en el nivel. Por último el atributo bIsActive que lo usaremos como bandera para desactivar la moneda cuando se colisione con ella.

Pasa ahora a la .cpp y modifica el constructor para que te quede así:


ACoin::ACoin(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
    //Crea la instancia del USphereComponent
    BaseCollisionComponent = PCIP.CreateDefaultSubobject<USphereComponent>(this, TEXT("BaseSphereComponent"));
    
    //Inicializa el RootComponent de este Actor con el USphereComponent
    RootComponent = BaseCollisionComponent;
    
    //Crea la instancia del UStaticMeshComponent
    CoinMesh = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("CoinMesh"));
    
    //Agregamos el UStaticMeshComponent como hijo del root component
    CoinMesh->AttachTo(RootComponent);
    
    //Por defecto la moneda estará activa
    bIsActive = true;
}

Aquí instanciamos un USphereComponent como el RootComponent de nuestra moneda. Creamos la instancia del UStaticMeshComponent que después configuraremos desde el editor, lo agregamos al RootComponent y por ultimo inicializamos en true bIsActive. Ya que queremos que por defecto la moneda esté active.

Agregando monedas al nivel

Vamos ahora a crear el Blueprint de la moneda, como mismo hicimos con el Character en el tutorial pasado. Entra en la carpeta Coin del ContentBrowser, clic derecho/Blueprint y selecciona Coin como clase base, dale de nombre CoinBlueprint.

Como verás, está compuesta por los mismos componentes que definimos en el constructor un USphereComponent como RootComponent y un UStaticMeshComponent. Despliega el CoinMesh y selecciona el StaticMesh que importamos para la moneda.

Sección Components del CoinBlueprint. Agregándole el StaticMesh

Sección Components del CoinBlueprint. Agregándole el StaticMesh

 

Ahora, vamos a agregar algunas monedas al escenario. Busca en el ContentBrowser el CoinBlueprint y arrástralo al nivel en lugares donde quieras que salga la moneda para que el personaje las pueda alcanzar. Una cosa importante a tener en cuenta, recuerda que en este estilo de juego el personaje siempre tendrá un Y fija por lo que tienes que asegurarte de poner las monedas en la misma Y del personaje para que cuando camine en dirección de la moneda pueda colisionar con ella. Mi nivel es muy simple, quedó así:

Nivel en edición con dos monedas agregadas.

Nivel en edición con dos monedas agregadas.

 

Ya tenemos las monedas en el nivel, pero poco se puede hacer con eso. Si caminas hacia las monedas no pasa nada, además las monedas se ven muy feas estáticas ahí sin moverse. Vamos a arreglar estas cosas

Simple mecanismo de colisión para recolectar las monedas

Como pudiste ver, ahora mismo cuando el personaje le pasa por arriba a la moneda no pasa nada, esta sigue ahí como si nada pasara. Vamos a solucionar esto implementado la lógica para detectar cuando el personaje está sobre una moneda. De momento será simple nuestra implementación, lo que haremos es incrementar un valor de “Monedas recolectadas“ que tendremos en nuestro personaje y llamar al método OnCollected() que crearemos en la moneda para ponerla inactiva, eliminarla del nivel y temporalmente imprimir un log en la pantalla para debuguear este mecanismo.

Abre la clase HeroCharacter.h ya agrega las declaraciones siguientes:


    /** Cantidad de monedas recolectadas por el personaje */
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category=Coins)
    int32 CoinsCollected;

    /** Se llama constantemente en el Tick del personaje para determinar si se está colisionando con una moneda */
    void CollectCoins();
    
    /** 
     * Se ejecuta automáticamente por el Engine en cada frame del juego
     * @param DeltaSeconds la diferencia en segundos entre el frame pasado y el actual
     */
    virtual void Tick(float DeltaSeconds) OVERRIDE; 

Vamos a comentar un poco cual es la lógica que usaremos. Todos los Actors en Unreal Engine cuentan con el método Tick. Este método es llamado automáticamente por el Engine en cada frame del juego. Si estas leyendo este tutorial probablemente estés familiarizado con el desarrollo de juegos, al menos la teoría, y sabrás de sobra la importancia de este método. Básicamente, todos lo algoritmos que queramos que estén en constante ejecución por un Actor determinado tiene que ir dentro de este método Tick. El método Tick recibe como parámetro un float que es la variación en segundos entre un frame y el anterior. Ese valor es de mucha ayuda para usarlo como multiplicador a la hora de modificar la posición del personaje o la rotación del actor para que esto se ejecute dependiente del framerate que tenga el juego en ese momento y evitar saltos en caso que la ejecución del juego baje el framerate.

Entonces, lo que haremos será tener en constante ejecución el método CollectCoins y este método lo que hará será determinar si hay alguna moneda dentro del CapsuleComponent del Character, si es así, es que está arriba de una moneda.

Abre HeroCharacter.cpp y antes de terminar la implementación del constructor agrega la línea CoinsCollected = 0. Muy simple, al crearse el Character no ha recogido ninguna moneda. Ahora agrega la implementación del método CollectCoins() y Tick


/** Se llama constantemente en el Tick del personaje para determinar si se está colisionando con una moneda */
void AHeroCharacter::CollectCoins()
{
    //Arreglo de AActors para guardar temporalmente todos los Actors que se detecten que están colisionando con el personaje
    TArray<AActor*> CollectedActors;
    
    //CapsuleComponent cuenta con el método GetOverlappingActors. Este metodo nos retorna en la llamada dentro del arreglo que le pasamos por parámetro
    //todos los objetos que estan dentro de la capsula en ese momento.
    CapsuleComponent->GetOverlappingActors(CollectedActors);
    
    //Recorremos todos los objetos dentro del CapsuleComponent
    for(int32 i = 0; i < CollectedActors.Num(); i++)
    {
        
        //Como el arreglo es de AActors tenemos que catear cada elemento a ACoin antes de usarlo
        ACoin *Coin = Cast<ACoin>(CollectedActors[i]);
        
        //Nos aseguramos que la moneda está activa y que no ha sido llamado aún el método Destroy
        if(Coin != NULL && !Coin->IsPendingKill() && Coin->bIsActive)
        {
            //Incrementamos la cantidad de momendas recolectadas
            CoinsCollected++;
            
            //Por último llamamos al OnCollected de la moneda para ejecutar toda la lógica de la moneda cuando esta es tomada por el personaje
            Coin->OnCollected();
        }
    }
}

/**
 * Se ejecuta automáticamente por el Engine en cada frame del juego
 * @param DeltaSeconds la diferencia en segundos entre el frame pasado y el actual
 */
void AHeroCharacter::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    //En cada update del juego llamamos al CollectCoins para estar constantemente determinando si se está colisionando con alguna moneda
    CollectCoins();
}

Este método como dijimos es el que se llamará constantemente en el Tick del player para determinar si se está colisionando con una moneda o no. Tomate unos minutos en los comentarios de cada línea para que entiendas bien el proceso para determinar si se está colisionando con una moneda. Este es un mecanismo de colisión muy simple, pero suficiente para nuestro primer trabajo con colisiones en Unreal Engine 4 :). Una cosa importante, dentro del método CollectCoins hacemos referencia a la clase ACoin que creamos, para que esto no de error ve a la parte de arriba del .ccp y debajo de la línea #include “UE4Demo.h” agrega #include “Coin.h”

Solo nos falta un detallito. Si intentas compilar ahora tendrás un error, porque cuando se detecta una colisión se llama al método OnCollected de la clase Coin, y nosotros nunca hemos implementado ese método. Pues bien, vamos a implementarlo.

Agrega la declaración del método en el Coin.h y la implementación en el .cpp


/** A llamar cuando se detecte la colision con una moneda */
void ACoin::OnCollected()
{
    //A modo de Debug ponemos un log en la pantalla
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Coin Collected !!");
    
    //Pasamos a false el flag de bIsActive
    bIsActive = false;
    
    //Y con el método Destroy de Actor, eliminamos la moneda del escenario
    Destroy();
}

Este método es el que se llamará cuando el personaje colisiona con la moneda, y de momento es muy simple. Primero usamos el método AddOnScreenDebugMessage del objeto GEngine que nos da el Framework. Este método es muy útil porque nos permite imprimir en la pantalla mensajes de un color determinado por un tiempo determinado. Para ir revisando nuestro código en tiempo de ejecución es genial. Aquí lo usamos para cuando el personaje colisione con una moneda se imprime en pantalla el texto Coin Collected !!”. Además ponemos en false la moneda y la eliminamos del escenario con la ayuda del método Destroy() de la clase Actor.

Listo, compila y ejecuta el juego. Ahora camina hacia una moneda … Bieeen !! al pasarle por arriba a la moneda esta se elimina de la escena, se incrementa la cantidad de monedas colectadas por el personaje (esto es interno, de momento visualmente no mostramos nada al respecto) y por ultimo mostramos ese log temporal en la pantalla de Coin Collected ¡!.

“Debugueando” las colisiones en el escenario

Si le prestas atención a los detalles verás un problemita que tenemos en este mecanismo de colisión. Trata de acercarte poco a poco a la moneda, verás que la colisión se detecta antes de que el personaje esté sobre la moneda. Para encontrar el problema en estos casos el Unreal Engine nos brinda un comando fenomenal que nos permite revisar en tiempo de ejecución el componente que tiene cada actor para las colisiones. Dale Play al juego nuevamente y fíjate que el editor tiene en la esquina superior derecha un campo para escribir comandos. Escribe en ese campo: show COLLISION y toca Enter. Inmediatamente en el juego se ve sobre cada actor los componentes.

Juego corriendo con el comando show COLLISION para debuguear los componentes para colisiones de cada Actor.

Juego corriendo con el comando show COLLISION para debuguear los componentes para colisiones de cada Actor.

 

Fíjate que el componente de la moneda es mucho mayor que el modelo de la moneda y probablemente sea bueno también reducir el radio de la capsula del personaje. Termina la ejecución del juego, abre el CoinBlueprint, selecciona en el modo Componentes el ROOT, en el panel Detalles busca la sección Shape que tiene la propiedad del radio de la esfera. Cambia este valor a 15, verás como en el preview la esfera que rodea a la moneda se acopla mucho más a nuestro modelo. Guarda y prueba de nuevo, verás como mejoró y ahora tiene el personaje que pegarse mucho más a la moneda para atraparla.

Modificando el radio del componente esfera para ajustar las colisiones con la moneda

Modificando el radio del componente esfera para ajustar las colisiones con la moneda

 

Puedes hacer lo mismo para la capsula del personaje si la quieres modificar un poco. Vale aclarar también que estos valores los puedes definir desde un inicio en el constructor de la clase desde C++. Prueba hacerlo como ejercicio.

Haciendo rotar las monedas constantemente en su eje.

Para darle un poco de vida a las monedas en el escenario, vamos a hacer que estas estén rotando siempre en el eje Y. Cierra el editor, abre la clase Coin.h y agrega las siguientes declaraciones:


    /** Factor de rotacion de la moneda */
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rotation")
    FRotator RotationRate;
    
    virtual void Tick(float DeltaTime) OVERRIDE;

Simplemente agregamos la declaración del método Tick que ya conocemos y un atributo nuevo RotationRate de tipo FRotator. Este será el vector de rotación que usaremos para aplicarle a la moneda constantemente en cada Tick. En realidad no tenemos que declarar un atributo para esto, pero haciéndalo así y sobre todo gracias al macro UPROPERTY podemos dejar en configuración desde el Editor el factor de rotación que tendrá la moneda.

Pasa ahora a Coin.cpp, antes de terminar la implementación del constructor agrega estas dos línea


    //Inicializa el factor de rotacion de la moneda en cada update
    RotationRate = FRotator(0.0f, 180.0f, 0.0f);
    
    //Activa para que se llame el método Tick de este actor en cada update del juego
    PrimaryActorTick.bCanEverTick = true;

La primera es la inicialización del vector de rotación que usaremos en el Tick de la moneda y la segunda línea es muy importante. Por defecto en esta clase que hereda de Actor el método Tick NO se llama, para activar en el Engine que se llame el tick de esta clase es necesario inicializar el atributo PrimaryActorTick.bCanEverTick en true.

Bien, hecho esto agrega la implementación del método Tick


void ACoin::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    //Agrega una rotacion a la moneda en cada tick para tenerlas rotando constantemente en el escenario
    AddActorLocalRotation(this->RotationRate * DeltaTime, false);
}

Como vez, muy simple. Básicamente en cada update de este Actor lo que hacemos es agregar un factor de rotación gracias al método AddActorLocalRotation.

Listo !! compila ejecuta y prueba. Super verdad !! ya las monedas se encuentran rotando en el escenario esperando a que las atrapes :)

Implementando la lógica de la moneda mediante el Blueprint.

Como ya hemos dicho, la decisión de implementar una determinada cosa en C++ o mediante VisualScripting con el Blueprint Editor es de uno. Yo en lo personal sigo las dos siguientes premisas. Primero, implementar algo siempre en un solo lugar. O sea, no tener parte de la lógica de un Actor en C++ y otra parte en el Blueprint. Trato de tenerlo todo en un mismo lugar en la medida de lo posible. Segundo, si la lógica a implementar en determinado Actor es muy simple, como es el caso de esta moneda, pues la solución ideal es el Blueprint. Independientemente de que con el Blueprint se pueden implementar algoritmos súper largos y muy complejos, yo para estos casos prefiero usar C++.

Una buen ejercicio es que intentes implementar la lógica de la moneda, todo lo que hemos hecho aquí pero en Blueprint. Te tiene que quedar así:

VisualScript de la moneda

VisualScript de la moneda

 

Fíjate que desde el Blueprint implementamos lo que va a pasar cuando se llama el método OnCollected. Para poderlo agregar aquí tienes que agregar en la declaración del método el macro UFUNCTION(BlueprintNativeEvent) o UFUNCTION(BlueprintImplementableEvent). Te explico ambos:

UFUNCTION(BlueprintImplementableEvent) es un método que NO va a tener implementación en C++, solamente será para agregarlo al Blueprint e implementarlo desde ahí. Por lo que en el .cpp no tendrás nada de este método y en el .h solo la declaración. Es en el Blueprint donde tienes que implementar lo que hará el método.

UFUNCTION(BlueprintImplementableEvent). Este es un poco más interesante. Cuando creamos un método con este macro, en la implementación tenemos que agregar en el nombre del método _Implementation. Por ejemplo, si quieres probar con OnCollected. La definición sería de la siguiente forma:


UFUNCTION(BlueprintNativeEvent)
void OnCollected();

Y la implementación quedaría así:


void ACoin::OnCollected_Implementation()
{
    //A modo de Debug ponemos un log en la pantalla
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, "Coin Collected !!");
    
    //Pasamos a false el flag de bIsActive
    bIsActive = false;
    
    //Y con el método Destroy de Actor, eliminamos la moneda del escenario
    Destroy();
}

Lo bueno con esto es que desde C++ tenemos una implementación, pero si queremos podemos sobreescribir esta implementación desde el Blueprint … super verdad ¡!?? cosas del UE4 :). Prueba esto implementado toda la lógica de la moneda desde VisualScripting, para que vayas ganando soltura con el Blueprint Editor, una de las maravillas del Unreal Engine 4.

Conclusión

Bueno, vamos terminando por hoy con este segundo tutorial sobre el desarrollo de juegos con Unreal Engine 4. Hoy aprendimos varias: Un mecanismo simple de detección de colisiones, la utilidad del método Tick, le agregamos las acciones de correr y saltar al personaje, configuramos la cámara de nuestro juego al estilo Side-Scroller 3D, vimos varios macros de variables y funciones de clase para la integración entre el código C++ y el Editor, y otras cosillas. Para el próximo tutorial continuaremos nuestro juego, vamos a agregar un HUD para que el usuario sepa la cantidad de monedas que ha alcanzado y el tiempo que le queda para lograr su objetivo, vamos a programar el GameMode para definir las condiciones de GameOver del juego y muchas cosas más 😉

Espero que te haya sido útil y te guste, si es así, déjame saber tus comentarios y comparte este tutorial con el resto de tus amigos también apasionados por el desarrollo de video juegos con Unreal Engine 4. Hasta la próxima !! . . . bye

Fernando Castillo Coello
Follow me

Fernando Castillo Coello

Gameplay Programmer at GameOlic
I've been into games development with Unreal Engine since 2014 and want to share with you some of the tips and tricks that I learned during my journey.
Fernando Castillo Coello
Follow me

9 pensamientos en “Tutorial: ¿Cómo hacer un juego side-scroller 3D con UE4 ?

  1. javiermaura

    Hola, estoy siguiendo tu tutorial y es una pasada!!. He extendido el ejemplo del SideScroll que viene en el engine, pero tengo un problema.
    He añadido una clase APorcaBrutaHUD que deriva de AHUD. Todo esto en C++. La asigno en el GameMode y funciona!!!
    Pero he querido externder esta clase APorcaBrutaHUD con un BluePrint. Todo bien, pero el problema es que en GameMode ahora no me está encontrando esta nueva clase :(
    ¿Tienes idea de porqué puede ser?

    Responder
    1. nan2cc

      Hola javiermaura, si ya tienes el blueprint que hereda de tu clase para el HUD, asumiendo que está en la ruta Game/SomeFolder/ y se llama PorcaBrutaHUDBlueprint, prueba agregar lo siguiente en el constructor de tu GameMode

      //Carga el Blueprint del HUD, si es cargado satisfactoriamente su instancia será guardada en HUDBPClass.Object
      static ConstructorHelpers::FObjectFinder<UClass> HUDBPClass(TEXT("Class'/Game/SomeFolder/PorcaBrutaHUDBlueprint.PorcaBrutaHUDBlueprint_C'"));
      
      //Si HUDBPClass.Object es válido...
      if (HUDBPClass.Object != NULL)
      {
      	//... Inicializa el HUDClass del GameMode (atributo que representa el HUD del juego) con el blueprint cargado
      	HUDClass = HUDBPClass.Object;
      }
      

      No estoy seguro si es esto lo que te está pasando, pero pruébalo y déjame saber si con eso se resuelve tu problema.

      Saludos

      Responder
      1. javiermaura

        Gracias!!! Al final encontré la solución:
        static ConstructorHelpers::FClassFinder TheHUDOb(TEXT(“/Game/Blueprints/myHUD”));

        if (TheHUDOb.Class != NULL)
        {
        HUDClass = (UClass*)TheHUDOb.Class;
        }

        ObjectFinder no funcionaba, porqué supongo que este método busca en la lista de objetos en la escena, y el HUD no está en la escena, sólo es una clase. El error estaba en que en el template tenia que indicar la clase base primaria ( AHUD) y no APorcaBrutaHUD :(

        Responder
  2. ktulu!

    Hola! En primer lugar: Muchas gracias por tu tutorial. Tengo un pequeño problema con la cámara sidescroll, a ver si me puedes ayudar.
    La cosa es pongo el springarm (anclado a la cápsula), luego la cámara (anclada al springarm) y… nada. Cuando pongo play es como si la cámara estuviera siempre apuntando al lado contrario al que está mi personaje (no estoy seguro de si está colocada en el personaje mismo). He repasado y vuelto a repasar todos los valores, y no tengo idea de que puede estar pasando. :(

    Responder
    1. nan2cc

      Hola ktulu!,

      La verdad ahora mismo no sabría decirte con exactitud el problema que tienes, puede que sea simplemente algún valor de rotación o posición que tengas puesto mal en la cámara o en el springarm. Lo que se me ocurre es que pruebes crear un proyecto nuevo en base a la plantilla de scroll-side que viene con el Unreal (en el tutorial se usa ese mismo principio) y pruebes entonces comparar los valores a ver si encuentras la diferencia y el motivo del problema.

      Saludos

      Responder

Responder a NightmareSystem Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>